mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-30 17:59:55 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
403008ac9e
110
.gitignore
vendored
110
.gitignore
vendored
|
@ -1,7 +1,3 @@
|
|||
# Docs
|
||||
/_build/
|
||||
/docs/
|
||||
|
||||
# Generated code
|
||||
/telethon/tl/functions/
|
||||
/telethon/tl/types/
|
||||
|
@ -11,102 +7,18 @@
|
|||
|
||||
# User session
|
||||
*.session
|
||||
usermedia/
|
||||
/usermedia/
|
||||
|
||||
# Quick tests should live in this file
|
||||
example.py
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
# Builds and testing
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
/dist/
|
||||
/build/
|
||||
/*.egg-info/
|
||||
/readthedocs/_build/
|
||||
/.tox/
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
# API reference docs
|
||||
/docs/
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
/docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IPython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# Nix build results
|
||||
result
|
||||
result-*
|
||||
# File used to manually test new changes, contains sensitive data
|
||||
/example.py
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
include LICENSE
|
||||
include README.rst
|
||||
|
||||
recursive-include telethon *
|
126
default.nix
126
default.nix
|
@ -1,126 +0,0 @@
|
|||
# A NUR-compatible package specification.
|
||||
{ pkgs ? import <nixpkgs> {}, useRelease ? true }:
|
||||
|
||||
rec {
|
||||
# The `lib`, `modules`, and `overlay` names are special
|
||||
lib = ({ pkgs }: { }) { inherit pkgs; }; # functions
|
||||
modules = { }; # NixOS modules
|
||||
overlays = { }; # nixpkgs overlays
|
||||
|
||||
# # development
|
||||
|
||||
# ## development.python-modules
|
||||
|
||||
# use in a shell like
|
||||
# ```nix
|
||||
# ((pkgs.python3.override {
|
||||
# packageOverrides = pythonPackageOverrides;
|
||||
# }).withPackages (ps: [ ps.telethon ])).env
|
||||
# ```
|
||||
pythonPackageOverrides = self: super: let
|
||||
defaultTelethonArgs = { inherit useRelease; };
|
||||
telethonPkg = v: args: self.callPackage (./nix/telethon + "/${v}.nix")
|
||||
(defaultTelethonArgs // args);
|
||||
in rec {
|
||||
telethon = telethon_1;
|
||||
telethon-devel = self.callPackage ./nix/telethon/devel.nix { };
|
||||
|
||||
telethon_1 = telethon_1_10;
|
||||
telethon_1_10 = telethon_1_10_1;
|
||||
telethon_1_10_1 = telethonPkg "1.10" { version = "1.10.1"; };
|
||||
telethon_1_10_0 = telethonPkg "1.10" { version = "1.10.0"; };
|
||||
telethon_1_9 = telethon_1_9_0;
|
||||
telethon_1_9_0 = telethonPkg "1.9" { version = "1.9.0"; };
|
||||
telethon_1_8 = telethon_1_8_0;
|
||||
telethon_1_8_0 = telethonPkg "1.8" { version = "1.8.0"; };
|
||||
telethon_1_7 = telethon_1_7_7;
|
||||
telethon_1_7_7 = telethonPkg "1.7" { version = "1.7.7"; };
|
||||
telethon_1_7_6 = telethonPkg "1.7" { version = "1.7.6"; };
|
||||
telethon_1_7_5 = telethonPkg "1.7" { version = "1.7.5"; };
|
||||
telethon_1_7_4 = telethonPkg "1.7" { version = "1.7.4"; };
|
||||
telethon_1_7_3 = telethonPkg "1.7" { version = "1.7.3"; };
|
||||
telethon_1_7_2 = telethonPkg "1.7" { version = "1.7.2"; };
|
||||
telethon_1_7_1 = telethonPkg "1.7" { version = "1.7.1"; };
|
||||
telethon_1_7_0 = telethonPkg "1.7" { version = "1.7.0"; };
|
||||
telethon_1_6 = telethon_1_6_2;
|
||||
telethon_1_6_2 = telethonPkg "1.6" { version = "1.6.2"; };
|
||||
# 1.6.1.post1: hotpatch that fixed Telethon.egg-info dir perms
|
||||
telethon_1_6_1 = telethonPkg "1.6" { version = "1.6.1"; };
|
||||
telethon_1_6_0 = telethonPkg "1.6" { version = "1.6.0"; };
|
||||
telethon_1_5 = telethon_1_5_5;
|
||||
telethon_1_5_5 = telethonPkg "1.5" { version = "1.5.5"; };
|
||||
telethon_1_5_4 = telethonPkg "1.5" { version = "1.5.4"; };
|
||||
telethon_1_5_3 = telethonPkg "1.5" { version = "1.5.3"; };
|
||||
telethon_1_5_2 = telethonPkg "1.5" { version = "1.5.2"; };
|
||||
telethon_1_5_1 = telethonPkg "1.5" { version = "1.5.1"; };
|
||||
telethon_1_5_0 = telethonPkg "1.5" { version = "1.5.0"; };
|
||||
telethon_1_4 = telethon_1_4_3;
|
||||
telethon_1_4_3 = telethonPkg "1.4" { version = "1.4.3"; };
|
||||
telethon_1_4_2 = telethonPkg "1.4" { version = "1.4.2"; };
|
||||
telethon_1_4_1 = telethonPkg "1.4" { version = "1.4.1"; };
|
||||
telethon_1_4_0 = telethonPkg "1.4" { version = "1.4.0"; };
|
||||
#telethon_1_3_0
|
||||
#telethon_1_2_0
|
||||
#telethon_1_1_1
|
||||
#telethon_1_1_0
|
||||
#telethon_1_0_4
|
||||
#telethon_1_0_3
|
||||
#telethon_1_0_2
|
||||
#telethon_1_0_1
|
||||
#telethon_1_0_0-rc1
|
||||
#telethon_1_0_0
|
||||
#telethon_0_19_1
|
||||
#telethon_0_19_0
|
||||
#telethon_0_18_3
|
||||
#telethon_0_18_2
|
||||
#telethon_0_18_1
|
||||
#telethon_0_18_0
|
||||
#telethon_0_17_4
|
||||
#telethon_0_17_3
|
||||
#telethon_0_17_2
|
||||
#telethon_0_17_1
|
||||
#telethon_0_17_0
|
||||
#telethon_0_16_2
|
||||
#telethon_0_16_1
|
||||
#telethon_0_16_0
|
||||
#telethon_0_15_5
|
||||
#telethon_0_15_4
|
||||
#telethon_0_15_3
|
||||
#telethon_0_15_2
|
||||
#telethon_0_15_1
|
||||
#telethon_0_15_0
|
||||
#telethon_0_14_2
|
||||
#telethon_0_14_1
|
||||
#telethon_0_14_0
|
||||
#telethon_0_13_6
|
||||
#telethon_0_13_5
|
||||
#telethon_0_13_4
|
||||
#telethon_0_13_3
|
||||
#telethon_0_13_2
|
||||
#telethon_0_13_1
|
||||
#telethon_0_13_0
|
||||
#telethon_0_12_2
|
||||
#telethon_0_12_1
|
||||
#telethon_0_12_0
|
||||
#telethon_0_11_5
|
||||
#telethon_0_11_4
|
||||
#telethon_0_11_3
|
||||
#telethon_0_11_2
|
||||
#telethon_0_11_1
|
||||
#telethon_0_11_0
|
||||
#telethon_0_10_1
|
||||
#telethon_0_10_0
|
||||
#telethon_0_9_1
|
||||
#telethon_0_9_0
|
||||
#telethon_0_8_0
|
||||
#telethon_0_7_1
|
||||
#telethon_0_7_0
|
||||
#telethon_0_6_0
|
||||
#telethon_0_5_0
|
||||
#telethon_0_4_0
|
||||
#telethon_0_3_0
|
||||
#telethon_0_2_0
|
||||
#telethon_0_1_0
|
||||
};
|
||||
}
|
||||
|
59
nix/ci.nix
59
nix/ci.nix
|
@ -1,59 +0,0 @@
|
|||
# This file provides all the buildable and cacheable packages and
|
||||
# package outputs in you package set. These are what gets built by CI,
|
||||
# so if you correctly mark packages as
|
||||
#
|
||||
# - broken (using `meta.broken`),
|
||||
# - unfree (using `meta.license.free`), and
|
||||
# - locally built (using `preferLocalBuild`)
|
||||
#
|
||||
# then your CI will be able to build and cache only those packages for
|
||||
# which this is possible.
|
||||
|
||||
{ pkgs ? import <nixpkgs> {}, enableEnvs ? false }:
|
||||
|
||||
with builtins;
|
||||
|
||||
let
|
||||
|
||||
isReserved = n: n == "lib" || n == "overlays" || n == "modules";
|
||||
isDerivation = p: isAttrs p && p ? type && p.type == "derivation";
|
||||
isBuildable = p: !(p.meta.broken or false) && p.meta.license.free or true;
|
||||
isCacheable = p: !(p.preferLocalBuild or false);
|
||||
shouldRecurseForDerivations = p:
|
||||
isAttrs p && p.recurseForDerivations or false;
|
||||
|
||||
nameValuePair = n: v: { name = n; value = v; };
|
||||
|
||||
concatMap = builtins.concatMap or (f: xs: concatLists (map f xs));
|
||||
|
||||
flattenPkgs = s:
|
||||
let
|
||||
f = p:
|
||||
if shouldRecurseForDerivations p then flattenPkgs p
|
||||
else if isDerivation p then [p]
|
||||
else [];
|
||||
in
|
||||
concatMap f (attrValues s);
|
||||
|
||||
outputsOf = p: map (o: p.${o}) p.outputs;
|
||||
|
||||
# build & test packages across Python versions
|
||||
# (withPackages "distributions" are also generated for testing)
|
||||
nurAttrs = import ./extended.nix { inherit pkgs enableEnvs; };
|
||||
|
||||
nurPkgs =
|
||||
flattenPkgs
|
||||
(listToAttrs
|
||||
(map (n: nameValuePair n nurAttrs.${n})
|
||||
(filter (n: !isReserved n)
|
||||
(attrNames nurAttrs))));
|
||||
|
||||
in
|
||||
|
||||
rec {
|
||||
buildPkgs = filter isBuildable nurPkgs;
|
||||
cachePkgs = filter isCacheable buildPkgs;
|
||||
|
||||
buildOutputs = concatMap outputsOf buildPkgs;
|
||||
cacheOutputs = concatMap outputsOf cachePkgs;
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
{ pkgs ? import <nixpkgs> { }, enableEnvs ? true, useRelease ? true }:
|
||||
|
||||
# packages built against all Python versions (along with withPackages
|
||||
# environments for testing)
|
||||
|
||||
# to use for testing, you'll probably want a variant of:
|
||||
# ```sh
|
||||
# nix-shell nix/extended.nix -A telethon-devel-python37 --run "python"
|
||||
# ```
|
||||
|
||||
let
|
||||
inherit (pkgs.lib) attrNames attrValues concatMap head listToAttrs
|
||||
mapAttrsToList optional optionals tail;
|
||||
nurAttrs = import ../default.nix { inherit pkgs useRelease; };
|
||||
|
||||
pyVersions = concatMap (n: optional (pkgs ? ${n}) n) [
|
||||
"python3"
|
||||
"python35"
|
||||
"python36"
|
||||
"python37"
|
||||
# "pypy3"
|
||||
# "pypy35"
|
||||
# "pypy36"
|
||||
# "pypy37"
|
||||
];
|
||||
|
||||
pyPkgEnvs = [
|
||||
[ "telethon" "telethon" ]
|
||||
[ "telethon-devel" "telethon-devel" ]
|
||||
|
||||
[ "telethon_1" "telethon_1" ]
|
||||
[ "telethon_1_10" "telethon_1_10" ]
|
||||
[ "telethon_1_10_1" "telethon_1_10_1" ]
|
||||
[ "telethon_1_10_0" "telethon_1_10_0" ]
|
||||
[ "telethon_1_9" "telethon_1_9" ]
|
||||
[ "telethon_1_9_0" "telethon_1_9_0" ]
|
||||
[ "telethon_1_8" "telethon_1_8" ]
|
||||
[ "telethon_1_8_0" "telethon_1_8_0" ]
|
||||
[ "telethon_1_7" "telethon_1_7" ]
|
||||
[ "telethon_1_7_7" "telethon_1_7_7" ]
|
||||
[ "telethon_1_7_6" "telethon_1_7_6" ]
|
||||
[ "telethon_1_7_5" "telethon_1_7_5" ]
|
||||
[ "telethon_1_7_4" "telethon_1_7_4" ]
|
||||
[ "telethon_1_7_3" "telethon_1_7_3" ]
|
||||
[ "telethon_1_7_2" "telethon_1_7_2" ]
|
||||
[ "telethon_1_7_1" "telethon_1_7_1" ]
|
||||
[ "telethon_1_7_0" "telethon_1_7_0" ]
|
||||
[ "telethon_1_6" "telethon_1_6" ]
|
||||
[ "telethon_1_6_2" "telethon_1_6_2" ]
|
||||
[ "telethon_1_6_1" "telethon_1_6_1" ]
|
||||
[ "telethon_1_6_0" "telethon_1_6_0" ]
|
||||
[ "telethon_1_5" "telethon_1_5" ]
|
||||
[ "telethon_1_5_5" "telethon_1_5_5" ]
|
||||
[ "telethon_1_5_4" "telethon_1_5_4" ]
|
||||
[ "telethon_1_5_3" "telethon_1_5_3" ]
|
||||
[ "telethon_1_5_2" "telethon_1_5_2" ]
|
||||
[ "telethon_1_5_1" "telethon_1_5_1" ]
|
||||
[ "telethon_1_5_0" "telethon_1_5_0" ]
|
||||
[ "telethon_1_4" "telethon_1_4" ]
|
||||
[ "telethon_1_4_3" "telethon_1_4_3" ]
|
||||
# [ "telethon_1_4_2" "telethon_1_4_2" ]
|
||||
# [ "telethon_1_4_1" "telethon_1_4_1" ]
|
||||
# [ "telethon_1_4_0" "telethon_1_4_0" ]
|
||||
];
|
||||
|
||||
getPkgPair = pkgs: n: let p = pkgs.${n}; in { name = n; value = p; };
|
||||
getPkgPairs = pkgs: map (getPkgPair pkgs);
|
||||
pyPkgPairs = py:
|
||||
concatMap (d: map (getPkgPair py.pkgs) (tail d)) pyPkgEnvs;
|
||||
pyPkgEnvPair = pyNm: py: envNm: env: {
|
||||
name = "${envNm}-env-${pyNm}";
|
||||
value = (py.withPackages (ps: map (pn: ps.${pn}) env)).overrideAttrs (o: {
|
||||
name = "${envNm}-${py.name}-env";
|
||||
preferLocalBuild = true;
|
||||
});
|
||||
};
|
||||
pyNurPairs = pyNm: py:
|
||||
map ({ name, value }: { name = "${name}-${pyNm}"; inherit value; })
|
||||
(pyPkgPairs py) ++
|
||||
optionals enableEnvs
|
||||
(map (d: pyPkgEnvPair pyNm py (head d) (tail d)) pyPkgEnvs);
|
||||
in nurAttrs // (listToAttrs (concatMap (py: let
|
||||
python = pkgs.${py}.override {
|
||||
packageOverrides = nurAttrs.pythonPackageOverrides;
|
||||
}; in
|
||||
pyNurPairs py python) pyVersions))
|
|
@ -1,18 +0,0 @@
|
|||
# You can use this file as a nixpkgs overlay. This is useful in the
|
||||
# case where you don't want to add the whole NUR namespace to your
|
||||
# configuration.
|
||||
|
||||
self: super:
|
||||
|
||||
let
|
||||
|
||||
isReserved = n: n == "lib" || n == "overlays" || n == "modules";
|
||||
nameValuePair = n: v: { name = n; value = v; };
|
||||
nurAttrs = import ./default.nix { pkgs = super; };
|
||||
|
||||
in
|
||||
|
||||
builtins.listToAttrs
|
||||
(map (n: nameValuePair n nurAttrs.${n})
|
||||
(builtins.filter (n: !isReserved n)
|
||||
(builtins.attrNames nurAttrs)))
|
|
@ -1,39 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.10.1" = {
|
||||
pypiSha256 = "1ql8ai01c6v3l13lh3csh37jjkrb33gj50jyvdfi3qjn60qs2rfl";
|
||||
sourceSha256 = "1skckq4lai51p476r3shgld89x5yg5snrcrzjfxxxai00lm65cbv";
|
||||
};
|
||||
"1.10.0" = {
|
||||
pypiSha256 = "1n2g2r5w44nlhn229r8kamhwjxggv16gl3jxq25bpg5y4qgrxzd8";
|
||||
sourceSha256 = "1rvrc63j6i7yr887g2csciv4zyy407yhdn4n8q2q00dkildh64qw";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [ rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, async_generator, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null && fetchpatch != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.4.3" = {
|
||||
pypiSha256 = "1igslvhd743qy9p4kfs7lg09s8d5vhn9jhzngpv12797569p4lcj";
|
||||
sourceSha256 = "19vz0ppk7lq1dmqzf47n6h023i08pqvcwnixvm28vrijykq0z315";
|
||||
};
|
||||
"1.4.2" = {
|
||||
pypiSha256 = "1f4ncyfzqj4b6zib0417r01pgnd0hb1p4aiinhlkxkmk7vy5fqfy";
|
||||
sourceSha256 = "0rsbz5kqp0d10gasadir3mgalc9aqq4fcv8xa1p7fg263f43rjl4";
|
||||
};
|
||||
"1.4.1" = {
|
||||
pypiSha256 = "1n0jhdqflinyamzy5krnww7hc0s7pw9yfck1p7816pdbgir74qsw";
|
||||
sourceSha256 = "07q48gw4ry3wf9yzi6kf8lw3b23a0dvk9r8sabpxwrlqy7gnksxx";
|
||||
};
|
||||
"1.4.0" = {
|
||||
version = "1.4";
|
||||
pypiSha256 = "1g7rznwmj87n9k86zby9i75h570hm84izrv0srhsmxi52pjan1ml";
|
||||
sourceSha256 = "14nv86yrj01wmlj5cfg6iq5w03ssl67av1arfy9mq1935mly5nly";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
patches = lib.optionals (!useRelease) [
|
||||
(if (lib.versionOlder version "1.4.3") then
|
||||
common.patches.generator-use-pathlib-to-1_4_3
|
||||
else
|
||||
common.patches.generator-use-pathlib-from-1_4_3-to-1_5_0)
|
||||
common.patches.generator-use-pathlib-open-to-1_5_3
|
||||
common.patches.sort-generated-tlobjects-to-1_7_1
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [ async_generator rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, async_generator, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null && fetchpatch != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.5.5" = {
|
||||
pypiSha256 = "1qpc4vc3lidhlp1c7521nxizjr6y5c3l9x41knqv02x8n3l9knxa";
|
||||
sourceSha256 = "1x5niscjbrg5a0cg261z6awln57v3nn8si5j58vhsnckws2c48a5";
|
||||
};
|
||||
"1.5.4" = {
|
||||
pypiSha256 = "1kjqi3wy4hswsf3vmrjg7z5c3f9wpdfk4wz1yfsqmj9ppwllkjsj";
|
||||
sourceSha256 = "0rmp9zk7a354nb39c01mjcrhi2j6v9im40xmdcvmizx990vlv476";
|
||||
};
|
||||
"1.5.3" = {
|
||||
pypiSha256 = "11xd5ni0chzsfny0vwwqyh37mvmrwrk2bmkhwp1ipbxyis8jjjia";
|
||||
sourceSha256 = "1l3i6wx3fgcy3vmr75qdbv5fvc5qnk0j47hv7jszsqq9rvqvz2xs";
|
||||
};
|
||||
"1.5.2" = {
|
||||
pypiSha256 = "0ymv6l9xn41sgpkilqkivwbjna89m43i0a728lak2cppp7i1i1h7";
|
||||
sourceSha256 = "0gnqvlhh3qyvibl7icn6774rshlx1nnhb5f78609da44743lyv17";
|
||||
};
|
||||
"1.5.1" = {
|
||||
pypiSha256 = "1ypxpsfj814gzln4fl7z17l1l6q0bzd5p1ivas85yim3a992ixww";
|
||||
sourceSha256 = "15w5nshvmj8hgqdcbpw0fjcf1cspaci8dldm9ml1pmijw7zgmpdg";
|
||||
};
|
||||
"1.5.0" = {
|
||||
version = "1.5";
|
||||
pypiSha256 = "1kzkzcxyz7adjzvm2ml9faz2c5yx469j211yvi5xfvjwp58ic2jc";
|
||||
sourceSha256 = "12232d3xfv0bbykk9xaxpxsr3656ywjx4ra1q5q99rpp6wv438n1";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
patches = lib.optionals (!useRelease) ([
|
||||
common.patches.sort-generated-tlobjects-to-1_7_1
|
||||
] ++ lib.optional (lib.versionOlder version "1.5.3")
|
||||
common.patches.generator-use-pathlib-open-to-1_5_3);
|
||||
|
||||
propagatedBuildInputs = [ async_generator rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null && fetchpatch != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.6.2" = {
|
||||
pypiSha256 = "074h5gj0c330rb1nxzpqm31fp1vw7calh1cdkapbjx90j769iz18";
|
||||
sourceSha256 = "1daqlb4sva5qkljzbjr8xvjfgp7bdcrl2li1i4434za6a0isgd3j";
|
||||
};
|
||||
"1.6.1" = {
|
||||
# hotpatch with missing .pyc files and fixed Telethon.egg-info perms
|
||||
pypiVersion = "1.6.1.post1";
|
||||
pypiSha256 = "17s1qp69bbj6jniam9wbcpaj60ah56sjw0q3kr8ca28y17s88si7";
|
||||
# pypiVersion = "1.6.1";
|
||||
# pypiSha256 = "036lhr1jr79np74c6ih51c4pjy828r3lvwcq07q5wynyjprm1qbz";
|
||||
sourceSha256 = "1hk1bpnk51rpsifb67s31c2qph5hmw28i2vgh97i4i56vynx2yxz";
|
||||
};
|
||||
"1.6.0" = {
|
||||
version = "1.6";
|
||||
pypiSha256 = "06prmld9068zcm9rfmq3rpq1szw72c6dkxl62b035i9w8wdpvg0m";
|
||||
sourceSha256 = "0qk14mrnvv9a043ik0y2w6q97l83abvbvn441zn2jl00w4ykfqrh";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
patches = lib.optional (!useRelease)
|
||||
common.patches.sort-generated-tlobjects-to-1_7_1;
|
||||
|
||||
propagatedBuildInputs = [ rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.7.7" = {
|
||||
pypiSha256 = "0mgpihjc7g4gfrq57srripdavxbsgivn4qsjanv3yds5drskciv0";
|
||||
sourceSha256 = "08c3iakd7fyacc79pg8hyzpa6zx3gbp7xivi10af34zj775lp2pi";
|
||||
};
|
||||
"1.7.6" = {
|
||||
pypiSha256 = "192xda98685s3hmz7ircxpsn7yq913y0r1kmqrsav90m4g4djn4j";
|
||||
sourceSha256 = "1ss2pfpd3hby25g9ighbr7ccp66awfzda4srsnvr9s6i28har6ag";
|
||||
};
|
||||
"1.7.5" = {
|
||||
pypiSha256 = "0i5s7ahicw5k0s1i7pi26vc6rp6ppr1gr848sa61yh3qqa4c0qnr";
|
||||
sourceSha256 = "1rssh0l466h9y6v0z095c9aa63nz9im7gg5771jjj5w70mkpm5w6";
|
||||
};
|
||||
"1.7.4" = {
|
||||
pypiSha256 = "1qpc9f1y559zdwz59qqz4hbf1mrynjjbcg357nzaa2x5a2q4lz0s";
|
||||
sourceSha256 = "1q43lwfp67q4skfcrb6sdlnjw4ajrpizf08fd9wjrw521kkd8g4y";
|
||||
};
|
||||
"1.7.3" = {
|
||||
pypiSha256 = "0s8qmsarlfgpb0k3w50siv354hpa7b1dnrjjd0iqz7vc5bc7ni84";
|
||||
sourceSha256 = "0c393smp1qm8kk39r0k31p74p89qzvjdjxq4bxq75h07a1yqbs8x";
|
||||
};
|
||||
"1.7.2" = {
|
||||
pypiSha256 = "0465dwikhpbka2sj1g952rac03jkixq497gbmmyx2i9xb594db27";
|
||||
sourceSha256 = "1gw09zbaqvn074skwjhmm4yp8p75rw9njwjbkcfvqb4gr6dg8wpq";
|
||||
};
|
||||
"1.7.1" = {
|
||||
pypiSha256 = "186z6imf7zqy8vf4yv2w2kxpd7lxmfppa1qi8nxjdgq8rz7wbglf";
|
||||
sourceSha256 = "05mpqfj4w5qxyl1ai5p0f31pkagz55xxh8060r8y9i3d44j9bn1c";
|
||||
};
|
||||
"1.7.0" = {
|
||||
version = "1.7";
|
||||
pypiSha256 = "06cqb121k2y0h3x7gvckyvbsn97wc1a25pghinxz2vb7vg8wwxvw";
|
||||
sourceSha256 = "0myx32hqax71ijfw6ksxvk27cb6x06kbz8jb7ib9d1cayr2viir6";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
patches = lib.optional (!useRelease && lib.versionOlder version "1.7.1")
|
||||
common.patches.sort-generated-tlobjects-to-1_7_1;
|
||||
|
||||
propagatedBuildInputs = [ rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.8.0" = {
|
||||
pypiSha256 = "099br8ldjrfzwipv7g202lnjghmqj79j6gicgx11s0vawb5mb3vf";
|
||||
sourceSha256 = "1q5mcijmjw2m2v3ilw28xnavmcdck5md0k98kwnz0kyx4iqckcv0";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [ rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
{ lib, buildPythonPackage, pythonOlder
|
||||
, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null
|
||||
, pyaes, rsa
|
||||
, version
|
||||
, useRelease ? true
|
||||
}:
|
||||
|
||||
assert useRelease -> fetchPypi != null;
|
||||
assert !useRelease -> fetchFromGitHub != null;
|
||||
let
|
||||
common = import ./common.nix {
|
||||
inherit lib fetchFromGitHub fetchPypi fetchpatch;
|
||||
};
|
||||
versions = {
|
||||
"1.9.0" = {
|
||||
pypiSha256 = "1p4y4qd1ndzi1lg4fhnvq1rqz7611yrwnwwvzh63aazfpzaplyd8";
|
||||
sourceSha256 = "1g6khxc7mvm3q8rqksw9dwn4l2w8wzvr3zb74n2lb7g5ilpxsadd";
|
||||
};
|
||||
};
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
inherit version;
|
||||
|
||||
src = common.fetchTelethon {
|
||||
inherit useRelease version;
|
||||
versionData = versions.${version};
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [ rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
{ lib, fetchFromGitHub ? null, fetchPypi ? null, fetchpatch ? null }:
|
||||
|
||||
rec {
|
||||
fetchTelethon = { useRelease, version, versionData }:
|
||||
if useRelease then assert versionData.pypiSha256 != null; fetchPypi {
|
||||
pname = "Telethon";
|
||||
version = versionData.pypiVersion or (versionData.version or version);
|
||||
sha256 = versionData.pypiSha256;
|
||||
} else assert versionData.sourceSha256 != null; fetchFromGitHub {
|
||||
owner = "LonamiWebs";
|
||||
repo = "Telethon";
|
||||
rev = versionData.rev or "v${versionData.version or version}";
|
||||
sha256 = versionData.sourceSha256;
|
||||
};
|
||||
|
||||
fetchpatchTelethon = { rev, ... } @ args:
|
||||
fetchpatch ({
|
||||
url = "https://github.com/LonamiWebs/Telethon/commit/${rev}.patch";
|
||||
} // (builtins.removeAttrs args [ "rev" ]));
|
||||
|
||||
# sorted by name, then by logical version range
|
||||
patches = rec {
|
||||
generator-use-pathlib-to-1_4_3 = ./generator-use-pathlib-to-1_4_3.patch;
|
||||
generator-use-pathlib-from-1_4_3-to-1_5_0 = [
|
||||
(fetchpatchTelethon {
|
||||
rev = "e71c556ca71aec11166dc66f949a05e700aeb24f";
|
||||
sha256 = "058phfaggf22j0cjpy9j17y63zgd9m8j4qf7ldsg0jqm1vrym76w";
|
||||
})
|
||||
(fetchpatchTelethon {
|
||||
rev = "8224e5aabf18bb31c6af8c460c38ced11756f080";
|
||||
sha256 = "0x3xfkld4d2kc0a1a8ldxy85pi57zaipq3b401b16r6rzbi4sh1j";
|
||||
})
|
||||
(fetchpatchTelethon {
|
||||
rev = "aefa429236d28ae68bec4e4ef9f12d13f647dfe6";
|
||||
sha256 = "043hks8hg5sli1amfv5453h831nwy4dgyw8xr4xxfaxh74754icx";
|
||||
})
|
||||
];
|
||||
generator-use-pathlib-open-to-1_5_3 = fetchpatchTelethon {
|
||||
rev = "b57e3e3e0a752903fe7d539fb87787ec6712a3d9";
|
||||
sha256 = "1rl3lkwfi3h62ppzglrmz13zfai8i8cchzqgbjccr4l7nzh1n6nq";
|
||||
};
|
||||
sort-generated-tlobjects-to-1_7_1 = fetchpatchTelethon {
|
||||
rev = "08f8aa3c526c043c107ec1b489b89c011555722f";
|
||||
sha256 = "1lkvvjzhm9jfrxpm4hbvvysz5f3qi0v4f7vqnfmrzawl73s8qk80";
|
||||
};
|
||||
};
|
||||
|
||||
meta = let inherit (lib) licenses maintainers; in {
|
||||
description = "Full-featured Telegram client library for Python 3";
|
||||
fullDescription = ''
|
||||
Telegram is a popular messaging application. This library is meant to
|
||||
make it easy for you to write Python programs that can interact with
|
||||
Telegram. Think of it as a wrapper that has already done the heavy job
|
||||
for you, so you can focus on developing an application.
|
||||
'';
|
||||
homepage = https://github.com/LonamiWebs/Telethon;
|
||||
license = licenses.mit;
|
||||
maintainers = [ maintainers.bb010g maintainers.nyanloutre ];
|
||||
};
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{ lib, buildPythonPackage, nix-gitignore, pythonOlder
|
||||
, async_generator, pyaes, rsa
|
||||
}:
|
||||
|
||||
let
|
||||
common = import ./common.nix { inherit lib; };
|
||||
in buildPythonPackage rec {
|
||||
pname = "telethon";
|
||||
# If pinning to a specific commit, use the following output instead:
|
||||
# ```sh
|
||||
# TZ=UTC git show -s --format=format:%cd --date=short-local
|
||||
# ```
|
||||
version = "HEAD";
|
||||
|
||||
src = nix-gitignore.gitignoreSource ''
|
||||
/.git
|
||||
/default.nix
|
||||
/nix
|
||||
'' ../..;
|
||||
|
||||
propagatedBuildInputs = [ async_generator rsa pyaes ];
|
||||
|
||||
doCheck = false; # No tests available
|
||||
|
||||
disabled = pythonOlder "3.5";
|
||||
meta = common.meta;
|
||||
}
|
|
@ -1,819 +0,0 @@
|
|||
--- a/setup.py
|
||||
+++ b/setup.py
|
||||
@@ -12,10 +12,11 @@
|
||||
|
||||
import itertools
|
||||
import json
|
||||
-import os
|
||||
import re
|
||||
import shutil
|
||||
-from codecs import open
|
||||
+from os import chdir
|
||||
+from pathlib import Path
|
||||
+from subprocess import run
|
||||
from sys import argv
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
@@ -29,30 +30,29 @@
|
||||
self.original = None
|
||||
|
||||
def __enter__(self):
|
||||
- self.original = os.path.abspath(os.path.curdir)
|
||||
- os.chdir(os.path.abspath(os.path.dirname(__file__)))
|
||||
+ self.original = Path('.')
|
||||
+ chdir(str(Path(__file__).parent))
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
- os.chdir(self.original)
|
||||
+ chdir(str(self.original))
|
||||
|
||||
|
||||
-GENERATOR_DIR = 'telethon_generator'
|
||||
-LIBRARY_DIR = 'telethon'
|
||||
+GENERATOR_DIR = Path('telethon_generator')
|
||||
+LIBRARY_DIR = Path('telethon')
|
||||
|
||||
-ERRORS_IN_JSON = os.path.join(GENERATOR_DIR, 'data', 'errors.json')
|
||||
-ERRORS_IN_DESC = os.path.join(GENERATOR_DIR, 'data', 'error_descriptions')
|
||||
-ERRORS_OUT = os.path.join(LIBRARY_DIR, 'errors', 'rpcerrorlist.py')
|
||||
+ERRORS_IN_JSON = GENERATOR_DIR / 'data/errors.json'
|
||||
+ERRORS_IN_DESC = GENERATOR_DIR / 'data/error_descriptions'
|
||||
+ERRORS_OUT = LIBRARY_DIR / 'errors/rpcerrorlist.py'
|
||||
|
||||
-INVALID_BM_IN = os.path.join(GENERATOR_DIR, 'data', 'invalid_bot_methods.json')
|
||||
+INVALID_BM_IN = GENERATOR_DIR / 'data/invalid_bot_methods.json'
|
||||
|
||||
-TLOBJECT_IN_CORE_TL = os.path.join(GENERATOR_DIR, 'data', 'mtproto_api.tl')
|
||||
-TLOBJECT_IN_TL = os.path.join(GENERATOR_DIR, 'data', 'telegram_api.tl')
|
||||
-TLOBJECT_OUT = os.path.join(LIBRARY_DIR, 'tl')
|
||||
+TLOBJECT_IN_TLS = [Path(x) for x in GENERATOR_DIR.glob('data/*.tl')]
|
||||
+TLOBJECT_OUT = LIBRARY_DIR / 'tl'
|
||||
IMPORT_DEPTH = 2
|
||||
|
||||
-DOCS_IN_RES = os.path.join(GENERATOR_DIR, 'data', 'html')
|
||||
-DOCS_OUT = 'docs'
|
||||
+DOCS_IN_RES = GENERATOR_DIR / 'data/html'
|
||||
+DOCS_OUT = Path('docs')
|
||||
|
||||
|
||||
def generate(which):
|
||||
@@ -60,15 +60,12 @@
|
||||
from telethon_generator.generators import\
|
||||
generate_errors, generate_tlobjects, generate_docs, clean_tlobjects
|
||||
|
||||
- # Older Python versions open the file as bytes instead (3.4.2)
|
||||
- with open(INVALID_BM_IN, 'r') as f:
|
||||
+ with INVALID_BM_IN.open('r') as f:
|
||||
invalid_bot_methods = set(json.load(f))
|
||||
-
|
||||
- layer = find_layer(TLOBJECT_IN_TL)
|
||||
+ layer = next(filter(None, map(find_layer, TLOBJECT_IN_TLS)))
|
||||
errors = list(parse_errors(ERRORS_IN_JSON, ERRORS_IN_DESC))
|
||||
- tlobjects = list(itertools.chain(
|
||||
- parse_tl(TLOBJECT_IN_CORE_TL, layer, invalid_bot_methods),
|
||||
- parse_tl(TLOBJECT_IN_TL, layer, invalid_bot_methods)))
|
||||
+ tlobjects = list(itertools.chain(*(
|
||||
+ parse_tl(file, layer, invalid_bot_methods) for file in TLOBJECT_IN_TLS)))
|
||||
|
||||
if not which:
|
||||
which.extend(('tl', 'errors'))
|
||||
@@ -96,30 +93,29 @@
|
||||
which.remove('errors')
|
||||
print(action, 'RPCErrors...')
|
||||
if clean:
|
||||
- if os.path.isfile(ERRORS_OUT):
|
||||
- os.remove(ERRORS_OUT)
|
||||
+ if ERRORS_OUT.is_file():
|
||||
+ ERRORS_OUT.unlink()
|
||||
else:
|
||||
- with open(ERRORS_OUT, 'w', encoding='utf-8') as file:
|
||||
+ with ERRORS_OUT.open('w') as file:
|
||||
generate_errors(errors, file)
|
||||
|
||||
if 'docs' in which:
|
||||
which.remove('docs')
|
||||
print(action, 'documentation...')
|
||||
if clean:
|
||||
- if os.path.isdir(DOCS_OUT):
|
||||
- shutil.rmtree(DOCS_OUT)
|
||||
+ if DOCS_OUT.is_dir():
|
||||
+ shutil.rmtree(str(DOCS_OUT))
|
||||
else:
|
||||
generate_docs(tlobjects, methods, layer, DOCS_IN_RES, DOCS_OUT)
|
||||
|
||||
if 'json' in which:
|
||||
which.remove('json')
|
||||
print(action, 'JSON schema...')
|
||||
- mtproto = 'mtproto_api.json'
|
||||
- telegram = 'telegram_api.json'
|
||||
+ json_files = [x.with_suffix('.json') for x in TLOBJECT_IN_TLS]
|
||||
if clean:
|
||||
- for x in (mtproto, telegram):
|
||||
- if os.path.isfile(x):
|
||||
- os.remove(x)
|
||||
+ for file in json_files:
|
||||
+ if file.is_file():
|
||||
+ file.unlink()
|
||||
else:
|
||||
def gen_json(fin, fout):
|
||||
methods = []
|
||||
@@ -131,8 +130,8 @@
|
||||
with open(fout, 'w') as f:
|
||||
json.dump(what, f, indent=2)
|
||||
|
||||
- gen_json(TLOBJECT_IN_CORE_TL, mtproto)
|
||||
- gen_json(TLOBJECT_IN_TL, telegram)
|
||||
+ for fin, fout in zip(TLOBJECT_IN_TLS, json_files):
|
||||
+ gen_json(fin, fout)
|
||||
|
||||
if which:
|
||||
print('The following items were not understood:', which)
|
||||
@@ -156,22 +155,17 @@
|
||||
print('Packaging for PyPi aborted, importing the module failed.')
|
||||
return
|
||||
|
||||
- # Need python3.5 or higher, but Telethon is supposed to support 3.x
|
||||
- # Place it here since noone should be running ./setup.py pypi anyway
|
||||
- from subprocess import run
|
||||
- from shutil import rmtree
|
||||
-
|
||||
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||
- rmtree(x, ignore_errors=True)
|
||||
+ shutil.rmtree(x, ignore_errors=True)
|
||||
run('python3 setup.py sdist', shell=True)
|
||||
run('python3 setup.py bdist_wheel', shell=True)
|
||||
run('twine upload dist/*', shell=True)
|
||||
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||
- rmtree(x, ignore_errors=True)
|
||||
+ shutil.rmtree(x, ignore_errors=True)
|
||||
|
||||
else:
|
||||
# e.g. install from GitHub
|
||||
- if os.path.isdir(GENERATOR_DIR):
|
||||
+ if GENERATOR_DIR.is_dir():
|
||||
generate(['tl', 'errors'])
|
||||
|
||||
# Get the long description from the README file
|
||||
--- a/telethon_generator/docswriter.py
|
||||
+++ b/telethon_generator/docswriter.py
|
||||
@@ -2,0 +2,0 @@
|
||||
|
||||
|
||||
class DocsWriter:
|
||||
- """Utility class used to write the HTML files used on the documentation"""
|
||||
- def __init__(self, filename, type_to_path):
|
||||
- """Initializes the writer to the specified output file,
|
||||
- creating the parent directories when used if required.
|
||||
-
|
||||
- 'type_to_path_function' should be a function which, given a type
|
||||
- name and a named argument relative_to, returns the file path for
|
||||
- the specified type, relative to the given filename
|
||||
+ """
|
||||
+ Utility class used to write the HTML files used on the documentation.
|
||||
+ """
|
||||
+ def __init__(self, root, filename, type_to_path):
|
||||
"""
|
||||
+ Initializes the writer to the specified output file,
|
||||
+ creating the parent directories when used if required.
|
||||
+ """
|
||||
+ self.root = root
|
||||
self.filename = filename
|
||||
+ self._parent = str(self.filename.parent)
|
||||
self.handle = None
|
||||
+ self.title = ''
|
||||
|
||||
# Should be set before calling adding items to the menu
|
||||
self.menu_separator_tag = None
|
||||
|
||||
- # Utility functions TODO There must be a better way
|
||||
- self.type_to_path = lambda t: type_to_path(
|
||||
- t, relative_to=self.filename
|
||||
- )
|
||||
+ # Utility functions
|
||||
+ self.type_to_path = lambda t: self._rel(type_to_path(t))
|
||||
|
||||
# Control signals
|
||||
self.menu_began = False
|
||||
@@ -30,11 +30,20 @@
|
||||
self.write_copy_script = False
|
||||
self._script = ''
|
||||
|
||||
+ def _rel(self, path):
|
||||
+ """
|
||||
+ Get the relative path for the given path from the current
|
||||
+ file by working around https://bugs.python.org/issue20012.
|
||||
+ """
|
||||
+ return os.path.relpath(str(path), self._parent)
|
||||
+
|
||||
# High level writing
|
||||
- def write_head(self, title, relative_css_path, default_css):
|
||||
+ def write_head(self, title, css_path, default_css):
|
||||
"""Writes the head part for the generated document,
|
||||
with the given title and CSS
|
||||
"""
|
||||
+ #
|
||||
+ self.title = title
|
||||
self.write(
|
||||
'''<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -54,17 +63,17 @@
|
||||
<body>
|
||||
<div id="main_div">''',
|
||||
title=title,
|
||||
- rel_css=relative_css_path.rstrip('/'),
|
||||
+ rel_css=self._rel(css_path),
|
||||
def_css=default_css
|
||||
)
|
||||
|
||||
- def set_menu_separator(self, relative_image_path):
|
||||
+ def set_menu_separator(self, img):
|
||||
"""Sets the menu separator.
|
||||
Must be called before adding entries to the menu
|
||||
"""
|
||||
- if relative_image_path:
|
||||
- self.menu_separator_tag = \
|
||||
- '<img src="{}" alt="/" />'.format(relative_image_path)
|
||||
+ if img:
|
||||
+ self.menu_separator_tag = '<img src="{}" alt="/" />'.format(
|
||||
+ self._rel(img))
|
||||
else:
|
||||
self.menu_separator_tag = None
|
||||
|
||||
@@ -80,7 +89,7 @@
|
||||
|
||||
self.write('<li>')
|
||||
if link:
|
||||
- self.write('<a href="{}">', link)
|
||||
+ self.write('<a href="{}">', self._rel(link))
|
||||
|
||||
# Write the real menu entry text
|
||||
self.write(name)
|
||||
@@ -210,7 +219,7 @@
|
||||
if bold:
|
||||
self.write('<b>')
|
||||
if link:
|
||||
- self.write('<a href="{}">', link)
|
||||
+ self.write('<a href="{}">', self._rel(link))
|
||||
|
||||
# Finally write the real table data, the given text
|
||||
self.write(text)
|
||||
@@ -278,10 +287,7 @@
|
||||
# With block
|
||||
def __enter__(self):
|
||||
# Sanity check
|
||||
- parent = os.path.dirname(self.filename)
|
||||
- if parent:
|
||||
- os.makedirs(parent, exist_ok=True)
|
||||
-
|
||||
+ self.filename.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.handle = open(self.filename, 'w', encoding='utf-8')
|
||||
return self
|
||||
|
||||
--- a/telethon_generator/generators/docs.py
|
||||
+++ b/telethon_generator/generators/docs.py
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
-import csv
|
||||
import functools
|
||||
-import os
|
||||
import re
|
||||
import shutil
|
||||
from collections import defaultdict
|
||||
+from pathlib import Path
|
||||
|
||||
from ..docswriter import DocsWriter
|
||||
from ..parsers import TLObject, Usability
|
||||
@@ -35,41 +34,33 @@
|
||||
|
||||
def _get_create_path_for(root, tlobject, make=True):
|
||||
"""Creates and returns the path for the given TLObject at root."""
|
||||
- out_dir = 'methods' if tlobject.is_function else 'constructors'
|
||||
+ # TODO Can we pre-create all required directories?
|
||||
+ out_dir = root / ('methods' if tlobject.is_function else 'constructors')
|
||||
if tlobject.namespace:
|
||||
- out_dir = os.path.join(out_dir, tlobject.namespace)
|
||||
+ out_dir /= tlobject.namespace
|
||||
|
||||
- out_dir = os.path.join(root, out_dir)
|
||||
if make:
|
||||
- os.makedirs(out_dir, exist_ok=True)
|
||||
- return os.path.join(out_dir, _get_file_name(tlobject))
|
||||
+ out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
+ return out_dir / _get_file_name(tlobject)
|
||||
|
||||
-def _get_path_for_type(root, type_, relative_to='.'):
|
||||
+
|
||||
+def _get_path_for_type(type_):
|
||||
"""Similar to `_get_create_path_for` but for only type names."""
|
||||
if type_.lower() in CORE_TYPES:
|
||||
- path = 'index.html#%s' % type_.lower()
|
||||
+ return Path('index.html#%s' % type_.lower())
|
||||
elif '.' in type_:
|
||||
namespace, name = type_.split('.')
|
||||
- path = 'types/%s/%s' % (namespace, _get_file_name(name))
|
||||
+ return Path('types', namespace, _get_file_name(name))
|
||||
else:
|
||||
- path = 'types/%s' % _get_file_name(type_)
|
||||
-
|
||||
- return _get_relative_path(os.path.join(root, path), relative_to)
|
||||
-
|
||||
-
|
||||
-def _get_relative_path(destination, relative_to, folder=False):
|
||||
- """Return the relative path to destination from relative_to."""
|
||||
- if not folder:
|
||||
- relative_to = os.path.dirname(relative_to)
|
||||
-
|
||||
- return os.path.relpath(destination, start=relative_to)
|
||||
+ return Path('types', _get_file_name(type_))
|
||||
|
||||
|
||||
def _find_title(html_file):
|
||||
"""Finds the <title> for the given HTML file, or (Unknown)."""
|
||||
- with open(html_file, 'r') as fp:
|
||||
- for line in fp:
|
||||
+ # TODO Is it necessary to read files like this?
|
||||
+ with html_file.open() as f:
|
||||
+ for line in f:
|
||||
if '<title>' in line:
|
||||
# + 7 to skip len('<title>')
|
||||
return line[line.index('<title>') + 7:line.index('</title>')]
|
||||
@@ -77,25 +68,27 @@
|
||||
return '(Unknown)'
|
||||
|
||||
|
||||
-def _build_menu(docs, filename, root, relative_main_index):
|
||||
- """Builds the menu using the given DocumentWriter up to 'filename',
|
||||
- which must be a file (it cannot be a directory)"""
|
||||
- filename = _get_relative_path(filename, root)
|
||||
- docs.add_menu('API', relative_main_index)
|
||||
-
|
||||
- items = filename.split('/')
|
||||
- for i in range(len(items) - 1):
|
||||
- item = items[i]
|
||||
- link = '../' * (len(items) - (i + 2))
|
||||
- link += 'index.html'
|
||||
- docs.add_menu(item.title(), link=link)
|
||||
+def _build_menu(docs):
|
||||
+ """
|
||||
+ Builds the menu used for the current ``DocumentWriter``.
|
||||
+ """
|
||||
+
|
||||
+ paths = []
|
||||
+ current = docs.filename
|
||||
+ while current != docs.root:
|
||||
+ current = current.parent
|
||||
+ paths.append(current)
|
||||
+
|
||||
+ for path in reversed(paths):
|
||||
+ docs.add_menu(path.stem.title(), link=path / 'index.html')
|
||||
+
|
||||
+ if docs.filename.stem != 'index':
|
||||
+ docs.add_menu(docs.title, link=docs.filename)
|
||||
|
||||
- if items[-1] != 'index.html':
|
||||
- docs.add_menu(os.path.splitext(items[-1])[0])
|
||||
docs.end_menu()
|
||||
|
||||
|
||||
-def _generate_index(folder, original_paths, root,
|
||||
+def _generate_index(root, folder, paths,
|
||||
bots_index=False, bots_index_paths=()):
|
||||
"""Generates the index file for the specified folder"""
|
||||
# Determine the namespaces listed here (as sub folders)
|
||||
@@ -105,38 +98,24 @@
|
||||
INDEX = 'index.html'
|
||||
BOT_INDEX = 'botindex.html'
|
||||
|
||||
- if not bots_index:
|
||||
- for item in os.listdir(folder):
|
||||
- if os.path.isdir(os.path.join(folder, item)):
|
||||
- namespaces.append(item)
|
||||
- elif item not in (INDEX, BOT_INDEX):
|
||||
- files.append(item)
|
||||
- else:
|
||||
- # bots_index_paths should be a list of "namespace/method.html"
|
||||
- # or "method.html"
|
||||
- for item in bots_index_paths:
|
||||
- dirname = os.path.dirname(item)
|
||||
- if dirname and dirname not in namespaces:
|
||||
- namespaces.append(dirname)
|
||||
- elif not dirname and item not in (INDEX, BOT_INDEX):
|
||||
- files.append(item)
|
||||
-
|
||||
- paths = {k: _get_relative_path(v, folder, folder=True)
|
||||
- for k, v in original_paths.items()}
|
||||
+ for item in (bots_index_paths or folder.iterdir()):
|
||||
+ if item.is_dir():
|
||||
+ namespaces.append(item)
|
||||
+ elif item.name not in (INDEX, BOT_INDEX):
|
||||
+ files.append(item)
|
||||
|
||||
# Now that everything is setup, write the index.html file
|
||||
- filename = os.path.join(folder, BOT_INDEX if bots_index else INDEX)
|
||||
- with DocsWriter(filename, type_to_path=_get_path_for_type) as docs:
|
||||
+ filename = folder / (BOT_INDEX if bots_index else INDEX)
|
||||
+ with DocsWriter(root, filename, _get_path_for_type) as docs:
|
||||
# Title should be the current folder name
|
||||
- docs.write_head(folder.title(),
|
||||
- relative_css_path=paths['css'],
|
||||
- default_css=original_paths['default_css'])
|
||||
+ docs.write_head(str(folder).title(),
|
||||
+ css_path=paths['css'],
|
||||
+ default_css=paths['default_css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
- _build_menu(docs, filename, root,
|
||||
- relative_main_index=paths['index_all'])
|
||||
+ _build_menu(docs)
|
||||
+ docs.write_title(str(filename.parent.relative_to(root)).title())
|
||||
|
||||
- docs.write_title(_get_relative_path(folder, root, folder=True).title())
|
||||
if bots_index:
|
||||
docs.write_text('These are the methods that you may be able to '
|
||||
'use as a bot. Click <a href="{}">here</a> to '
|
||||
@@ -153,24 +132,22 @@
|
||||
namespace_paths = []
|
||||
if bots_index:
|
||||
for item in bots_index_paths:
|
||||
- if os.path.dirname(item) == namespace:
|
||||
- namespace_paths.append(os.path.basename(item))
|
||||
- _generate_index(os.path.join(folder, namespace),
|
||||
- original_paths, root,
|
||||
+ if item.parent == namespace:
|
||||
+ namespace_paths.append(item)
|
||||
+
|
||||
+ _generate_index(root, namespace, paths,
|
||||
bots_index, namespace_paths)
|
||||
- if bots_index:
|
||||
- docs.add_row(namespace.title(),
|
||||
- link=os.path.join(namespace, BOT_INDEX))
|
||||
- else:
|
||||
- docs.add_row(namespace.title(),
|
||||
- link=os.path.join(namespace, INDEX))
|
||||
+
|
||||
+ docs.add_row(
|
||||
+ namespace.stem.title(),
|
||||
+ link=namespace / (BOT_INDEX if bots_index else INDEX))
|
||||
|
||||
docs.end_table()
|
||||
|
||||
docs.write_title('Available items')
|
||||
docs.begin_table(2)
|
||||
|
||||
- files = [(f, _find_title(os.path.join(folder, f))) for f in files]
|
||||
+ files = [(f, _find_title(f)) for f in files]
|
||||
files.sort(key=lambda t: t[1])
|
||||
|
||||
for file, title in files:
|
||||
@@ -231,7 +208,7 @@
|
||||
))
|
||||
|
||||
|
||||
-def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
|
||||
+def _write_html_pages(root, tlobjects, methods, layer, input_res):
|
||||
"""
|
||||
Generates the documentation HTML files from from ``scheme.tl``
|
||||
to ``/methods`` and ``/constructors``, etc.
|
||||
@@ -239,21 +216,18 @@
|
||||
# Save 'Type: [Constructors]' for use in both:
|
||||
# * Seeing the return type or constructors belonging to the same type.
|
||||
# * Generating the types documentation, showing available constructors.
|
||||
- original_paths = {
|
||||
- 'css': 'css',
|
||||
- 'arrow': 'img/arrow.svg',
|
||||
- 'search.js': 'js/search.js',
|
||||
- '404': '404.html',
|
||||
- 'index_all': 'index.html',
|
||||
- 'bot_index': 'botindex.html',
|
||||
- 'index_types': 'types/index.html',
|
||||
- 'index_methods': 'methods/index.html',
|
||||
- 'index_constructors': 'constructors/index.html'
|
||||
- }
|
||||
- original_paths = {k: os.path.join(output_dir, v)
|
||||
- for k, v in original_paths.items()}
|
||||
-
|
||||
- original_paths['default_css'] = 'light' # docs.<name>.css, local path
|
||||
+ paths = {k: root / v for k, v in (
|
||||
+ ('css', 'css'),
|
||||
+ ('arrow', 'img/arrow.svg'),
|
||||
+ ('search.js', 'js/search.js'),
|
||||
+ ('404', '404.html'),
|
||||
+ ('index_all', 'index.html'),
|
||||
+ ('bot_index', 'botindex.html'),
|
||||
+ ('index_types', 'types/index.html'),
|
||||
+ ('index_methods', 'methods/index.html'),
|
||||
+ ('index_constructors', 'constructors/index.html')
|
||||
+ )}
|
||||
+ paths['default_css'] = 'light' # docs.<name>.css, local path
|
||||
type_to_constructors = defaultdict(list)
|
||||
type_to_functions = defaultdict(list)
|
||||
for tlobject in tlobjects:
|
||||
@@ -266,24 +240,20 @@
|
||||
methods = {m.name: m for m in methods}
|
||||
|
||||
# Since the output directory is needed everywhere partially apply it now
|
||||
- create_path_for = functools.partial(_get_create_path_for, output_dir)
|
||||
- path_for_type = functools.partial(_get_path_for_type, output_dir)
|
||||
+ create_path_for = functools.partial(_get_create_path_for, root)
|
||||
+ path_for_type = lambda t: root / _get_path_for_type(t)
|
||||
bot_docs_paths = []
|
||||
|
||||
for tlobject in tlobjects:
|
||||
filename = create_path_for(tlobject)
|
||||
- paths = {k: _get_relative_path(v, filename)
|
||||
- for k, v in original_paths.items()}
|
||||
-
|
||||
- with DocsWriter(filename, type_to_path=path_for_type) as docs:
|
||||
+ with DocsWriter(root, filename, path_for_type) as docs:
|
||||
docs.write_head(title=tlobject.class_name,
|
||||
- relative_css_path=paths['css'],
|
||||
- default_css=original_paths['default_css'])
|
||||
+ css_path=paths['css'],
|
||||
+ default_css=paths['default_css'])
|
||||
|
||||
# Create the menu (path to the current TLObject)
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
- _build_menu(docs, filename, output_dir,
|
||||
- relative_main_index=paths['index_all'])
|
||||
+ _build_menu(docs)
|
||||
|
||||
# Create the page title
|
||||
docs.write_title(tlobject.class_name)
|
||||
@@ -333,9 +303,7 @@
|
||||
inner = tlobject.result
|
||||
|
||||
docs.begin_table(column_count=1)
|
||||
- docs.add_row(inner, link=path_for_type(
|
||||
- inner, relative_to=filename
|
||||
- ))
|
||||
+ docs.add_row(inner, link=path_for_type(inner))
|
||||
docs.end_table()
|
||||
|
||||
cs = type_to_constructors.get(inner, [])
|
||||
@@ -349,7 +317,6 @@
|
||||
docs.begin_table(column_count=2)
|
||||
for constructor in cs:
|
||||
link = create_path_for(constructor)
|
||||
- link = _get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(constructor.class_name, link=link)
|
||||
docs.end_table()
|
||||
|
||||
@@ -380,8 +347,8 @@
|
||||
docs.add_row('!' + friendly_type, align='center')
|
||||
else:
|
||||
docs.add_row(
|
||||
- friendly_type, align='center', link=
|
||||
- path_for_type(arg.type, relative_to=filename)
|
||||
+ friendly_type, align='center',
|
||||
+ link=path_for_type(arg.type)
|
||||
)
|
||||
|
||||
# Add a description for this argument
|
||||
@@ -441,18 +408,13 @@
|
||||
docs.add_script(relative_src=paths['search.js'])
|
||||
docs.end_body()
|
||||
|
||||
- temp = []
|
||||
- for item in bot_docs_paths:
|
||||
- temp.append(os.path.sep.join(item.split(os.path.sep)[2:]))
|
||||
- bot_docs_paths = temp
|
||||
-
|
||||
# Find all the available types (which are not the same as the constructors)
|
||||
# Each type has a list of constructors associated to it, hence is a map
|
||||
for t, cs in type_to_constructors.items():
|
||||
filename = path_for_type(t)
|
||||
- out_dir = os.path.dirname(filename)
|
||||
+ out_dir = filename.parent
|
||||
if out_dir:
|
||||
- os.makedirs(out_dir, exist_ok=True)
|
||||
+ out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Since we don't have access to the full TLObject, split the type
|
||||
if '.' in t:
|
||||
@@ -460,17 +422,13 @@
|
||||
else:
|
||||
namespace, name = None, t
|
||||
|
||||
- paths = {k: _get_relative_path(v, out_dir, folder=True)
|
||||
- for k, v in original_paths.items()}
|
||||
-
|
||||
- with DocsWriter(filename, type_to_path=path_for_type) as docs:
|
||||
+ with DocsWriter(root, filename, path_for_type) as docs:
|
||||
docs.write_head(title=snake_to_camel_case(name),
|
||||
- relative_css_path=paths['css'],
|
||||
- default_css=original_paths['default_css'])
|
||||
+ css_path=paths['css'],
|
||||
+ default_css=paths['default_css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
- _build_menu(docs, filename, output_dir,
|
||||
- relative_main_index=paths['index_all'])
|
||||
+ _build_menu(docs)
|
||||
|
||||
# Main file title
|
||||
docs.write_title(snake_to_camel_case(name))
|
||||
@@ -489,7 +447,6 @@
|
||||
for constructor in cs:
|
||||
# Constructor full name
|
||||
link = create_path_for(constructor)
|
||||
- link = _get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(constructor.class_name, link=link)
|
||||
docs.end_table()
|
||||
|
||||
@@ -509,7 +466,6 @@
|
||||
docs.begin_table(2)
|
||||
for func in functions:
|
||||
link = create_path_for(func)
|
||||
- link = _get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(func.class_name, link=link)
|
||||
docs.end_table()
|
||||
|
||||
@@ -534,7 +490,6 @@
|
||||
docs.begin_table(2)
|
||||
for ot in other_methods:
|
||||
link = create_path_for(ot)
|
||||
- link = _get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(ot.class_name, link=link)
|
||||
docs.end_table()
|
||||
|
||||
@@ -560,7 +515,6 @@
|
||||
docs.begin_table(2)
|
||||
for ot in other_types:
|
||||
link = create_path_for(ot)
|
||||
- link = _get_relative_path(link, relative_to=filename)
|
||||
docs.add_row(ot.class_name, link=link)
|
||||
docs.end_table()
|
||||
docs.end_body()
|
||||
@@ -570,11 +524,10 @@
|
||||
# information that we have available, simply a file listing all the others
|
||||
# accessible by clicking on their title
|
||||
for folder in ['types', 'methods', 'constructors']:
|
||||
- _generate_index(os.path.join(output_dir, folder), original_paths,
|
||||
- output_dir)
|
||||
+ _generate_index(root, root / folder, paths)
|
||||
|
||||
- _generate_index(os.path.join(output_dir, 'methods'), original_paths,
|
||||
- output_dir, True, bot_docs_paths)
|
||||
+ _generate_index(root, root / 'methods', paths, True,
|
||||
+ bot_docs_paths)
|
||||
|
||||
# Write the final core index, the main index for the rest of files
|
||||
types = set()
|
||||
@@ -596,9 +549,8 @@
|
||||
methods = sorted(methods, key=lambda m: m.name)
|
||||
cs = sorted(cs, key=lambda c: c.name)
|
||||
|
||||
- shutil.copy(os.path.join(input_res, '404.html'), original_paths['404'])
|
||||
- _copy_replace(os.path.join(input_res, 'core.html'),
|
||||
- original_paths['index_all'], {
|
||||
+ shutil.copy(str(input_res / '404.html'), str(paths['404']))
|
||||
+ _copy_replace(input_res / 'core.html', paths['index_all'], {
|
||||
'{type_count}': len(types),
|
||||
'{method_count}': len(methods),
|
||||
'{constructor_count}': len(tlobjects) - len(methods),
|
||||
@@ -624,17 +576,15 @@
|
||||
type_names = fmt(types, formatter=lambda x: x)
|
||||
|
||||
# Local URLs shouldn't rely on the output's root, so set empty root
|
||||
- create_path_for = functools.partial(_get_create_path_for, '', make=False)
|
||||
- path_for_type = functools.partial(_get_path_for_type, '')
|
||||
+ create_path_for = functools.partial(
|
||||
+ _get_create_path_for, Path(), make=False)
|
||||
+
|
||||
request_urls = fmt(methods, create_path_for)
|
||||
- type_urls = fmt(types, path_for_type)
|
||||
+ type_urls = fmt(types, _get_path_for_type)
|
||||
constructor_urls = fmt(cs, create_path_for)
|
||||
|
||||
- os.makedirs(os.path.abspath(os.path.join(
|
||||
- original_paths['search.js'], os.path.pardir
|
||||
- )), exist_ok=True)
|
||||
- _copy_replace(os.path.join(input_res, 'js', 'search.js'),
|
||||
- original_paths['search.js'], {
|
||||
+ paths['search.js'].parent.mkdir(parents=True, exist_ok=True)
|
||||
+ _copy_replace(input_res / 'js/search.js', paths['search.js'], {
|
||||
'{request_names}': request_names,
|
||||
'{type_names}': type_names,
|
||||
'{constructor_names}': constructor_names,
|
||||
@@ -649,11 +599,11 @@
|
||||
('img', ['arrow.svg'])]:
|
||||
- dirpath = os.path.join(out_dir, dirname)
|
||||
- os.makedirs(dirpath, exist_ok=True)
|
||||
+ dirpath = out_dir / dirname
|
||||
+ dirpath.mkdir(parents=True, exist_ok=True)
|
||||
for file in files:
|
||||
- shutil.copy(os.path.join(res_dir, dirname, file), dirpath)
|
||||
+ shutil.copy(str(res_dir / dirname / file), str(dirpath))
|
||||
|
||||
|
||||
def generate_docs(tlobjects, methods, layer, input_res, output_dir):
|
||||
- os.makedirs(output_dir, exist_ok=True)
|
||||
- _write_html_pages(tlobjects, methods, layer, input_res, output_dir)
|
||||
+ output_dir.mkdir(parents=True, exist_ok=True)
|
||||
+ _write_html_pages(output_dir, tlobjects, methods, layer, input_res)
|
||||
_copy_resources(input_res, output_dir)
|
||||
--- a/telethon_generator/generators/tlobject.py
|
||||
+++ b/telethon_generator/generators/tlobject.py
|
||||
@@ -48,9 +48,8 @@
|
||||
def _write_modules(
|
||||
out_dir, depth, kind, namespace_tlobjects, type_constructors):
|
||||
# namespace_tlobjects: {'namespace', [TLObject]}
|
||||
- os.makedirs(out_dir, exist_ok=True)
|
||||
+ out_dir.mkdir(parents=True, exist_ok=True)
|
||||
for ns, tlobjects in namespace_tlobjects.items():
|
||||
- file = os.path.join(out_dir, '{}.py'.format(ns or '__init__'))
|
||||
- with open(file, 'w', encoding='utf-8') as f,\
|
||||
- SourceBuilder(f) as builder:
|
||||
+ file = out_dir / '{}.py'.format(ns or '__init__')
|
||||
+ with file.open('w') as f, SourceBuilder(f) as builder:
|
||||
builder.writeln(AUTO_GEN_NOTICE)
|
||||
|
||||
builder.writeln('from {}.tl.tlobject import TLObject', '.' * depth)
|
||||
@@ -635,11 +634,10 @@
|
||||
|
||||
|
||||
def _write_patched(out_dir, namespace_tlobjects):
|
||||
- os.makedirs(out_dir, exist_ok=True)
|
||||
+ out_dir.mkdir(parents=True, exist_ok=True)
|
||||
for ns, tlobjects in namespace_tlobjects.items():
|
||||
- file = os.path.join(out_dir, '{}.py'.format(ns or '__init__'))
|
||||
- with open(file, 'w', encoding='utf-8') as f,\
|
||||
- SourceBuilder(f) as builder:
|
||||
+ file = out_dir / '{}.py'.format(ns or '__init__')
|
||||
+ with file.open('w') as f, SourceBuilder(f) as builder:
|
||||
builder.writeln(AUTO_GEN_NOTICE)
|
||||
|
||||
builder.writeln('import struct')
|
||||
@@ -715,26 +713,24 @@
|
||||
if tlobject.fullname in PATCHED_TYPES:
|
||||
namespace_patched[tlobject.namespace].append(tlobject)
|
||||
|
||||
- get_file = functools.partial(os.path.join, output_dir)
|
||||
- _write_modules(get_file('functions'), import_depth, 'TLRequest',
|
||||
+ _write_modules(output_dir / 'functions', import_depth, 'TLRequest',
|
||||
namespace_functions, type_constructors)
|
||||
- _write_modules(get_file('types'), import_depth, 'TLObject',
|
||||
+ _write_modules(output_dir / 'types', import_depth, 'TLObject',
|
||||
namespace_types, type_constructors)
|
||||
- _write_patched(get_file('patched'), namespace_patched)
|
||||
+ _write_patched(output_dir / 'patched', namespace_patched)
|
||||
|
||||
- filename = os.path.join(get_file('alltlobjects.py'))
|
||||
- with open(filename, 'w', encoding='utf-8') as file:
|
||||
+ filename = output_dir / 'alltlobjects.py'
|
||||
+ with filename.open('w') as file:
|
||||
with SourceBuilder(file) as builder:
|
||||
_write_all_tlobjects(tlobjects, layer, builder)
|
||||
|
||||
|
||||
def clean_tlobjects(output_dir):
|
||||
- get_file = functools.partial(os.path.join, output_dir)
|
||||
for d in ('functions', 'types'):
|
||||
- d = get_file(d)
|
||||
- if os.path.isdir(d):
|
||||
- shutil.rmtree(d)
|
||||
+ d = output_dir / d
|
||||
+ if d.is_dir():
|
||||
+ shutil.rmtree(str(d))
|
||||
|
||||
- tl = get_file('alltlobjects.py')
|
||||
- if os.path.isfile(tl):
|
||||
- os.remove(tl)
|
||||
+ tl = output_dir / 'alltlobjects.py'
|
||||
+ if tl.is_file():
|
||||
+ tl.unlink()
|
||||
--- a/telethon_generator/parsers/errors.py
|
||||
+++ b/telethon_generator/parsers/errors.py
|
||||
@@ -57,7 +57,7 @@
|
||||
Parses the input CSV file with columns (name, error codes, description)
|
||||
and yields `Error` instances as a result.
|
||||
"""
|
||||
- with open(csv_file, newline='') as f:
|
||||
+ with csv_file.open(newline='') as f:
|
||||
f = csv.reader(f)
|
||||
next(f, None) # header
|
||||
for line, (name, codes, description) in enumerate(f, start=2):
|
||||
--- a/telethon_generator/parsers/methods.py
|
||||
+++ b/telethon_generator/parsers/methods.py
|
||||
@@ -30,7 +30,7 @@
|
||||
Parses the input CSV file with columns (method, usability, errors)
|
||||
and yields `MethodInfo` instances as a result.
|
||||
"""
|
||||
- with open(csv_file, newline='') as f:
|
||||
+ with csv_file.open(newline='') as f:
|
||||
f = csv.reader(f)
|
||||
next(f, None) # header
|
||||
for line, (method, usability, errors) in enumerate(f, start=2):
|
||||
--- a/telethon_generator/parsers/tlobject/parser.py
|
||||
+++ b/telethon_generator/parsers/tlobject/parser.py
|
||||
@@ -86,7 +86,7 @@
|
||||
obj_all = []
|
||||
obj_by_name = {}
|
||||
obj_by_type = collections.defaultdict(list)
|
||||
- with open(file_path, 'r', encoding='utf-8') as file:
|
||||
+ with file_path.open() as file:
|
||||
is_function = False
|
||||
for line in file:
|
||||
comment_index = line.find('//')
|
|
@ -19,7 +19,7 @@ use these if possible.
|
|||
# Getting information about yourself
|
||||
me = await client.get_me()
|
||||
|
||||
# "me" is an User object. You can pretty-print
|
||||
# "me" is a user object. You can pretty-print
|
||||
# any Telegram object with the "stringify" method:
|
||||
print(me.stringify())
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ applications"? Now do the same with the library. Use what applies:
|
|||
|
||||
# (These examples assume you are inside an "async def")
|
||||
async with client:
|
||||
# Does it have an username? Use it!
|
||||
# Does it have a username? Use it!
|
||||
entity = await client.get_entity(username)
|
||||
|
||||
# Do you have a conversation open with them? Get dialogs.
|
||||
|
|
|
@ -2,81 +2,12 @@
|
|||
Telegram API in Other Languages
|
||||
===============================
|
||||
|
||||
Telethon was made for **Python**, and it has inspired other libraries such as
|
||||
`gramjs <https://github.com/gram-js/gramjs>`__ (JavaScript) and `grammers
|
||||
<https://github.com/Lonami/grammers>`__ (Rust). But there is a lot more beyond
|
||||
those, made independently by different developers.
|
||||
|
||||
Telethon was made for **Python**, and as far as I know, there is no
|
||||
*exact* port to other languages. However, there *are* other
|
||||
implementations made by awesome people (one needs to be awesome to
|
||||
understand the official Telegram documentation) on several languages
|
||||
(even more Python too), listed below:
|
||||
|
||||
C
|
||||
=
|
||||
|
||||
Possibly the most well-known unofficial open source implementation out
|
||||
there by `@vysheng <https://github.com/vysheng>`__,
|
||||
`tgl <https://github.com/vysheng/tgl>`__, and its console client
|
||||
`telegram-cli <https://github.com/vysheng/tg>`__. Latest development
|
||||
has been moved to `BitBucket <https://bitbucket.org/vysheng/tdcli>`__.
|
||||
|
||||
C++
|
||||
===
|
||||
|
||||
The newest (and official) library, written from scratch, is called
|
||||
`tdlib <https://github.com/tdlib/td>`__ and is what the Telegram X
|
||||
uses. You can find more information in the official documentation,
|
||||
published `here <https://core.telegram.org/tdlib/docs/>`__.
|
||||
|
||||
JavaScript
|
||||
==========
|
||||
|
||||
`Ali Gasymov <https://github.com/alik0211>`__ made the `@mtproto/core <https://github.com/alik0211/mtproto-core>`__ library for the browser and nodejs installable via `npm <https://www.npmjs.com/package/@mtproto/core>`__.
|
||||
|
||||
`painor <https://github.com/painor>`__ is the primary author of `gramjs <https://github.com/gram-js/gramjs>`__,
|
||||
a Telegram client implementation in JavaScript.
|
||||
|
||||
Kotlin
|
||||
======
|
||||
|
||||
`Kotlogram <https://github.com/badoualy/kotlogram>`__ is a Telegram
|
||||
implementation written in Kotlin (one of the
|
||||
`official <https://blog.jetbrains.com/kotlin/2017/05/kotlin-on-android-now-official/>`__
|
||||
languages for
|
||||
`Android <https://developer.android.com/kotlin/index.html>`__) by
|
||||
`@badoualy <https://github.com/badoualy>`__, currently as a beta–
|
||||
yet working.
|
||||
|
||||
Language-Agnostic
|
||||
=================
|
||||
|
||||
`Taas <https://www.t-a-a-s.ru/>`__ is a service that lets you use Telegram API with any HTTP client via API. Using tdlib under the hood, Taas is commercial service, but allows free access if you use under 5000 requests per month.
|
||||
|
||||
PHP
|
||||
===
|
||||
|
||||
A PHP implementation is also available thanks to
|
||||
`@danog <https://github.com/danog>`__ and his
|
||||
`MadelineProto <https://github.com/danog/MadelineProto>`__ project, with
|
||||
a very nice `online
|
||||
documentation <https://daniil.it/MadelineProto/API_docs/>`__ too.
|
||||
|
||||
Python
|
||||
======
|
||||
|
||||
A fairly new (as of the end of 2017) Telegram library written from the
|
||||
ground up in Python by
|
||||
`@delivrance <https://github.com/delivrance>`__ and his
|
||||
`Pyrogram <https://github.com/pyrogram/pyrogram>`__ library.
|
||||
There isn't really a reason to pick it over Telethon and it'd be kinda
|
||||
sad to see you go, but it would be nice to know what you miss from each
|
||||
other library in either one so both can improve.
|
||||
|
||||
Rust
|
||||
====
|
||||
|
||||
The `grammers <https://github.com/Lonami/grammers>`__ library is made by
|
||||
the `same author as Telethon's <https://github.com/Lonami>`__! If you are
|
||||
looking for a Telethon alternative written in Rust, this is a valid option!
|
||||
|
||||
Another older, work-in-progress implementation, on Rust is made by
|
||||
`@JuanPotato <https://github.com/JuanPotato>`__ under the fancy
|
||||
name of `Vail <https://github.com/JuanPotato/Vail>`__.
|
||||
If you're looking for something like Telethon but in a different programming
|
||||
language, head over to `Telegram API in Other Languages in the official wiki
|
||||
<https://github.com/LonamiWebs/Telethon/wiki/Telegram-API-in-Other-Languages>`__
|
||||
for a (mostly) up-to-date list.
|
||||
|
|
|
@ -13,6 +13,136 @@ it can take advantage of new goodies!
|
|||
|
||||
.. contents:: List of All Versions
|
||||
|
||||
Channel comments and Anonymous Admins (v1.17)
|
||||
=============================================
|
||||
|
||||
+------------------------+
|
||||
| Scheme layer used: 119 |
|
||||
+------------------------+
|
||||
|
||||
New minor version, new layer change! This time is a good one to remind every
|
||||
consumer of Python libraries that **you should always specify fixed versions
|
||||
of your dependencies**! If you're using a ``requirements.txt`` file and you
|
||||
want to stick with the old version (or any version) for the time being, you
|
||||
can `use the following syntax <https://pip.pypa.io/en/stable/user_guide/>`__:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
telethon~=1.16.0
|
||||
|
||||
This will install any version compatible with the written version (so, any in
|
||||
the ``1.16`` series). Patch releases will never break your code (and if they
|
||||
do, it's a bug). You can also use that syntax in ``pip install``. Your code
|
||||
can't know what new versions will look like, so saying it will work with all
|
||||
versions is a lie and will cause issues.
|
||||
|
||||
The reason to bring this up is that Telegram has changed things again, and
|
||||
with the introduction of anonymous administrators and channel comments, the
|
||||
sender of a message may not be a :tl:`User`! To accomodate for this, the field
|
||||
is now a :tl:`Peer` and not `int`. As a reminder, it's always a good idea to
|
||||
use Telethon's friendly methods and custom properties, which have a higher
|
||||
stability guarantee than accessing raw API fields.
|
||||
|
||||
Even if you don't update, your code will still need to account for the fact
|
||||
that the sender of a message might be one of the accounts Telegram introduced
|
||||
to preserve backwards compatibility, because this is a server-side change, so
|
||||
it's better to update and not lag behind. As it's mostly just a single person
|
||||
driving the project on their free time, bug-fixes are not backported.
|
||||
|
||||
This version also updates the format of SQLite sessions (the default), so
|
||||
after upgrading and using an old session, the session will be updated, which
|
||||
means trying to use it back in older versions of the library won't work.
|
||||
|
||||
For backwards-compatibility sake, the library has introduced the properties
|
||||
`Message.reply_to_msg_id <telethon.tl.custom.message.Message.reply_to_msg_id>`
|
||||
and `Message.to_id <telethon.tl.custom.message.Message.to_id>` that behave
|
||||
like they did before (Telegram has renamed and changed how these fields work).
|
||||
|
||||
|
||||
Breaking Changes
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* ``Message.from_id`` is now a :tl:`Peer`, not `int`! If you want the marked
|
||||
sender ID (much like old behaviour), replace all uses of ``.from_id`` with
|
||||
``.sender_id``. This will mostly work, but of course in old and new versions
|
||||
you have to account for the fact that this sender may no longer be a user.
|
||||
* You can no longer assign to `Message.reply_to_msg_id
|
||||
<telethon.tl.custom.message.Message.reply_to_msg_id>` and `Message.to_id
|
||||
<telethon.tl.custom.message.Message.to_id>` because these are now properties
|
||||
that offer a "view" to the real value from a different field.
|
||||
* Answering inline queries with a ``photo`` or ``document`` will now send the
|
||||
photo or document used in the resulting message by default. Not sending the
|
||||
media was technically a bug, but some people may be relying on this old
|
||||
behaviour. You can use the old behaviour with ``include_media=False``.
|
||||
|
||||
Additions
|
||||
~~~~~~~~~
|
||||
|
||||
* New ``raise_last_call_error`` parameter in the client constructor to raise
|
||||
the same error produced by the last failing call, rather than a generic
|
||||
`ValueError`.
|
||||
* New ``formatting_entities`` parameter in `client.send_message()
|
||||
<telethon.client.messages.MessageMethods.send_message>`, and
|
||||
`client.send_file() <telethon.client.uploads.UploadMethods.send_file>`
|
||||
to bypass the parse mode and manually specify the formatting entities.
|
||||
* New `client.get_permissions() <telethon.client.chats.ChatMethods.get_permissions>`
|
||||
method to query a participant's permissions in a group or channel. This
|
||||
request is slightly expensive in small group chats because it has to fetch
|
||||
the entire chat to check just a user, so use of a cache is advised.
|
||||
* `Message.click() <telethon.tl.custom.message.Message.click>` now works on
|
||||
normal polls!
|
||||
* New ``local_addr`` parameter in the client constructor to use a specific
|
||||
local network address when connecting to Telegram.
|
||||
* `client.inline_query() <telethon.client.bots.BotMethods.inline_query>` now
|
||||
lets you specify the chat where the query is being made from, which some
|
||||
bots need to provide certain functionality.
|
||||
* You can now get comments in a channel post with the ``reply_to`` parameter in
|
||||
`client.iter_messages() <telethon.client.messages.MessageMethods.iter_messages>`.
|
||||
Comments are messages that "reply to" a specific channel message, hence the
|
||||
name (which is consistent with how Telegram's API calls it).
|
||||
|
||||
Enhancements
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Updated documentation and list of known errors.
|
||||
* If ``hachoir`` is available, the file metadata can now be extracted from
|
||||
streams and in-memory bytes.
|
||||
* The default parameters used to initialize a connection now match the format
|
||||
of those used by Telegram Desktop.
|
||||
* Specifying 0 retries will no longer cause the library to attempt to reconnect.
|
||||
* The library should now be able to reliably download very large files.
|
||||
* Global search should work more reliably now.
|
||||
* Old usernames are evicted from cache, so getting entities by cached username
|
||||
should now be more reliable.
|
||||
* Slightly less noisy logs.
|
||||
* Stability regarding transport-level errors (transport flood, authorization
|
||||
key not found) should be improved. In particular, you should no longer be
|
||||
getting unnecessarily logged out.
|
||||
* Reconnection should no longer occur if the client gets logged out (for
|
||||
example, another client revokes the session).
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
||||
* In some cases, there were issues when using `events.Album
|
||||
<telethon.events.album.Album>` together with `events.Raw
|
||||
<telethon.events.raw.Raw>`.
|
||||
* For some channels, one of their channel photos would not show up in
|
||||
`client.iter_profile_photos() <telethon.client.chats.ChatMethods.iter_profile_photos>`.
|
||||
* In some cases, a request that failed to be sent would be forgotten, causing
|
||||
the original caller to be "locked" forever for a response that would never
|
||||
arrive. Failing requests should now consistently be automatically re-sent.
|
||||
* The library should more reliably handle certain updates with "empty" data.
|
||||
* Sending documents in inline queries should now work fine.
|
||||
* Manually using `client.sign_up <telethon.client.auth.AuthMethods.sign_up>`
|
||||
should now work correctly, instead of claiming "code invalid".
|
||||
|
||||
Special mention to some of the other changes in the 1.16.x series:
|
||||
|
||||
* The ``thumb`` for ``download_media`` now supports both `str` and :tl:`VideoSize`.
|
||||
* Thumbnails are sorted, so ``-1`` is always the largest.
|
||||
|
||||
|
||||
Bug Fixes (v1.16.1)
|
||||
===================
|
||||
|
||||
|
@ -3676,7 +3806,7 @@ things with the ``InteractiveTelegramClient``:
|
|||
- **Download** any message's media (photos, documents or even contacts!).
|
||||
- **Receive message updates** as you talk (i.e., someone sent you a message).
|
||||
|
||||
It actually is an usable-enough client for your day by day. You could
|
||||
It actually is a usable-enough client for your day by day. You could
|
||||
even add ``libnotify`` and pop, you're done! A great cli-client with
|
||||
desktop notifications.
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ you may need when using Telethon. They are sorted by relevance and are not in
|
|||
alphabetical order.
|
||||
|
||||
You should use this page to learn about which methods are available, and
|
||||
if you need an usage example or further description of the arguments, be
|
||||
if you need a usage example or further description of the arguments, be
|
||||
sure to follow the links.
|
||||
|
||||
.. contents::
|
||||
|
|
14
setup.py
14
setup.py
|
@ -165,8 +165,14 @@ def main(argv):
|
|||
print('Packaging for PyPi aborted, importing the module failed.')
|
||||
return
|
||||
|
||||
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||
remove_dirs = ['__pycache__', 'build', 'dist', 'Telethon.egg-info']
|
||||
for root, _dirs, _files in os.walk(LIBRARY_DIR, topdown=False):
|
||||
# setuptools is including __pycache__ for some reason (#1605)
|
||||
if root.endswith('/__pycache__'):
|
||||
remove_dirs.append(root)
|
||||
for x in remove_dirs:
|
||||
shutil.rmtree(x, ignore_errors=True)
|
||||
|
||||
run('python3 setup.py sdist', shell=True)
|
||||
run('python3 setup.py bdist_wheel', shell=True)
|
||||
run('twine upload dist/*', shell=True)
|
||||
|
@ -218,11 +224,13 @@ def main(argv):
|
|||
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6'
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
],
|
||||
keywords='telegram api chat client library messaging mtproto',
|
||||
packages=find_packages(exclude=[
|
||||
'telethon_*', 'run_tests.py', 'try_telethon.py'
|
||||
'telethon_*', 'tests*'
|
||||
]),
|
||||
install_requires=['pyaes', 'rsa'],
|
||||
extras_require={
|
||||
|
|
|
@ -92,7 +92,7 @@ class AuthMethods:
|
|||
# Starting as a bot account
|
||||
await client.start(bot_token=bot_token)
|
||||
|
||||
# Starting as an user account
|
||||
# Starting as a user account
|
||||
await client.start(phone)
|
||||
# Please enter the code you received: 12345
|
||||
# Please enter your password: *******
|
||||
|
@ -427,6 +427,23 @@ class AuthMethods:
|
|||
if me:
|
||||
return me
|
||||
|
||||
# To prevent abuse, one has to try to sign in before signing up. This
|
||||
# is the current way in which Telegram validates the code to sign up.
|
||||
#
|
||||
# `sign_in` will set `_tos`, so if it's set we don't need to call it
|
||||
# because the user already tried to sign in.
|
||||
#
|
||||
# We're emulating pre-layer 104 behaviour so except the right error:
|
||||
if not self._tos:
|
||||
try:
|
||||
return await self.sign_in(
|
||||
phone=phone,
|
||||
code=code,
|
||||
phone_code_hash=phone_code_hash,
|
||||
)
|
||||
except errors.PhoneNumberUnoccupiedError:
|
||||
pass # code is correct and was used, now need to sign in
|
||||
|
||||
if self._tos and self._tos.text:
|
||||
if self.parse_mode:
|
||||
t = self.parse_mode.unparse(self._tos.text, self._tos.entities)
|
||||
|
|
|
@ -1167,6 +1167,11 @@ class ChatMethods:
|
|||
"""
|
||||
Fetches the permissions of a user in a specific chat or channel.
|
||||
|
||||
.. note::
|
||||
|
||||
This request has to fetch the entire chat for small group chats,
|
||||
which can get somewhat expensive, so use of a cache is advised.
|
||||
|
||||
Arguments
|
||||
entity (`entity`):
|
||||
The channel or chat the user is participant of.
|
||||
|
|
|
@ -193,7 +193,13 @@ class MessageParseMethods:
|
|||
mapping = sched_to_message
|
||||
opposite = id_to_message # scheduled may be treated as normal, though
|
||||
|
||||
random_id = request if isinstance(request, (int, list)) else request.random_id
|
||||
random_id = request if isinstance(request, (int, list)) else getattr(request, 'random_id', None)
|
||||
if random_id is None:
|
||||
# Can happen when pinning a message does not actually produce a service message.
|
||||
self._log[__name__].warning(
|
||||
'No random_id in %s to map to, returning None message for %s', request, result)
|
||||
return None
|
||||
|
||||
if not utils.is_list_like(random_id):
|
||||
msg = mapping.get(random_to_id.get(random_id))
|
||||
if not msg:
|
||||
|
|
|
@ -19,7 +19,7 @@ class _MessagesIter(RequestIter):
|
|||
"""
|
||||
async def _init(
|
||||
self, entity, offset_id, min_id, max_id,
|
||||
from_user, offset_date, add_offset, filter, search
|
||||
from_user, offset_date, add_offset, filter, search, reply_to
|
||||
):
|
||||
# Note that entity being `None` will perform a global search.
|
||||
if entity:
|
||||
|
@ -58,11 +58,6 @@ class _MessagesIter(RequestIter):
|
|||
|
||||
if from_user:
|
||||
from_user = await self.client.get_input_entity(from_user)
|
||||
ty = helpers._entity_type(from_user)
|
||||
if ty != helpers._EntityType.USER:
|
||||
from_user = None # Ignore from_user unless it's a user
|
||||
|
||||
if from_user:
|
||||
self.from_id = await self.client.get_peer_id(from_user)
|
||||
else:
|
||||
self.from_id = None
|
||||
|
@ -87,6 +82,18 @@ class _MessagesIter(RequestIter):
|
|||
offset_id=offset_id,
|
||||
limit=1
|
||||
)
|
||||
elif reply_to is not None:
|
||||
self.request = functions.messages.GetRepliesRequest(
|
||||
peer=self.entity,
|
||||
msg_id=reply_to,
|
||||
offset_id=offset_id,
|
||||
offset_date=offset_date,
|
||||
add_offset=add_offset,
|
||||
limit=1,
|
||||
max_id=0,
|
||||
min_id=0,
|
||||
hash=0
|
||||
)
|
||||
elif search is not None or filter or from_user:
|
||||
# Telegram completely ignores `from_id` in private chats
|
||||
ty = helpers._entity_type(self.entity)
|
||||
|
@ -121,7 +128,8 @@ class _MessagesIter(RequestIter):
|
|||
#
|
||||
# Even better, using `filter` and `from_id` seems to always
|
||||
# trigger `RPC_CALL_FAIL` which is "internal issues"...
|
||||
if filter and offset_date and not search and not offset_id:
|
||||
if not isinstance(filter, types.InputMessagesFilterEmpty) \
|
||||
and offset_date and not search and not offset_id:
|
||||
async for m in self.client.iter_messages(
|
||||
self.entity, 1, offset_date=offset_date):
|
||||
self.request.offset_id = m.id + 1
|
||||
|
@ -236,7 +244,7 @@ class _MessagesIter(RequestIter):
|
|||
# (only for the first request), it's safe to just clear it off.
|
||||
self.request.max_date = None
|
||||
else:
|
||||
# getHistory and searchGlobal call it offset_date
|
||||
# getHistory, searchGlobal and getReplies call it offset_date
|
||||
self.request.offset_date = last_message.date
|
||||
|
||||
if isinstance(self.request, functions.messages.SearchGlobalRequest):
|
||||
|
@ -278,7 +286,7 @@ class _IDsIter(RequestIter):
|
|||
else:
|
||||
r = await self.client(functions.messages.GetMessagesRequest(ids))
|
||||
if self._entity:
|
||||
from_id = utils.get_peer(self._entity)
|
||||
from_id = await self.client._get_peer(self._entity)
|
||||
|
||||
if isinstance(r, types.messages.MessagesNotModified):
|
||||
self.buffer.extend(None for _ in ids)
|
||||
|
@ -325,7 +333,8 @@ class MessageMethods:
|
|||
from_user: 'hints.EntityLike' = None,
|
||||
wait_time: float = None,
|
||||
ids: 'typing.Union[int, typing.Sequence[int]]' = None,
|
||||
reverse: bool = False
|
||||
reverse: bool = False,
|
||||
reply_to: int = None
|
||||
) -> 'typing.Union[_MessagesIter, _IDsIter]':
|
||||
"""
|
||||
Iterator over the messages for the given chat.
|
||||
|
@ -392,8 +401,7 @@ class MessageMethods:
|
|||
containing photos.
|
||||
|
||||
from_user (`entity`):
|
||||
Only messages from this user will be returned.
|
||||
This parameter will be ignored if it is not an user.
|
||||
Only messages from this entity will be returned.
|
||||
|
||||
wait_time (`int`):
|
||||
Wait time (in seconds) between different
|
||||
|
@ -433,6 +441,26 @@ class MessageMethods:
|
|||
|
||||
You cannot use this if both `entity` and `ids` are `None`.
|
||||
|
||||
reply_to (`int`, optional):
|
||||
If set to a message ID, the messages that reply to this ID
|
||||
will be returned. This feature is also known as comments in
|
||||
posts of broadcast channels, or viewing threads in groups.
|
||||
|
||||
This feature can only be used in broadcast channels and their
|
||||
linked megagroups. Using it in a chat or private conversation
|
||||
will result in ``telethon.errors.PeerIdInvalidError`` to occur.
|
||||
|
||||
When using this parameter, the ``filter`` and ``search``
|
||||
parameters have no effect, since Telegram's API doesn't
|
||||
support searching messages in replies.
|
||||
|
||||
.. note::
|
||||
|
||||
This feature is used to get replies to a message in the
|
||||
*discussion* group. If the same broadcast channel sends
|
||||
a message and replies to it itself, that reply will not
|
||||
be included in the results.
|
||||
|
||||
Yields
|
||||
Instances of `Message <telethon.tl.custom.message.Message>`.
|
||||
|
||||
|
@ -459,6 +487,10 @@ class MessageMethods:
|
|||
from telethon.tl.types import InputMessagesFilterPhotos
|
||||
async for message in client.iter_messages(chat, filter=InputMessagesFilterPhotos):
|
||||
print(message.photo)
|
||||
|
||||
# Getting comments from a post in a channel:
|
||||
async for message in client.iter_messages(channel, reply_to=123):
|
||||
print(message.chat.title, message.text)
|
||||
"""
|
||||
if ids is not None:
|
||||
if not utils.is_list_like(ids):
|
||||
|
@ -486,7 +518,8 @@ class MessageMethods:
|
|||
offset_date=offset_date,
|
||||
add_offset=add_offset,
|
||||
filter=filter,
|
||||
search=search
|
||||
search=search,
|
||||
reply_to=reply_to
|
||||
)
|
||||
|
||||
async def get_messages(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList':
|
||||
|
@ -766,7 +799,7 @@ class MessageMethods:
|
|||
if isinstance(result, types.UpdateShortSentMessage):
|
||||
message = types.Message(
|
||||
id=result.id,
|
||||
peer_id=utils.get_peer(entity),
|
||||
peer_id=await self._get_peer(entity),
|
||||
message=message,
|
||||
date=result.date,
|
||||
out=result.out,
|
||||
|
@ -1208,7 +1241,7 @@ class MessageMethods:
|
|||
notify: bool = False
|
||||
):
|
||||
"""
|
||||
Pins or unpins a message in a chat.
|
||||
Pins a message in a chat.
|
||||
|
||||
The default behaviour is to *not* notify members, unlike the
|
||||
official applications.
|
||||
|
@ -1221,7 +1254,7 @@ class MessageMethods:
|
|||
|
||||
message (`int` | `Message <telethon.tl.custom.message.Message>`):
|
||||
The message or the message ID to pin. If it's
|
||||
`None`, the message will be unpinned instead.
|
||||
`None`, all messages will be unpinned instead.
|
||||
|
||||
notify (`bool`, optional):
|
||||
Whether the pin should notify people or not.
|
||||
|
@ -1233,18 +1266,55 @@ class MessageMethods:
|
|||
message = await client.send_message(chat, 'Pinotifying is fun!')
|
||||
await client.pin_message(chat, message, notify=True)
|
||||
"""
|
||||
return await self._pin(entity, message, unpin=False, notify=notify)
|
||||
|
||||
async def unpin_message(
|
||||
self: 'TelegramClient',
|
||||
entity: 'hints.EntityLike',
|
||||
message: 'typing.Optional[hints.MessageIDLike]' = None,
|
||||
*,
|
||||
notify: bool = False
|
||||
):
|
||||
"""
|
||||
Unpins a message in a chat.
|
||||
|
||||
If no message ID is specified, all pinned messages will be unpinned.
|
||||
|
||||
See also `Message.unpin() <telethon.tl.custom.message.Message.unpin>`.
|
||||
|
||||
Arguments
|
||||
entity (`entity`):
|
||||
The chat where the message should be pinned.
|
||||
|
||||
message (`int` | `Message <telethon.tl.custom.message.Message>`):
|
||||
The message or the message ID to unpin. If it's
|
||||
`None`, all messages will be unpinned instead.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
# Unpin all messages from a chat
|
||||
await client.unpin_message(chat)
|
||||
"""
|
||||
return await self._pin(entity, message, unpin=True, notify=notify)
|
||||
|
||||
async def _pin(self, entity, message, *, unpin, notify=False):
|
||||
message = utils.get_message_id(message) or 0
|
||||
entity = await self.get_input_entity(entity)
|
||||
if message <= 0: # old behaviour accepted negative IDs to unpin
|
||||
await self(functions.messages.UnpinAllMessagesRequest(entity))
|
||||
return
|
||||
|
||||
request = functions.messages.UpdatePinnedMessageRequest(
|
||||
peer=entity,
|
||||
id=message,
|
||||
silent=not notify
|
||||
silent=not notify,
|
||||
unpin=unpin,
|
||||
)
|
||||
result = await self(request)
|
||||
|
||||
# Unpinning does not produce a service message, and technically
|
||||
# users can pass negative IDs which seem to behave as unpinning too.
|
||||
if message <= 0:
|
||||
# Unpinning does not produce a service message
|
||||
if unpin:
|
||||
return
|
||||
|
||||
# Pinning in User chats (just with yourself really) does not produce a service message
|
||||
|
|
|
@ -463,6 +463,10 @@ class UserMethods:
|
|||
.format(peer)
|
||||
)
|
||||
|
||||
async def _get_peer(self: 'TelegramClient', peer: 'hints.EntityLike'):
|
||||
i, cls = utils.resolve_id(await self.get_peer_id(peer))
|
||||
return cls(i)
|
||||
|
||||
async def get_peer_id(
|
||||
self: 'TelegramClient',
|
||||
peer: 'hints.EntityLike',
|
||||
|
|
|
@ -31,16 +31,15 @@ class ChatAction(EventBuilder):
|
|||
"""
|
||||
@classmethod
|
||||
def build(cls, update, others=None, self_id=None):
|
||||
if isinstance(update, types.UpdateChannelPinnedMessage) and update.id == 0:
|
||||
# Telegram does not always send
|
||||
# UpdateChannelPinnedMessage for new pins
|
||||
# but always for unpin, with update.id = 0
|
||||
if isinstance(update, types.UpdatePinnedChannelMessages):
|
||||
return cls.Event(types.PeerChannel(update.channel_id),
|
||||
unpin=True)
|
||||
pin_ids=update.messages,
|
||||
pin=update.pinned)
|
||||
|
||||
elif isinstance(update, types.UpdateChatPinnedMessage) and update.id == 0:
|
||||
return cls.Event(types.PeerChat(update.chat_id),
|
||||
unpin=True)
|
||||
elif isinstance(update, types.UpdatePinnedMessages):
|
||||
return cls.Event(update.peer,
|
||||
pin_ids=update.messages,
|
||||
pin=update.pinned)
|
||||
|
||||
elif isinstance(update, types.UpdateChatParticipantAdd):
|
||||
return cls.Event(types.PeerChat(update.chat_id),
|
||||
|
@ -108,12 +107,8 @@ class ChatAction(EventBuilder):
|
|||
return cls.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_photo=True)
|
||||
elif isinstance(action, types.MessageActionPinMessage) and msg.reply_to:
|
||||
# Seems to not be reliable on unpins, but when pinning
|
||||
# we prefer this because we know who caused it.
|
||||
return cls.Event(msg,
|
||||
users=msg.from_id,
|
||||
new_pin=msg.reply_to.reply_to_msg_id)
|
||||
# Handled by specific updates
|
||||
# elif isinstance(action, types.MessageActionPinMessage) and msg.reply_to:
|
||||
|
||||
class Event(EventCommon):
|
||||
"""
|
||||
|
@ -153,19 +148,22 @@ class ChatAction(EventBuilder):
|
|||
unpin (`bool`):
|
||||
`True` if the existing pin gets unpinned.
|
||||
"""
|
||||
def __init__(self, where, new_pin=None, new_photo=None,
|
||||
def __init__(self, where, new_photo=None,
|
||||
added_by=None, kicked_by=None, created=None,
|
||||
users=None, new_title=None, unpin=None):
|
||||
users=None, new_title=None, pin_ids=None, pin=None):
|
||||
if isinstance(where, types.MessageService):
|
||||
self.action_message = where
|
||||
where = where.peer_id
|
||||
else:
|
||||
self.action_message = None
|
||||
|
||||
super().__init__(chat_peer=where, msg_id=new_pin)
|
||||
# TODO needs some testing (can there be more than one id, and do they follow pin order?)
|
||||
# same in get_pinned_message
|
||||
super().__init__(chat_peer=where, msg_id=pin_ids[0] if pin_ids else None)
|
||||
|
||||
self.new_pin = isinstance(new_pin, int)
|
||||
self._pinned_message = new_pin
|
||||
self.new_pin = pin_ids is not None
|
||||
self._pin_ids = pin_ids
|
||||
self._pinned_messages = None
|
||||
|
||||
self.new_photo = new_photo is not None
|
||||
self.photo = \
|
||||
|
@ -202,7 +200,7 @@ class ChatAction(EventBuilder):
|
|||
self._users = None
|
||||
self._input_users = None
|
||||
self.new_title = new_title
|
||||
self.unpin = unpin
|
||||
self.unpin = not pin
|
||||
|
||||
def _set_client(self, client):
|
||||
super()._set_client(client)
|
||||
|
@ -256,16 +254,26 @@ class ChatAction(EventBuilder):
|
|||
If ``new_pin`` is `True`, this returns the `Message
|
||||
<telethon.tl.custom.message.Message>` object that was pinned.
|
||||
"""
|
||||
if self._pinned_message == 0:
|
||||
return None
|
||||
if self._pinned_messages is None:
|
||||
await self.get_pinned_messages()
|
||||
|
||||
if isinstance(self._pinned_message, int)\
|
||||
and await self.get_input_chat():
|
||||
self._pinned_message = await self._client.get_messages(
|
||||
self._input_chat, ids=self._pinned_message)
|
||||
if self._pinned_messages:
|
||||
return self._pinned_messages[0]
|
||||
|
||||
if isinstance(self._pinned_message, types.Message):
|
||||
return self._pinned_message
|
||||
async def get_pinned_messages(self):
|
||||
"""
|
||||
If ``new_pin`` is `True`, this returns a `list` of `Message
|
||||
<telethon.tl.custom.message.Message>` objects that were pinned.
|
||||
"""
|
||||
if not self._pin_ids:
|
||||
return self._pin_ids # either None or empty list
|
||||
|
||||
chat = await self.get_input_chat()
|
||||
if chat:
|
||||
self._pinned_messages = await self._client.get_messages(
|
||||
self._input_chat, ids=self._pin_ids)
|
||||
|
||||
return self._pinned_messages
|
||||
|
||||
@property
|
||||
def added_by(self):
|
||||
|
|
|
@ -147,6 +147,9 @@ class InlineQuery(EventBuilder):
|
|||
"""
|
||||
Answers the inline query with the given results.
|
||||
|
||||
See the documentation for `builder` to know what kind of answers
|
||||
can be given.
|
||||
|
||||
Args:
|
||||
results (`list`, optional):
|
||||
A list of :tl:`InputBotInlineResult` to use.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""
|
||||
Several extensions Python is missing, such as a proper class to handle a TCP
|
||||
communication with support for cancelling the operation, and an utility class
|
||||
communication with support for cancelling the operation, and a utility class
|
||||
to read arbitrary binary data in a more comfortable way, with int/strings/etc.
|
||||
"""
|
||||
from .binaryreader import BinaryReader
|
||||
|
|
|
@ -95,7 +95,7 @@ class TcpMTProxy(ObfuscatedConnection):
|
|||
obfuscated_io = MTProxyIO
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def __init__(self, ip, port, dc_id, *, loggers, proxy=None):
|
||||
def __init__(self, ip, port, dc_id, *, loggers, proxy=None, local_addr=None):
|
||||
# connect to proxy's host and port instead of telegram's ones
|
||||
proxy_host, proxy_port = self.address_info(proxy)
|
||||
self._secret = bytes.fromhex(proxy[2])
|
||||
|
|
|
@ -20,12 +20,14 @@ class Button:
|
|||
instances instead making them yourself (i.e. don't do ``Button(...)``
|
||||
but instead use methods line `Button.inline(...) <inline>` etc.
|
||||
|
||||
You can use `inline`, `switch_inline` and `url`
|
||||
You can use `inline`, `switch_inline`, `url` and `auth`
|
||||
together to create inline buttons (under the message).
|
||||
|
||||
You can use `text`, `request_location` and `request_phone`
|
||||
You can use `text`, `request_location`, `request_phone` and `request_poll`
|
||||
together to create a reply markup (replaces the user keyboard).
|
||||
You can also configure the aspect of the reply with these.
|
||||
The latest message with a reply markup will be the one shown to the user
|
||||
(messages contain the buttons, not the chat itself).
|
||||
|
||||
You **cannot** mix the two type of buttons together,
|
||||
and it will error if you try to do so.
|
||||
|
@ -63,6 +65,13 @@ class Button:
|
|||
|
||||
Note that the given `data` must be less or equal to 64 bytes.
|
||||
If more than 64 bytes are passed as data, ``ValueError`` is raised.
|
||||
If you need to store more than 64 bytes, consider saving the real
|
||||
data in a database and a reference to that data inside the button.
|
||||
|
||||
When the user clicks this button, `events.CallbackQuery
|
||||
<telethon.events.callbackquery.CallbackQuery>` will trigger with the
|
||||
same data that the button contained, so that you can determine which
|
||||
button was pressed.
|
||||
"""
|
||||
if not data:
|
||||
data = text.encode('utf-8')
|
||||
|
@ -85,6 +94,10 @@ class Button:
|
|||
If ``same_peer is True`` the inline query will directly be
|
||||
set under the currently opened chat. Otherwise, the user will
|
||||
have to select a different dialog to make the query.
|
||||
|
||||
When the user clicks this button, after a chat is selected, their
|
||||
input field will be filled with the username of your bot followed
|
||||
by the query text, ready to make inline queries.
|
||||
"""
|
||||
return types.KeyboardButtonSwitchInline(text, query, same_peer)
|
||||
|
||||
|
@ -96,6 +109,11 @@ class Button:
|
|||
If no `url` is given, the `text` will be used as said URL instead.
|
||||
|
||||
You cannot detect that the user clicked this button directly.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to open the displayed URL unless
|
||||
the domain is trusted, and once confirmed the URL will open in their
|
||||
device.
|
||||
"""
|
||||
return types.KeyboardButtonUrl(text, url or text)
|
||||
|
||||
|
@ -133,6 +151,9 @@ class Button:
|
|||
fwd_text (`str`):
|
||||
The new text to show in the button if the message is
|
||||
forwarded. By default, the button text will be the same.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to login to the specified domain.
|
||||
"""
|
||||
return types.InputKeyboardButtonUrlAuth(
|
||||
text=text,
|
||||
|
@ -161,6 +182,12 @@ class Button:
|
|||
be "selective". The keyboard will be shown only to specific
|
||||
users. It will target users that are @mentioned in the text
|
||||
of the message or to the sender of the message you reply to.
|
||||
|
||||
When the user clicks this button, a text message with the same text
|
||||
as the button will be sent, and can be handled with `events.NewMessage
|
||||
<telethon.events.newmessage.NewMessage>`. You cannot distinguish
|
||||
between a button press and the user typing and sending exactly the
|
||||
same text on their own.
|
||||
"""
|
||||
return cls(types.KeyboardButton(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
@ -172,6 +199,10 @@ class Button:
|
|||
Creates a new keyboard button to request the user's location on click.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to share their location with the
|
||||
bot, and if confirmed a message with geo media will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestGeoLocation(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
@ -183,6 +214,10 @@ class Button:
|
|||
Creates a new keyboard button to request the user's phone on click.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a confirmation box will be shown
|
||||
to the user asking whether they want to share their phone with the
|
||||
bot, and if confirmed a message with contact media will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestPhone(text),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
@ -202,6 +237,9 @@ class Button:
|
|||
the vote, and the pol might be multiple choice.
|
||||
|
||||
``resize``, ``single_use`` and ``selective`` are documented in `text`.
|
||||
|
||||
When the user clicks this button, a screen letting the user create a
|
||||
poll will be shown, and if they do create one, the poll will be sent.
|
||||
"""
|
||||
return cls(types.KeyboardButtonRequestPoll(text, quiz=force_quiz),
|
||||
resize=resize, single_use=single_use, selective=selective)
|
||||
|
|
|
@ -3,6 +3,20 @@ import hashlib
|
|||
from .. import functions, types
|
||||
from ... import utils
|
||||
|
||||
_TYPE_TO_MIMES = {
|
||||
'gif': ['image/gif'], # 'video/mp4' too, but that's used for video
|
||||
'article': ['text/html'],
|
||||
'audio': ['audio/mpeg'],
|
||||
'contact': [],
|
||||
'file': ['application/pdf', 'application/zip'], # actually any
|
||||
'geo': [],
|
||||
'photo': ['image/jpeg'],
|
||||
'sticker': ['image/webp', 'application/x-tgsticker'],
|
||||
'venue': [],
|
||||
'video': ['video/mp4'], # tdlib includes text/html for some reason
|
||||
'voice': ['audio/ogg'],
|
||||
}
|
||||
|
||||
|
||||
class InlineBuilder:
|
||||
"""
|
||||
|
@ -83,6 +97,31 @@ class InlineBuilder:
|
|||
content (:tl:`InputWebDocument`, optional):
|
||||
The content to be shown for this result.
|
||||
For now it has to be a :tl:`InputWebDocument` if present.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Option with title and description sending a message.
|
||||
builder.article(
|
||||
title='First option',
|
||||
description='This is the first option',
|
||||
text='Text sent after clicking this option',
|
||||
),
|
||||
# Option with title URL to be opened when clicked.
|
||||
builder.article(
|
||||
title='Second option',
|
||||
url='https://example.com',
|
||||
text='Text sent if the user clicks the option and not the URL',
|
||||
),
|
||||
# Sending a message with buttons.
|
||||
# You can use a list or a list of lists to include more buttons.
|
||||
builder.article(
|
||||
title='Third option',
|
||||
text='Text sent with buttons below',
|
||||
buttons=Button.url('https://example.com'),
|
||||
),
|
||||
]
|
||||
"""
|
||||
# TODO Does 'article' work always?
|
||||
# article, photo, gif, mpeg4_gif, video, audio,
|
||||
|
@ -110,7 +149,7 @@ class InlineBuilder:
|
|||
|
||||
# noinspection PyIncorrectDocstring
|
||||
async def photo(
|
||||
self, file, *, id=None,
|
||||
self, file, *, id=None, include_media=True,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
|
@ -118,9 +157,36 @@ class InlineBuilder:
|
|||
Creates a new inline result of photo type.
|
||||
|
||||
Args:
|
||||
include_media (`bool`, optional):
|
||||
Whether the photo file used to display the result should be
|
||||
included in the message itself or not. By default, the photo
|
||||
is included, and the text parameter alters the caption.
|
||||
|
||||
file (`obj`, optional):
|
||||
Same as ``file`` for `client.send_file()
|
||||
<telethon.client.uploads.UploadMethods.send_file>`.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Sending just the photo when the user selects it.
|
||||
builder.photo('/path/to/photo.jpg'),
|
||||
|
||||
# Including a caption with some in-memory photo.
|
||||
photo_bytesio = ...
|
||||
builder.photo(
|
||||
photo_bytesio,
|
||||
text='This will be the caption of the sent photo',
|
||||
),
|
||||
|
||||
# Sending just the message without including the photo.
|
||||
builder.photo(
|
||||
photo,
|
||||
text='This will be a normal text message',
|
||||
include_media=False,
|
||||
),
|
||||
]
|
||||
"""
|
||||
try:
|
||||
fh = utils.get_input_photo(file)
|
||||
|
@ -144,6 +210,7 @@ class InlineBuilder:
|
|||
text=text or '',
|
||||
parse_mode=parse_mode,
|
||||
link_preview=link_preview,
|
||||
media=include_media,
|
||||
geo=geo,
|
||||
period=period,
|
||||
contact=contact,
|
||||
|
@ -162,7 +229,8 @@ class InlineBuilder:
|
|||
mime_type=None, attributes=None, force_document=False,
|
||||
voice_note=False, video_note=False, use_cache=True, id=None,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
geo=None, period=60, contact=None, game=False, buttons=None,
|
||||
include_media=True
|
||||
):
|
||||
"""
|
||||
Creates a new inline result of document type.
|
||||
|
@ -183,16 +251,50 @@ class InlineBuilder:
|
|||
Further explanation of what this result means.
|
||||
|
||||
type (`str`, optional):
|
||||
The type of the document. May be one of: photo, gif,
|
||||
mpeg4_gif, video, audio, voice, document, sticker.
|
||||
The type of the document. May be one of: article, audio,
|
||||
contact, file, geo, gif, photo, sticker, venue, video, voice.
|
||||
It will be automatically set if ``mime_type`` is specified,
|
||||
and default to ``'file'`` if no matching mime type is found.
|
||||
|
||||
See "Type of the result" in https://core.telegram.org/bots/api.
|
||||
include_media (`bool`, optional):
|
||||
Whether the document file used to display the result should be
|
||||
included in the message itself or not. By default, the document
|
||||
is included, and the text parameter alters the caption.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
results = [
|
||||
# Sending just the file when the user selects it.
|
||||
builder.document('/path/to/file.pdf'),
|
||||
|
||||
# Including a caption with some in-memory file.
|
||||
file_bytesio = ...
|
||||
builder.document(
|
||||
file_bytesio,
|
||||
text='This will be the caption of the sent file',
|
||||
),
|
||||
|
||||
# Sending just the message without including the file.
|
||||
builder.document(
|
||||
photo,
|
||||
text='This will be a normal text message',
|
||||
include_media=False,
|
||||
),
|
||||
]
|
||||
"""
|
||||
if type is None:
|
||||
if voice_note:
|
||||
type = 'voice'
|
||||
else:
|
||||
type = 'document'
|
||||
elif mime_type:
|
||||
for ty, mimes in _TYPE_TO_MIMES.items():
|
||||
for mime in mimes:
|
||||
if mime_type == mime:
|
||||
type = ty
|
||||
break
|
||||
|
||||
if type is None:
|
||||
type = 'file'
|
||||
|
||||
try:
|
||||
fh = utils.get_input_document(file)
|
||||
|
@ -225,6 +327,7 @@ class InlineBuilder:
|
|||
text=text or '',
|
||||
parse_mode=parse_mode,
|
||||
link_preview=link_preview,
|
||||
media=include_media,
|
||||
geo=geo,
|
||||
period=period,
|
||||
contact=contact,
|
||||
|
@ -270,7 +373,7 @@ class InlineBuilder:
|
|||
|
||||
async def _message(
|
||||
self, *,
|
||||
text=None, parse_mode=(), link_preview=True,
|
||||
text=None, parse_mode=(), link_preview=True, media=False,
|
||||
geo=None, period=60, contact=None, game=False, buttons=None
|
||||
):
|
||||
# Empty strings are valid but false-y; if they're empty use dummy '\0'
|
||||
|
@ -284,18 +387,25 @@ class InlineBuilder:
|
|||
|
||||
markup = self._client.build_reply_markup(buttons, inline_only=True)
|
||||
if text is not None:
|
||||
if not text: # Automatic media on empty string, like stickers
|
||||
return types.InputBotInlineMessageMediaAuto('')
|
||||
|
||||
text, msg_entities = await self._client._parse_message_text(
|
||||
text, parse_mode
|
||||
)
|
||||
return types.InputBotInlineMessageText(
|
||||
message=text,
|
||||
no_webpage=not link_preview,
|
||||
entities=msg_entities,
|
||||
reply_markup=markup
|
||||
)
|
||||
if media:
|
||||
# "MediaAuto" means it will use whatever media the inline
|
||||
# result itself has (stickers, photos, or documents), while
|
||||
# respecting the user's text (caption) and formatting.
|
||||
return types.InputBotInlineMessageMediaAuto(
|
||||
message=text,
|
||||
entities=msg_entities,
|
||||
reply_markup=markup
|
||||
)
|
||||
else:
|
||||
return types.InputBotInlineMessageText(
|
||||
message=text,
|
||||
no_webpage=not link_preview,
|
||||
entities=msg_entities,
|
||||
reply_markup=markup
|
||||
)
|
||||
elif isinstance(geo, (types.InputGeoPoint, types.GeoPoint)):
|
||||
return types.InputBotInlineMessageMediaGeo(
|
||||
geo_point=utils.get_input_geo(geo),
|
||||
|
|
|
@ -13,6 +13,10 @@ class InlineResult:
|
|||
result (:tl:`BotInlineResult`):
|
||||
The original :tl:`BotInlineResult` object.
|
||||
"""
|
||||
# tdlib types are the following (InlineQueriesManager::answer_inline_query @ 1a4a834):
|
||||
# gif, article, audio, contact, file, geo, photo, sticker, venue, video, voice
|
||||
#
|
||||
# However, those documented in https://core.telegram.org/bots/api#inline-mode are different.
|
||||
ARTICLE = 'article'
|
||||
PHOTO = 'photo'
|
||||
GIF = 'gif'
|
||||
|
|
|
@ -21,10 +21,6 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
have access to all their sender and chat properties and methods.
|
||||
|
||||
Members:
|
||||
id (`int`):
|
||||
The ID of this message. This field is *always* present.
|
||||
Any other member is optional and may be `None`.
|
||||
|
||||
out (`bool`):
|
||||
Whether the message is outgoing (i.e. you sent it from
|
||||
another session) or incoming (i.e. someone else sent it).
|
||||
|
@ -44,7 +40,6 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
Whether you have read the media in this message
|
||||
or not, e.g. listened to the voice note media.
|
||||
|
||||
|
||||
silent (`bool`):
|
||||
Whether the message should notify people with sound or not.
|
||||
Previously used in channels, but since 9 August 2019, it can
|
||||
|
@ -62,11 +57,38 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
legacy (`bool`):
|
||||
Whether this is a legacy message or not.
|
||||
|
||||
edit_hide (`bool`):
|
||||
Whether the edited mark of this message is edited
|
||||
should be hidden (e.g. in GUI clients) or shown.
|
||||
|
||||
pinned (`bool`):
|
||||
Whether this message is currently pinned or not.
|
||||
|
||||
id (`int`):
|
||||
The ID of this message. This field is *always* present.
|
||||
Any other member is optional and may be `None`.
|
||||
|
||||
from_id (:tl:`Peer`):
|
||||
The peer who sent this message, which is either
|
||||
:tl:`PeerUser`, :tl:`PeerChat` or :tl:`PeerChannel`.
|
||||
This value will be `None` for anonymous messages.
|
||||
|
||||
peer_id (:tl:`Peer`):
|
||||
The peer to which this message was sent, which is either
|
||||
:tl:`PeerUser`, :tl:`PeerChat` or :tl:`PeerChannel`. This
|
||||
will always be present except for empty messages.
|
||||
|
||||
fwd_from (:tl:`MessageFwdHeader`):
|
||||
The original forward header if this message is a forward.
|
||||
You should probably use the `forward` property instead.
|
||||
|
||||
via_bot_id (`int`):
|
||||
The ID of the bot used to send this message
|
||||
through its inline mode (e.g. "via @like").
|
||||
|
||||
reply_to (:tl:`MessageReplyHeader`):
|
||||
The original reply header if this message is replying to another.
|
||||
|
||||
date (`datetime`):
|
||||
The UTC+0 `datetime` object indicating when this message
|
||||
was sent. This will always be present except for empty
|
||||
|
@ -77,26 +99,6 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
<telethon.tl.custom.message.Message>` instances,
|
||||
which will be `None` for other types of messages.
|
||||
|
||||
action (:tl:`MessageAction`):
|
||||
The message action object of the message for :tl:`MessageService`
|
||||
instances, which will be `None` for other types of messages.
|
||||
|
||||
from_id (:tl:`Peer`):
|
||||
The peer who sent this message, which is either
|
||||
:tl:`PeerUser`, :tl:`PeerChat` or :tl:`PeerChannel`.
|
||||
This value will be `None` for anonymous messages.
|
||||
|
||||
reply_to (:tl:`MessageReplyHeader`):
|
||||
The original reply header if this message is replying to another.
|
||||
|
||||
fwd_from (:tl:`MessageFwdHeader`):
|
||||
The original forward header if this message is a forward.
|
||||
You should probably use the `forward` property instead.
|
||||
|
||||
via_bot_id (`int`):
|
||||
The ID of the bot used to send this message
|
||||
through its inline mode (e.g. "via @like").
|
||||
|
||||
media (:tl:`MessageMedia`):
|
||||
The media sent with this message if any (such as
|
||||
photos, videos, documents, gifs, stickers, etc.).
|
||||
|
@ -120,13 +122,15 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
The number of views this message from a broadcast
|
||||
channel has. This is also present in forwards.
|
||||
|
||||
forwards (`int`):
|
||||
The number of times this message has been forwarded.
|
||||
|
||||
replies (`int`):
|
||||
The number of times another message has replied to this message.
|
||||
|
||||
edit_date (`datetime`):
|
||||
The date when this message was last edited.
|
||||
|
||||
edit_hide (`bool`):
|
||||
Whether the edited mark of this message is edited
|
||||
should be hidden (e.g. in GUI clients) or shown.
|
||||
|
||||
post_author (`str`):
|
||||
The display name of the message sender to
|
||||
show in messages sent to broadcast channels.
|
||||
|
@ -139,6 +143,10 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
restriction_reason (List[:tl:`RestrictionReason`])
|
||||
An optional list of reasons why this message was restricted.
|
||||
If the list is `None`, this message has not been restricted.
|
||||
|
||||
action (:tl:`MessageAction`):
|
||||
The message action object of the message for :tl:`MessageService`
|
||||
instances, which will be `None` for other types of messages.
|
||||
"""
|
||||
|
||||
# region Initialization
|
||||
|
@ -161,40 +169,39 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
fwd_from=None, via_bot_id=None, media=None, reply_markup=None,
|
||||
entities=None, views=None, edit_date=None, post_author=None,
|
||||
grouped_id=None, from_scheduled=None, legacy=None,
|
||||
edit_hide=None, restriction_reason=None, forwards=None,
|
||||
replies=None,
|
||||
edit_hide=None, pinned=None, restriction_reason=None,
|
||||
forwards=None, replies=None,
|
||||
|
||||
# For MessageAction (mandatory)
|
||||
action=None):
|
||||
# Common properties to all messages
|
||||
self.id = id
|
||||
self.peer_id = peer_id
|
||||
self.date = date
|
||||
# Common properties to messages, then to service (in the order they're defined in the `.tl`)
|
||||
self.out = out
|
||||
self.mentioned = mentioned
|
||||
self.media_unread = media_unread
|
||||
self.silent = silent
|
||||
self.post = post
|
||||
self.from_id = from_id
|
||||
self.reply_to = reply_to
|
||||
self.message = message
|
||||
self.fwd_from = fwd_from
|
||||
self.via_bot_id = via_bot_id
|
||||
self.media = None if isinstance(
|
||||
media, types.MessageMediaEmpty) else media
|
||||
|
||||
self.reply_markup = reply_markup
|
||||
self.entities = entities
|
||||
self.views = views
|
||||
self.edit_date = edit_date
|
||||
self.post_author = post_author
|
||||
self.grouped_id = grouped_id
|
||||
self.from_scheduled = from_scheduled
|
||||
self.legacy = legacy
|
||||
self.edit_hide = edit_hide
|
||||
self.restriction_reason = restriction_reason
|
||||
self.id = id
|
||||
self.from_id = from_id
|
||||
self.peer_id = peer_id
|
||||
self.fwd_from = fwd_from
|
||||
self.via_bot_id = via_bot_id
|
||||
self.reply_to = reply_to
|
||||
self.date = date
|
||||
self.message = message
|
||||
self.media = None if isinstance(media, types.MessageMediaEmpty) else media
|
||||
self.reply_markup = reply_markup
|
||||
self.entities = entities
|
||||
self.views = views
|
||||
self.forwards = forwards
|
||||
self.replies = replies
|
||||
self.edit_date = edit_date
|
||||
self.pinned = pinned
|
||||
self.post_author = post_author
|
||||
self.grouped_id = grouped_id
|
||||
self.restriction_reason = restriction_reason
|
||||
self.action = action
|
||||
|
||||
# Convenient storage for custom functions
|
||||
|
@ -1004,6 +1011,16 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
|
|||
return await self._client.pin_message(
|
||||
await self.get_input_chat(), self.id, notify=notify)
|
||||
|
||||
async def unpin(self):
|
||||
"""
|
||||
Unpins the message. Shorthand for
|
||||
`telethon.client.messages.MessageMethods.unpin_message`
|
||||
with both ``entity`` and ``message`` already set.
|
||||
"""
|
||||
if self._client:
|
||||
return await self._client.unpin_message(
|
||||
await self.get_input_chat(), self.id)
|
||||
|
||||
# endregion Public Methods
|
||||
|
||||
# region Private Methods
|
||||
|
|
|
@ -20,7 +20,7 @@ from mimetypes import guess_extension
|
|||
from types import GeneratorType
|
||||
|
||||
from .extensions import markdown, html
|
||||
from .helpers import add_surrogate, del_surrogate
|
||||
from .helpers import add_surrogate, del_surrogate, strip_text
|
||||
from .tl import types
|
||||
|
||||
try:
|
||||
|
@ -1072,7 +1072,7 @@ def _rle_encode(string):
|
|||
|
||||
def _decode_telegram_base64(string):
|
||||
"""
|
||||
Decodes an url-safe base64-encoded string into its bytes
|
||||
Decodes a url-safe base64-encoded string into its bytes
|
||||
by first adding the stripped necessary padding characters.
|
||||
|
||||
This is the way Telegram shares binary data as strings,
|
||||
|
@ -1384,6 +1384,101 @@ def decode_waveform(waveform):
|
|||
return bytes(result)
|
||||
|
||||
|
||||
def split_text(text, entities, *, limit=4096, max_entities=100, split_at=(r'\n', r'\s', '.')):
|
||||
"""
|
||||
Split a message text and entities into multiple messages, each with their
|
||||
own set of entities. This allows sending a very large message as multiple
|
||||
messages while respecting the formatting.
|
||||
|
||||
Arguments
|
||||
text (`str`):
|
||||
The message text.
|
||||
|
||||
entities (List[:tl:`MessageEntity`])
|
||||
The formatting entities.
|
||||
|
||||
limit (`int`):
|
||||
The maximum message length of each individual message.
|
||||
|
||||
max_entities (`int`):
|
||||
The maximum amount of entities that will be present in each
|
||||
individual message.
|
||||
|
||||
split_at (Tuplel[`str`]):
|
||||
The list of regular expressions that will determine where to split
|
||||
the text. By default, a newline is searched. If no newline is
|
||||
present, a space is searched. If no space is found, the split will
|
||||
be made at any character.
|
||||
|
||||
The last expression should always match a character, or else the
|
||||
text will stop being splitted and the resulting text may be larger
|
||||
than the limit.
|
||||
|
||||
Yields
|
||||
Pairs of ``(str, entities)`` with the split message.
|
||||
|
||||
Example
|
||||
.. code-block:: python
|
||||
|
||||
from telethon import utils
|
||||
from telethon.extensions import markdown
|
||||
|
||||
very_long_markdown_text = "..."
|
||||
text, entities = markdown.parse(very_long_markdown_text)
|
||||
|
||||
for text, entities in utils.split_text(text, entities):
|
||||
await client.send_message(chat, text, formatting_entities=entities)
|
||||
"""
|
||||
# TODO add test cases (multiple entities beyond cutoff, at cutoff, splitting at emoji)
|
||||
# TODO try to optimize this a bit more? (avoid new_ent, smarter update method)
|
||||
def update(ent, **updates):
|
||||
kwargs = ent.to_dict()
|
||||
del kwargs['_']
|
||||
kwargs.update(updates)
|
||||
return ent.__class__(**kwargs)
|
||||
|
||||
text = add_surrogate(text)
|
||||
split_at = tuple(map(re.compile, split_at))
|
||||
|
||||
while True:
|
||||
if len(entities) > max_entities:
|
||||
last_ent = entities[max_entities - 1]
|
||||
cur_limit = min(limit, last_ent.offset + last_ent.length)
|
||||
else:
|
||||
cur_limit = limit
|
||||
|
||||
if len(text) <= cur_limit:
|
||||
break
|
||||
|
||||
for split in split_at:
|
||||
for i in reversed(range(cur_limit)):
|
||||
m = split.match(text, pos=i)
|
||||
if m:
|
||||
cur_text, new_text = text[:m.end()], text[m.end():]
|
||||
cur_ent, new_ent = [], []
|
||||
for ent in entities:
|
||||
if ent.offset < m.end():
|
||||
if ent.offset + ent.length > m.end():
|
||||
cur_ent.append(update(ent, length=m.end() - ent.offset))
|
||||
new_ent.append(update(ent, offset=0, length=ent.offset + ent.length - m.end()))
|
||||
else:
|
||||
cur_ent.append(ent)
|
||||
else:
|
||||
new_ent.append(update(ent, offset=ent.offset - m.end()))
|
||||
|
||||
yield del_surrogate(cur_text), cur_ent
|
||||
text, entities = new_text, new_ent
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
# Can't find where to split, just return the remaining text and entities
|
||||
break
|
||||
|
||||
yield del_surrogate(text), entities
|
||||
|
||||
|
||||
class AsyncClassWrapper:
|
||||
def __init__(self, wrapped):
|
||||
self.wrapped = wrapped
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '1.16.4'
|
||||
__version__ = '1.17.5'
|
||||
|
|
|
@ -69,7 +69,7 @@ inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = In
|
|||
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
|
||||
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
|
||||
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
|
||||
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
|
||||
inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
|
||||
inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
|
||||
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
|
||||
|
@ -78,7 +78,7 @@ inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?Inp
|
|||
inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
|
||||
|
||||
inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
|
||||
inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
|
||||
inputGeoPoint#48222faf flags:# lat:double long:double accuracy_radius:flags.0?int = InputGeoPoint;
|
||||
|
||||
inputPhotoEmpty#1cd7bf0d = InputPhoto;
|
||||
inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto;
|
||||
|
@ -141,7 +141,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
|
|||
chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
|
||||
|
||||
messageEmpty#83e5de54 id:int = Message;
|
||||
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
|
||||
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
|
||||
messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message;
|
||||
|
||||
messageMediaEmpty#3ded6320 = MessageMedia;
|
||||
|
@ -154,7 +154,7 @@ messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
|
|||
messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
|
||||
messageMediaGame#fdb19008 game:Game = MessageMedia;
|
||||
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
|
||||
messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
|
||||
messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia;
|
||||
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
|
||||
messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
|
||||
|
||||
|
@ -181,6 +181,7 @@ messageActionBotAllowed#abe9affe domain:string = MessageAction;
|
|||
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
|
||||
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
|
||||
messageActionContactSignUp#f3f25f76 = MessageAction;
|
||||
messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction;
|
||||
|
||||
dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
|
@ -195,7 +196,7 @@ photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize;
|
|||
photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector<int> = PhotoSize;
|
||||
|
||||
geoPointEmpty#1117dd5f = GeoPoint;
|
||||
geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint;
|
||||
geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint;
|
||||
|
||||
auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode;
|
||||
|
||||
|
@ -247,8 +248,8 @@ messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<
|
|||
messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
|
||||
|
||||
messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.messagesSlice#c8edce1e flags:# inexact:flags.1?true count:int next_rate:flags.0?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
|
||||
messages.messagesNotModified#74535f21 count:int = messages.Messages;
|
||||
|
||||
messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
|
||||
|
@ -274,6 +275,7 @@ inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
|
|||
inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter;
|
||||
inputMessagesFilterGeo#e7026d0d = MessagesFilter;
|
||||
inputMessagesFilterContacts#e062db83 = MessagesFilter;
|
||||
inputMessagesFilterPinned#1bb00451 = MessagesFilter;
|
||||
|
||||
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
|
||||
updateMessageID#4e90bfd6 id:int random_id:long = Update;
|
||||
|
@ -313,7 +315,6 @@ updateSavedGifs#9375341e = Update;
|
|||
updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update;
|
||||
updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
|
||||
updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update;
|
||||
updateChannelPinnedMessage#98592475 channel_id:int id:int = Update;
|
||||
updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
|
||||
updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
|
||||
updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
|
||||
|
@ -338,8 +339,6 @@ updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> =
|
|||
updateContactsReset#7084a7be = Update;
|
||||
updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
|
||||
updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
|
||||
updateUserPinnedMessage#4c43da18 user_id:int id:int = Update;
|
||||
updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update;
|
||||
updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
|
||||
updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update;
|
||||
updateFolderPeers#19360dc0 folder_peers:Vector<FolderPeer> pts:int pts_count:int = Update;
|
||||
|
@ -361,6 +360,8 @@ updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int
|
|||
updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update;
|
||||
updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update;
|
||||
updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update;
|
||||
updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector<int> pts:int pts_count:int = Update;
|
||||
updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector<int> pts:int pts_count:int = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
|
@ -607,6 +608,7 @@ channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelPar
|
|||
channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant;
|
||||
channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant;
|
||||
channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant;
|
||||
channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant;
|
||||
|
||||
channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter;
|
||||
channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter;
|
||||
|
@ -615,6 +617,7 @@ channelParticipantsBots#b0d1865b = ChannelParticipantsFilter;
|
|||
channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter;
|
||||
channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
|
||||
channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter;
|
||||
channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter;
|
||||
|
||||
channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
|
||||
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
|
||||
|
@ -628,7 +631,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
|
|||
|
||||
inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
|
||||
|
@ -640,7 +643,7 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input
|
|||
|
||||
botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
|
||||
|
||||
|
@ -1164,8 +1167,6 @@ messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:fla
|
|||
|
||||
messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> users:Vector<User> = messages.MessageViews;
|
||||
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
messages.discussionMessage#f5dd8f9d flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
|
||||
|
||||
messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
|
||||
|
@ -1174,6 +1175,8 @@ messageReplies#4128faac flags:# comments:flags.0?true replies:int replies_pts:in
|
|||
|
||||
peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
|
||||
|
||||
stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
|
@ -1299,7 +1302,7 @@ contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_hi
|
|||
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
|
||||
messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs;
|
||||
messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
||||
messages.search#4e17810b flags:# peer:InputPeer q:string from_id:flags.0?InputUser top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
||||
messages.search#c352eec flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
||||
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
|
||||
messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory;
|
||||
messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector<int> = messages.AffectedMessages;
|
||||
|
@ -1393,7 +1396,7 @@ messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
|
|||
messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
|
||||
messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
|
||||
messages.clearAllDrafts#7e58ee9c = Bool;
|
||||
messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates;
|
||||
messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates;
|
||||
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
|
||||
messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
|
||||
messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
|
||||
|
@ -1422,6 +1425,7 @@ messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = message
|
|||
messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
|
||||
messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
|
||||
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
|
||||
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
|
@ -1543,4 +1547,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
|
|||
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
|
||||
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
|
||||
|
||||
// LAYER 119
|
||||
// LAYER 120
|
||||
|
|
|
@ -9,6 +9,7 @@ ADMIN_RANK_INVALID,400,The given admin title or rank was invalid (possibly large
|
|||
API_ID_INVALID,400,The api_id/api_hash combination is invalid
|
||||
API_ID_PUBLISHED_FLOOD,400,"This API id was published somewhere, you can't use it now"
|
||||
ARTICLE_TITLE_EMPTY,400,The title of the article is empty
|
||||
AUDIO_TITLE_EMPTY,400,The title attribute of the audio must be non-empty
|
||||
AUTH_BYTES_INVALID,400,The provided authorization is invalid
|
||||
AUTH_KEY_DUPLICATED,406,"The authorization key (session file) was used under two different IP addresses simultaneously, and can no longer be used. Use the same session exclusively, or use different sessions"
|
||||
AUTH_KEY_INVALID,401,The key is invalid
|
||||
|
@ -22,6 +23,7 @@ BANNED_RIGHTS_INVALID,400,"You cannot use that set of permissions in this reques
|
|||
BOTS_TOO_MUCH,400,There are too many bots in this chat/channel
|
||||
BOT_CHANNELS_NA,400,Bots can't edit admin privileges
|
||||
BOT_COMMAND_DESCRIPTION_INVALID,400,"The command description was empty, too long or had invalid characters used"
|
||||
BOT_DOMAIN_INVALID,400,The domain used for the auth button does not match the one configured in @BotFather
|
||||
BOT_GAMES_DISABLED,400,Bot games cannot be used in this type of chat
|
||||
BOT_GROUPS_BLOCKED,400,This bot can't be added to groups
|
||||
BOT_INLINE_DISABLED,400,This bot can't be used in inline mode
|
||||
|
@ -88,6 +90,7 @@ EMAIL_INVALID,400,The given email is invalid
|
|||
EMAIL_UNCONFIRMED_X,400,"Email unconfirmed, the length of the code must be {code_length}"
|
||||
EMOTICON_EMPTY,400,The emoticon field cannot be empty
|
||||
EMOTICON_INVALID,400,The specified emoticon cannot be used or was not a emoticon
|
||||
EMOTICON_STICKERPACK_MISSING,400,The emoticon sticker pack you are trying to get is missing
|
||||
ENCRYPTED_MESSAGE_INVALID,400,Encrypted message invalid
|
||||
ENCRYPTION_ALREADY_ACCEPTED,400,Secret chat already accepted
|
||||
ENCRYPTION_ALREADY_DECLINED,400,The secret chat was already declined
|
||||
|
@ -272,6 +275,7 @@ START_PARAM_EMPTY,400,The start parameter is empty
|
|||
START_PARAM_INVALID,400,Start parameter invalid
|
||||
STATS_MIGRATE_X,303,The channel statistics must be fetched from DC {dc}
|
||||
STICKERSET_INVALID,400,The provided sticker set is invalid
|
||||
STICKERSET_OWNER_ANONYMOUS,406,This sticker set can't be used as the group's official stickers because it was created by one of its anonymous admins
|
||||
STICKERS_EMPTY,400,No sticker provided
|
||||
STICKER_DOCUMENT_INVALID,400,"The sticker file was invalid (this file has failed Telegram internal checks, make sure to use the correct format and comply with https://core.telegram.org/animated_stickers)"
|
||||
STICKER_EMOJI_INVALID,400,Sticker emoji invalid
|
||||
|
@ -294,7 +298,7 @@ TYPES_EMPTY,400,The types field is empty
|
|||
TYPE_CONSTRUCTOR_INVALID,,The type constructor is invalid
|
||||
UNKNOWN_METHOD,500,The method you tried to call cannot be called on non-CDN DCs
|
||||
UNTIL_DATE_INVALID,400,That date cannot be specified in this request (try using None)
|
||||
URL_INVALID,400,The URL used was invalid (e.g. when answering a callback with an URL that's not t.me/yourbot or your game's URL)
|
||||
URL_INVALID,400,The URL used was invalid (e.g. when answering a callback with a URL that's not t.me/yourbot or your game's URL)
|
||||
USERNAME_INVALID,400,"Nobody is using this username, or the username is unacceptable. If the latter, it must match r""[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]"""
|
||||
USERNAME_NOT_MODIFIED,400,The username is not different from the current username
|
||||
USERNAME_NOT_OCCUPIED,400,The username is not in use by anyone else yet
|
||||
|
|
|
|
@ -114,7 +114,7 @@ channels.readHistory,user,CHANNEL_INVALID CHANNEL_PRIVATE
|
|||
channels.readMessageContents,user,CHANNEL_INVALID CHANNEL_PRIVATE
|
||||
channels.reportSpam,user,CHANNEL_INVALID INPUT_USER_DEACTIVATED
|
||||
channels.setDiscussionGroup,user,BROADCAST_ID_INVALID LINK_NOT_MODIFIED MEGAGROUP_ID_INVALID MEGAGROUP_PREHISTORY_HIDDEN
|
||||
channels.setStickers,both,CHANNEL_INVALID PARTICIPANTS_TOO_FEW
|
||||
channels.setStickers,both,CHANNEL_INVALID PARTICIPANTS_TOO_FEW STICKERSET_OWNER_ANONYMOUS
|
||||
channels.togglePreHistoryHidden,user,CHAT_LINK_EXISTS
|
||||
channels.toggleSignatures,user,CHANNEL_INVALID
|
||||
channels.toggleSlowMode,user,SECONDS_INVALID
|
||||
|
@ -236,7 +236,7 @@ messages.getScheduledMessages,user,
|
|||
messages.getSearchCounters,user,
|
||||
messages.getSplitRanges,user,
|
||||
messages.getStatsURL,user,
|
||||
messages.getStickerSet,both,STICKERSET_INVALID
|
||||
messages.getStickerSet,both,EMOTICON_STICKERPACK_MISSING STICKERSET_INVALID
|
||||
messages.getStickers,user,EMOTICON_EMPTY
|
||||
messages.getSuggestedDialogFilters,user,
|
||||
messages.getUnreadMentions,user,PEER_ID_INVALID
|
||||
|
@ -273,7 +273,7 @@ messages.sendEncryptedFile,user,MSG_WAIT_FAILED
|
|||
messages.sendEncryptedService,user,DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED
|
||||
messages.sendInlineBotResult,user,CHAT_SEND_INLINE_FORBIDDEN CHAT_WRITE_FORBIDDEN INLINE_RESULT_EXPIRED PEER_ID_INVALID QUERY_ID_EMPTY SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
|
||||
messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY FILE_REFERENCE_EXPIRED GAME_BOT_INVALID INPUT_USER_DEACTIVATED MEDIA_CAPTION_TOO_LONG MEDIA_EMPTY PAYMENT_PROVIDER_INVALID PEER_ID_INVALID PHOTO_EXT_INVALID PHOTO_INVALID_DIMENSIONS PHOTO_SAVE_FILE_INVALID POLL_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID RANDOM_ID_DUPLICATE SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH STORAGE_CHECK_FAILED Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT VIDEO_CONTENT_TYPE_INVALID WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
|
||||
messages.sendMessage,both,AUTH_KEY_DUPLICATED BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
|
||||
messages.sendMessage,both,AUTH_KEY_DUPLICATED BOT_DOMAIN_INVALID BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
|
||||
messages.sendMultiMedia,both,SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH
|
||||
messages.sendScheduledMessages,user,
|
||||
messages.sendVote,user,MESSAGE_POLL_CLOSED OPTION_INVALID
|
||||
|
@ -282,7 +282,7 @@ messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY
|
|||
messages.setBotShippingResults,both,QUERY_ID_INVALID
|
||||
messages.setEncryptedTyping,user,CHAT_ID_INVALID
|
||||
messages.setGameScore,bot,PEER_ID_INVALID USER_BOT_REQUIRED
|
||||
messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID STICKER_DOCUMENT_INVALID USER_BOT_INVALID WEBDOCUMENT_URL_INVALID
|
||||
messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY AUDIO_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID STICKER_DOCUMENT_INVALID USER_BOT_INVALID WEBDOCUMENT_URL_INVALID
|
||||
messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED
|
||||
messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT
|
||||
messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID
|
||||
|
|
|
Loading…
Reference in New Issue
Block a user