Compare commits

..

No commits in common. "main" and "v2.3.55" have entirely different histories.

247 changed files with 1436 additions and 7078 deletions

View file

@ -14,25 +14,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Configure
run: bash build-docs.sh configure
- name: Cleanup
run: bash build-docs.sh cleanup
- name: Create virtual environment
run: bash build-docs.sh virtualenv
- uses: actions/checkout@v4
- name: Build
run: bash build-docs.sh build
- name: Clone gh-pages repository
run: bash build-docs.sh clone
run: bash build-docs.sh
env:
DOCS_KEY: ${{ secrets.DOCS_KEY }}
- name: Push changes
run: bash build-docs.sh push

View file

@ -9,7 +9,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4

651
.pylintrc
View file

@ -1,651 +0,0 @@
[MAIN]
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
# in a server-like mode.
clear-cache-post-run=no
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold under which the program will exit with error.
fail-under=10
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS
# Add files or directories matching the regular expressions patterns to the
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\\' represents the directory delimiter on Windows systems,
# it can't be used as an escape character.
ignore-paths=
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
# Emacs file locks
ignore-patterns=^\.#
# List of module names for which member attributes should not be checked and
# will not be imported (useful for modules/projects where namespaces are
# manipulated during runtime and thus existing member attributes cannot be
# deduced by static analysis). It supports qualified module names, as well as
# Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Resolve imports to .pyi stubs if available. May reduce no-member messages and
# increase not-an-iterable messages.
prefer-stubs=no
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.13
# Discover python modules and packages in the file system subtree.
recursive=no
# Add paths to the list of the source roots. Supports globbing patterns. The
# source root is an absolute path or a path relative to the current working
# directory used to determine a package namespace for modules located under the
# source root.
source-roots=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
#variable-rgx=
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
asyncSetUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of positional arguments for function / method.
max-positional-arguments=5
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,builtins.Exception
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero,
use-symbolic-message-instead,
missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
redefined-builtin
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=
[METHOD_ARGS]
# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
# Let 'consider-using-join' be raised when the separator to join on would be
# non-empty (resulting in expected fixes of the type: ``"- " + " -
# ".join(items)``)
suggest-join-with-non-empty-separator=yes
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# Set the output format. Available formats are: 'text', 'parseable',
# 'colorized', 'json2' (improved json format), 'json' (old json format), msvs
# (visual studio) and 'github' (GitHub actions). You can also give a reporter
# class, e.g. mypackage.mymodule.MyReporterClass.
#output-format=
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. No available dictionaries : You need to install
# both the python package and the system dependency for enchant to work.
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io

View file

@ -9,7 +9,7 @@
Homepage
</a>
<a href="https://pyrofork.wulan17.dev">
<a href="https://pyrofork.wulan17.top">
Documentation
</a>
@ -44,7 +44,7 @@ async def hello(client, message):
app.run()
```
**Pyrofork** is a modern, elegant and asynchronous [MTProto API](https://pyrofork.wulan17.dev/main/topics/mtproto-vs-botapi)
**Pyrofork** is a modern, elegant and asynchronous [MTProto API](https://pyrofork.wulan17.top/main/topics/mtproto-vs-botapi)
framework. It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot
identity (bot API alternative) using Python.
@ -72,6 +72,6 @@ pip3 install pyrofork
### Resources
- Check out the docs at https://pyrofork.wulan17.dev to learn more about Pyrofork, get started right
- Check out the docs at https://pyrofork.wulan17.top to learn more about Pyrofork, get started right
away and discover more in-depth material for building your client applications.
- Join the official group at https://t.me/MayuriChan_Chat and stay tuned for news, updates and announcements.

View file

@ -1,87 +1,42 @@
#!/bin/bash
export DOCS_KEY
VENV="$(pwd)"/venv
export VENV
# Check if config.sh exists
if [ -f config.sh ]; then
source config.sh
fi
function parse_parameters() {
while (($#)); do
case $1 in
all | configure | cleanup | virtualenv | build | clone | push ) action=$1 ;;
*) exit 33 ;;
esac
shift
done
}
function do_configure() {
echo "#!/bin/bash" > config.sh
echo "export VENV=\"$(pwd)/venv\"" >> config.sh
if [[ "$(echo "$GITHUB_REF" | cut -d '/' -f "1 2")" == "refs/tags" ]]; then
echo "export BRANCH=\"main\"" >> config.sh
elif [[ "$GITHUB_REF" == "refs/heads/staging" ]]; then
echo "export BRANCH=\"staging\"" >> config.sh
else
b="$(echo "$GITHUB_REF" | cut -d '/' -f '3 4')"
if [[ $(echo "$b" | cut -d '/' -f 1 ) == "dev" ]]; then
b="$(echo "$b" | cut -d '/' -f 2)"
if [[ "$b" =~ ^[0-9]\.[0-9]\.x ]]; then
echo "export BRANCH=\"$b\"" >> config.sh
else
exit 0
fi
if [[ "$(echo "$GITHUB_REF" | cut -d '/' -f "1 2")" == "refs/tags" ]]; then
branch="main"
elif [[ "$GITHUB_REF" == "refs/heads/staging" ]]; then
branch="staging"
else
b="$(echo "$GITHUB_REF" | cut -d '/' -f '3 4')"
if [[ $(echo "$b" | cut -d '/' -f 1 ) == "dev" ]]; then
b="$(echo "$b" | cut -d '/' -f 2)"
if [[ "$b" =~ ^[0-9]\.[0-9]\.x ]]; then
branch="$b"
else
exit 0
fi
else
exit 0
fi
chmod +x config.sh
}
fi
function do_cleanup() {
make clean
make clean-docs
}
function do_virtualenv() {
make venv
make api
"$VENV"/bin/pip install -e '.[docs]'
}
function do_build() {
cd compiler/docs || exit 1 && "$VENV"/bin/python compiler.py
cd ../.. || exit 1
"$VENV"/bin/sphinx-build -b html "docs/source" "docs/build/html" -j auto
}
function do_clone() {
git clone https://wulan17:"$DOCS_KEY"@github.com/Mayuri-Chan/pyrofork-docs.git
}
function do_push() {
cd pyrofork-docs || exit 1
mkdir -p "$BRANCH"
cd "$BRANCH" || exit 1
rm -rf _includes api genindex.html intro py-modindex.html sitemap.xml support.html topics _static faq index.html objects.inv searchindex.js start telegram
cp -r ../../docs/build/html/* .
git config --local user.name "Mayuri-Chan"
git config --local user.email "mayuri@mayuri.my.id"
git add --all
git commit -a -m "docs: $BRANCH: Update docs $(date '+%Y-%m-%d | %H:%m:%S %p %Z')" --signoff
git push -u origin --all
}
function do_all() {
do_configure
source config.sh
do_cleanup
do_virtualenv
do_build
do_clone
do_push
}
parse_parameters "$@"
do_"${action:=all}"
make clean
make clean-docs
make venv
make api
"$VENV"/bin/pip install -e '.[docs]'
cd compiler/docs || exit 1 && "$VENV"/bin/python compiler.py
cd ../.. || exit 1
"$VENV"/bin/sphinx-build -b html "docs/source" "docs/build/html" -j auto
git clone https://wulan17:"$DOCS_KEY"@github.com/Mayuri-Chan/pyrofork-docs.git
cd pyrofork-docs || exit 1
mkdir -p "$branch"
cd "$branch" || exit 1
rm -rf _includes api genindex.html intro py-modindex.html sitemap.xml support.html topics _static faq index.html objects.inv searchindex.js start telegram
cp -r ../../docs/build/html/* .
git config --local user.name "Mayuri-Chan"
git config --local user.email "mayuri@mayuri.my.id"
git add --all
git commit -a -m "docs: $branch: Update docs $(date '+%Y-%m-%d | %H:%m:%S %p %Z')" --signoff
git push -u origin --all

View file

@ -130,7 +130,7 @@ def get_type_hint(type: str) -> str:
return f"Optional[{type}] = None" if is_flag else type
else:
ns, name = type.split(".") if "." in type else ("", type)
type = '"raw.base.' + ".".join([ns, name]).strip(".") + '"'
type = f'"raw.base.' + ".".join([ns, name]).strip(".") + '"'
return f'{type}{" = None" if is_flag else ""}'
@ -430,11 +430,11 @@ def start(format: bool = False):
if function_docs:
docstring += function_docs["desc"] + "\n"
else:
docstring += "Telegram API function."
docstring += f"Telegram API function."
docstring += f"\n\n Details:\n - Layer: ``{layer}``\n - ID: ``{c.id[2:].upper()}``\n\n"
docstring += " Parameters:\n " + \
("\n ".join(docstring_args) if docstring_args else "No parameters required.\n")
docstring += f" Parameters:\n " + \
(f"\n ".join(docstring_args) if docstring_args else "No parameters required.\n")
if c.section == "functions":
docstring += "\n Returns:\n " + get_docstring_arg_type(c.qualtype)
@ -442,12 +442,12 @@ def start(format: bool = False):
references, count = get_references(c.qualname, "constructors")
if references:
docstring += "\n Functions:\n This object can be returned by " \
docstring += f"\n Functions:\n This object can be returned by " \
f"{count} function{'s' if count > 1 else ''}.\n\n" \
" .. currentmodule:: pyrogram.raw.functions\n\n" \
" .. autosummary::\n" \
" :nosignatures:\n\n" \
" " + references
f" .. currentmodule:: pyrogram.raw.functions\n\n" \
f" .. autosummary::\n" \
f" :nosignatures:\n\n" \
f" " + references
write_types = read_types = "" if c.has_flags else "# No flags\n "

File diff suppressed because it is too large Load diff

View file

@ -20,4 +20,4 @@ class {name}: # type: ignore
raise TypeError("Base types can only be used for type checking purposes: "
"you tried to use a base type instance as argument, "
"but you need to instantiate one of its constructors instead. "
"More info: https://pyrofork.wulan17.dev/telegram/base/{doc_name}")
"More info: https://pyrofork.wulan17.top/telegram/base/{doc_name}")

View file

@ -146,7 +146,6 @@ def pyrogram_api():
stop_transmission
export_session_string
set_parse_mode
ping
""",
conversation="""
Conversation
@ -165,7 +164,6 @@ def pyrogram_api():
""",
messages="""
Messages
add_task_to_todo
send_message
forward_media_group
forward_messages
@ -175,7 +173,6 @@ def pyrogram_api():
send_audio
send_document
send_sticker
send_todo
send_video
send_animation
send_voice
@ -187,7 +184,6 @@ def pyrogram_api():
send_contact
send_cached_media
send_reaction
set_todo_tasks_completion
edit_message_text
edit_message_caption
edit_message_media
@ -224,10 +220,8 @@ def pyrogram_api():
get_discussion_replies
get_discussion_replies_count
get_custom_emoji_stickers
transcribe_audio
translate_message_text
start_bot
delete_chat_history
""",
chats="""
Chats
@ -258,7 +252,6 @@ def pyrogram_api():
get_folders
get_forum_topics
get_forum_topics_by_id
get_forum_topics_count
set_chat_username
archive_chats
unarchive_chats
@ -285,7 +278,6 @@ def pyrogram_api():
hide_general_topic
reopen_forum_topic
reopen_general_topic
transfer_chat_ownership
unhide_general_topic
update_color
update_folder
@ -364,29 +356,20 @@ def pyrogram_api():
get_stars_transactions
get_stars_transactions_by_id
get_available_gifts
get_upgraded_gift
get_chat_gifts_count
get_chat_gifts
get_user_gifts_count
get_user_gifts
hide_gift
refund_star_payment
search_gifts_for_resale
send_invoice
send_paid_media
send_paid_reaction
send_payment_form
send_gift
send_resold_gift
set_gift_resale_price
set_pinned_gifts
show_gift
transfer_gift
upgrade_gift
get_stars_balance
""",
phone="""
Phone
get_call_members
""",
password="""
Password
enable_cloud_password
@ -415,17 +398,12 @@ def pyrogram_api():
set_bot_info
get_collectible_item_info
get_owned_bots
get_similar_bots
""",
business="""
Telegram Business
answer_pre_checkout_query
answer_shipping_query
delete_business_messages
get_business_connection
get_business_account_gifts
get_business_account_star_balance
transfer_business_account_stars
""",
authorization="""
Authorization
@ -437,11 +415,12 @@ def pyrogram_api():
resend_code
sign_in
sign_in_bot
sign_in_qrcode
sign_up
get_password_hint
check_password
send_recovery_code
recover_password
accept_terms_of_service
log_out
get_active_sessions
""",
@ -465,7 +444,7 @@ def pyrogram_api():
fmt_keys = {}
for k, v in categories.items():
_, *methods = get_title_list(v)
name, *methods = get_title_list(v)
fmt_keys.update({k: "\n ".join("{0} <{0}>".format(m) for m in methods)})
for method in methods:
@ -499,7 +478,6 @@ def pyrogram_api():
BusinessWeeklyOpen
BusinessWorkingHours
User
Username
Chat
ChatPreview
ChatPhoto
@ -518,12 +496,10 @@ def pyrogram_api():
Folder
Restriction
EmojiStatus
ExportedFolderLink
ForumTopic
PeerUser
PeerChannel
BotInfo
GroupCallMember
ChatColor
CollectibleItemInfo
BotVerification
@ -532,23 +508,11 @@ def pyrogram_api():
Messages & Media
Message
MessageEntity
MessageOriginChannel
MessageOriginChat
MessageOriginHiddenUser
MessageOriginImport
MessageOriginUser
MessageOrigin
Photo
Thumbnail
TodoList
TodoTask
TodoTasksAdded
TodoTasksCompleted
TodoTasksIncompleted
Audio
AvailableEffect
Document
ExternalReplyInfo
AlternativeVideo
Animation
Video
@ -569,9 +533,7 @@ def pyrogram_api():
WebPage
WebPageEmpty
WebPagePreview
TranscribedAudio
TranslatedText
TextQuote
Poll
PollOption
Dice
@ -587,7 +549,6 @@ def pyrogram_api():
ForumTopicCreated
ForumTopicEdited
ForumTopicClosed
ForumTopicDeleted
ForumTopicReopened
GeneralTopicHidden
GeneralTopicUnhidden
@ -627,7 +588,6 @@ def pyrogram_api():
Invoice
LabeledPrice
PaidMedia
PaidMessagePriceChanged
PaymentForm
PaymentInfo
PaymentRefunded
@ -737,20 +697,17 @@ def pyrogram_api():
InputMessageContent
InputMessageContent
InputReplyToMessage
InputReplyToMonoforum
InputReplyToStory
InputTextMessageContent
InputLocationMessageContent
InputVenueMessageContent
InputContactMessageContent
InputInvoiceMessageContent
InputTodoTask
""",
authorization="""
Authorization
ActiveSession
ActiveSessions
LoginToken
SentCode
TermsOfService
"""
@ -768,7 +725,7 @@ def pyrogram_api():
fmt_keys = {}
for k, v in categories.items():
_, *types = get_title_list(v)
name, *types = get_title_list(v)
fmt_keys.update({k: "\n ".join(types)})
@ -822,7 +779,6 @@ def pyrogram_api():
Message.reply_web_page
Message.get_media_group
Message.react
Message.transcribe
Message.translate
Message.wait_for_click
""",
@ -900,7 +856,6 @@ def pyrogram_api():
Gift.convert
Gift.upgrade
Gift.transfer
Gift.wear
""",
callback_query="""
Callback Query

View file

@ -17,7 +17,6 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import ast
import csv
import os
import re
@ -38,13 +37,6 @@ def caml(s):
s = snek(s).split("_")
return "".join([str(i.title()) for i in s])
def get_classes_from_file(file_path):
with open(file_path, "r", encoding="utf-8") as f:
tree = ast.parse(f.read())
classes = [node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
return classes
def start():
shutil.rmtree(DEST, ignore_errors=True)
@ -135,20 +127,6 @@ def start():
f_all.write(" },\n")
f_all.write("}\n")
with open(init, "a", encoding="utf-8") as f_init:
f_init.write("\n")
all_classes = []
for i in files:
code, name = re.search(r"(\d+)_([A-Z_]+)", i).groups()
classes = get_classes_from_file("{}/{}_{}.py".format(DEST, name.lower(), code))
for j in classes:
if j not in ["BaseException", "Exception", "PyrogramException"]:
all_classes.append(j)
f_init.write("__all__ = [\n")
all_classes = sorted(set(all_classes))
for i in all_classes:
f_init.write(" \"{}\",\n".format(i))
f_init.write("]\n")
with open("{}/all.py".format(DEST), encoding="utf-8") as f:
content = f.read()

View file

@ -35,7 +35,6 @@ BOT_GAMES_DISABLED Bot games cannot be used in this type of chat
BOT_GROUPS_BLOCKED This bot can't be added to groups
BOT_INLINE_DISABLED The inline feature of the bot is disabled
BOT_INVALID This is not a valid bot
BOT_INVOICE_INVALID The provided invoice is invalid
BOT_METHOD_INVALID The method can't be used by bots
BOT_MISSING This method can only be run by a bot
BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats
@ -47,7 +46,6 @@ BROADCAST_CALLS_DISABLED Broadcast calls disabled
BROADCAST_ID_INVALID The channel is invalid
BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels
BROADCAST_REQUIRED The request can only be used with a channel
BUSINESS_BOT_MISSING Business bot missing
BUTTON_DATA_INVALID The button callback data is invalid or too large
BUTTON_ID_INVALID The button_id parameter is invalid
BUTTON_TEXT_INVALID The specified button text is invalid
@ -505,4 +503,3 @@ BOOSTS_EMPTY You can't modify the icon of the General topic.
BOOST_NOT_MODIFIED You're already boosting the specified channel.
PAYMENT_REQUIRED The payment is required
BOOST_PEER_INVALID The specified `boost_peer` is invalid.
STARS_AMOUNT_INVALID The specified `amount` is invalid.
1 id message
35 BOT_GROUPS_BLOCKED This bot can't be added to groups
36 BOT_INLINE_DISABLED The inline feature of the bot is disabled
37 BOT_INVALID This is not a valid bot
BOT_INVOICE_INVALID The provided invoice is invalid
38 BOT_METHOD_INVALID The method can't be used by bots
39 BOT_MISSING This method can only be run by a bot
40 BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats
46 BROADCAST_ID_INVALID The channel is invalid
47 BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels
48 BROADCAST_REQUIRED The request can only be used with a channel
BUSINESS_BOT_MISSING Business bot missing
49 BUTTON_DATA_INVALID The button callback data is invalid or too large
50 BUTTON_ID_INVALID The button_id parameter is invalid
51 BUTTON_TEXT_INVALID The specified button text is invalid
503 BOOST_NOT_MODIFIED You're already boosting the specified channel.
504 PAYMENT_REQUIRED The payment is required
505 BOOST_PEER_INVALID The specified `boost_peer` is invalid.
STARS_AMOUNT_INVALID The specified `amount` is invalid.

View file

@ -44,4 +44,3 @@ GROUPCALL_ALREADY_STARTED The groupcall has already started, you can join direct
GROUPCALL_FORBIDDEN The group call has already ended
LIVE_DISABLED Story is disabled server-side
CHAT_GUEST_SEND_FORBIDDEN You need to join the discussion group before commenting
ALLOW_PAYMENT_REQUIRED_X Payment of {value} stars is required to perform this action
1 id message
44 GROUPCALL_FORBIDDEN The group call has already ended
45 LIVE_DISABLED Story is disabled server-side
46 CHAT_GUEST_SEND_FORBIDDEN You need to join the discussion group before commenting
ALLOW_PAYMENT_REQUIRED_X Payment of {value} stars is required to perform this action

View file

@ -1,8 +0,0 @@
GiftAttributeType
=================
.. autoclass:: pyrogram.enums.GiftForResaleOrder()
:members:
.. raw:: html
:file: ./cleanup.html

View file

@ -1,8 +0,0 @@
MessageOriginType
=================
.. autoclass:: pyrogram.enums.MessageOriginType()
:members:
.. raw:: html
:file: ./cleanup.html

View file

@ -29,7 +29,7 @@ from pygments.styles.friendly import FriendlyStyle
FriendlyStyle.background_color = "#f3f2f1"
project = "Pyrofork"
copyright = "2022-present, Mayuri-Chan"
copyright = f"2022-present, Mayuri-Chan"
author = "Mayuri-Chan"
version = ".".join(__version__.split(".")[:-1])
@ -73,7 +73,7 @@ html_theme_options = {
"repo": "fontawesome/brands/github",
"edit": "material/file-edit-outline",
},
"site_url": "https://pyrofork.wulan17.dev/",
"site_url": "https://pyrofork.wulan17.top/",
"repo_url": "https://github.com/Mayuri-Chan/pyrofork/",
"repo_name": "pyrofork",
"globaltoc_collapse": True,

View file

@ -47,7 +47,7 @@ like send_audio(), send_document(), send_location(), etc...
),
InlineKeyboardButton( # Opens a web URL
"URL",
url="https://pyrofork.wulan17.dev"
url="https://pyrofork.wulan17.top"
),
],
[ # Second row

View file

@ -24,13 +24,13 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler.
input_message_content=InputTextMessageContent(
"Here's how to install **Pyrofork**"
),
url="https://pyrofork.wulan17.dev/intro/install",
url="https://pyrofork.wulan17.top/intro/install",
description="How to install Pyrofork",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://pyrofork.wulan17.dev/intro/install"
url="https://pyrofork.wulan17.top/intro/install"
)]
]
)
@ -40,13 +40,13 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler.
input_message_content=InputTextMessageContent(
"Here's how to use **Pyrofork**"
),
url="https://pyrofork.wulan17.dev/start/invoking",
url="https://pyrofork.wulan17.top/start/invoking",
description="How to use Pyrofork",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://pyrofork.wulan17.dev/start/invoking"
url="https://pyrofork.wulan17.top/start/invoking"
)]
]
)

View file

@ -11,7 +11,7 @@ to make it only work for specific messages in a specific chat.
# Target chat. Can also be a list of multiple chat ids/usernames
TARGET = -100123456789
# Welcome message template
MESSAGE = "{} Welcome to [Pyrofork](https://pyrofork.wulan17.dev/)'s group chat {}!"
MESSAGE = "{} Welcome to [Pyrofork](https://pyrofork.wulan17.top/)'s group chat {}!"
app = Client("my_account")

View file

@ -80,20 +80,7 @@ Using async_pymongo (Recommended for python3.9+):
print(await app.get_me())
Using official mongodb driver:
.. code-block:: python
from pymongo import AsyncMongoClient
from pyrogram import Client
conn = AsyncMongoClient("mongodb://...")
async with Client("my_account", mongodb=dict(connection=conn, remove_peers=False)) as app:
print(await app.get_me())
Using motor (Deprecated, but still works):
Using motor:
.. code-block:: python

View file

@ -37,16 +37,68 @@ list of the basic styles currently supported by Pyrofork.
- spoiler
- `text URL <https://pyrogram.org>`_
- `user text mention <tg://user?id=123456789>`_
- :emoji:`🔥`
- ``inline fixed-width code``
- .. code-block:: text
pre-formatted
fixed-width
code block
- > Quoted text
- > Quoted text with collapse/expand button
Markdown Style
--------------
To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using
:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message:
.. code-block:: text
**bold**
__italic__
--underline--
~~strike~~
||spoiler||
[text URL](https://pyrogram.org/)
[text user mention](tg://user?id=123456789)
`inline fixed-width code`
```
pre-formatted
fixed-width
code block
```
> Quoted text
**Example**:
.. code-block:: python
from pyrogram import enums
await app.send_message(
"me",
(
"**bold**, "
"__italic__, "
"--underline--, "
"~~strike~~, "
"||spoiler||, "
"[URL](https://pyrogram.org), "
"`code`, "
"```"
"for i in range(10):\n"
" print(i)"
"```"
),
parse_mode=enums.ParseMode.MARKDOWN
)
HTML Style
----------
@ -70,10 +122,10 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode*
<a href="tg://user?id=123456789">inline mention</a>
<emoji id="12345678901234567890">🔥</emoji>
<code>inline fixed-width code</code>
<emoji id="12345678901234567890">🔥</emoji>
<pre>
pre-formatted
fixed-width
@ -82,8 +134,6 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode*
<blockquote>Quoted text</blockquote>
<blockquote expandable>Quoted text with collapse/expand button</blockquote>
**Example**:
.. code-block:: python
@ -128,72 +178,6 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode*
&lt;my text&gt;
Markdown Style
--------------
.. warning::
The Markdown style is not recommended for complex text formatting.
If you want to use complex text formatting such as nested entities, overlapping entities use the HTML style instead.
To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using
:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message:
.. code-block:: text
**bold**
__italic__
--underline--
~~strike~~
||spoiler||
[text URL](https://pyrogram.org/)
[text user mention](tg://user?id=123456789)
![🔥](tg://emoji?id=12345678901234567890)
`inline fixed-width code`
```
pre-formatted
fixed-width
code block
```
> Quoted text
**> Quoted text with collapse/expand button
**Example**:
.. code-block:: python
from pyrogram import enums
await app.send_message(
"me",
(
"**bold**, "
"__italic__, "
"--underline--, "
"~~strike~~, "
"||spoiler||, "
"[URL](https://pyrogram.org), "
"`code`, "
"```"
"for i in range(10):\n"
" print(i)"
"```"
),
parse_mode=enums.ParseMode.MARKDOWN
)
Different Styles
----------------
@ -246,18 +230,18 @@ strike` styles, and you can still combine both Markdown and HTML together.
Here there are some example texts you can try sending:
**Markdown**:
- ``**bold, --underline--**``
- ``**bold __italic --underline ~~strike~~--__**``
- ``**bold __and** italic__``
**HTML**:
- ``<b>bold, <u>underline</u></b>``
- ``<b>bold <i>italic <u>underline <s>strike</s></u></i></b>``
- ``<b>bold <i>and</b> italic</i>``
**Markdown (Not Recommended)**:
- ``**bold, --underline--**``
- ``**bold __italic --underline ~~strike~~--__**``
- ``**bold __and** italic__``
**Combined**:
- ``--you can combine <i>HTML</i> with **Markdown**--``

View file

@ -6,7 +6,7 @@ authors = [{ name = "wulan17", email = "mayuri@mayuri.my.id" }]
dependencies = ["pyaes==1.6.1", "pysocks==1.7.1", "pymediainfo-pyrofork>=6.0.1,<7.0.0"]
readme = "README.md"
license = "LGPL-3.0-or-later"
requires-python = "~=3.10"
requires-python = "~=3.9"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
@ -15,11 +15,11 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
@ -43,7 +43,7 @@ Homepage = "https://github.com/Mayuri-Chan"
Tracker = "https://github.com/Mayuri-Chan/pyrofork/issues"
Community = "https://t.me/MayuriChan_Chat"
Source = "https://github.com/Mayuri-Chan/pyrofork"
Documentation = "https://pyrofork.wulan17.dev"
Documentation = "https://pyrofork.wulan17.top"
[build-system]
requires = ["hatchling"]
@ -60,7 +60,7 @@ dev = [
docs = [
"sphinx",
"sphinx-immaterial==0.12.5",
"sphinx-immaterial==0.12.4",
"sphinx_copybutton",
"sphinx-autobuild",
"tornado>=6.3.3"

View file

@ -18,7 +18,7 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
__fork_name__ = "PyroFork"
__version__ = "2.3.69"
__version__ = "2.3.55"
__license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
__copyright__ = "Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>"
@ -37,24 +37,8 @@ class ContinuePropagation(StopAsyncIteration):
pass
from . import raw, types, filters, handlers, emoji, enums # pylint: disable=wrong-import-position
from .client import Client # pylint: disable=wrong-import-position
from .sync import idle, compose # pylint: disable=wrong-import-position
from . import raw, types, filters, handlers, emoji, enums
from .client import Client
from .sync import idle, compose
crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker")
__all__ = [
"Client",
"idle",
"compose",
"crypto_executor",
"StopTransmission",
"StopPropagation",
"ContinuePropagation",
"raw",
"types",
"filters",
"handlers",
"emoji",
"enums",
]

View file

@ -33,13 +33,12 @@ from importlib import import_module
from io import StringIO, BytesIO
from mimetypes import MimeTypes
from pathlib import Path
from typing import Union, List, Optional, Callable, AsyncGenerator, Tuple
from typing import Union, List, Optional, Callable, AsyncGenerator, Type, Tuple
import pyrogram
from pyrogram import __version__, __license__
from pyrogram import enums
from pyrogram import raw
from pyrogram import types
from pyrogram import utils
from pyrogram.crypto import aes
from pyrogram.errors import CDNFileHashMismatch
@ -52,26 +51,24 @@ from pyrogram.handlers.handler import Handler
from pyrogram.methods import Methods
from pyrogram.session import Auth, Session
from pyrogram.storage import FileStorage, MemoryStorage, Storage
from pyrogram.types import User
from pyrogram.utils import ainput
from .connection import Connection
from .connection.transport import TCPAbridged
from .dispatcher import Dispatcher
from .file_id import FileId, FileType, ThumbnailSource
from .mime_types import mime_types
from .parser import Parser
from .session.internals import MsgId
log = logging.getLogger(__name__)
MONGO_AVAIL = False
try:
import pymongo
except Exception:
pass
else:
from pyrogram.storage import MongoStorage
MONGO_AVAIL = True
from pyrogram.types import User, TermsOfService
from pyrogram.utils import ainput
from .connection import Connection
from .connection.transport import TCP, TCPAbridged
from .dispatcher import Dispatcher
from .file_id import FileId, FileType, ThumbnailSource
from .filters import Filter
from .mime_types import mime_types
from .parser import Parser
from .session.internals import MsgId
log = logging.getLogger(__name__)
class Client(Methods):
@ -131,9 +128,6 @@ class Client(Methods):
Pass a session string to load the session in-memory.
Implies ``in_memory=True``.
use_qrcode (``bool``, *optional*):
Pass True to login using a QR code.
in_memory (``bool``, *optional*):
Pass True to start an in-memory session that will be discarded as soon as the client stops.
In order to reconnect again using an in-memory session without having to login again, you can use
@ -227,13 +221,10 @@ class Client(Methods):
SYSTEM_VERSION = f"{platform.system()} {platform.release()}"
LANG_CODE = "en"
LANG_PACK = ""
SYSTEM_LANG_CODE = "en-US"
PARENT_DIR = Path(sys.argv[0]).parent
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
UPGRADED_GIFT_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:nft/|\+))([\w-]+)$")
WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None
WORKDIR = PARENT_DIR
@ -254,16 +245,13 @@ class Client(Methods):
app_version: str = APP_VERSION,
device_model: str = DEVICE_MODEL,
system_version: str = SYSTEM_VERSION,
system_lang_code: str = SYSTEM_LANG_CODE,
lang_code: str = LANG_CODE,
lang_pack: str = LANG_PACK,
ipv6: Optional[bool] = False,
alt_port: Optional[bool] = False,
proxy: Optional[dict] = None,
test_mode: Optional[bool] = False,
bot_token: Optional[str] = None,
session_string: Optional[str] = None,
use_qrcode: Optional[bool] = False,
in_memory: Optional[bool] = None,
mongodb: Optional[dict] = None,
storage: Optional[Storage] = None,
@ -278,7 +266,7 @@ class Client(Methods):
skip_updates: bool = True,
takeout: bool = None,
sleep_threshold: int = Session.SLEEP_THRESHOLD,
hide_password: Optional[bool] = True,
hide_password: Optional[bool] = False,
max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS,
client_platform: "enums.ClientPlatform" = enums.ClientPlatform.OTHER,
max_message_cache_size: int = MAX_CACHE_SIZE,
@ -292,16 +280,13 @@ class Client(Methods):
self.app_version = app_version
self.device_model = device_model
self.system_version = system_version
self.system_lang_code = system_lang_code.lower()
self.lang_code = lang_code.lower()
self.lang_pack = lang_pack.lower()
self.ipv6 = ipv6
self.alt_port = alt_port
self.proxy = proxy
self.test_mode = test_mode
self.bot_token = bot_token
self.session_string = session_string
self.use_qrcode = use_qrcode
self.in_memory = in_memory
self.mongodb = mongodb
self.phone_number = phone_number
@ -331,7 +316,9 @@ class Client(Methods):
elif self.in_memory:
self.storage = MemoryStorage(self.name)
elif self.mongodb:
if not MONGO_AVAIL:
try:
import pymongo
except Exception:
log.warning(
"pymongo is missing! "
"Using MemoryStorage as session storage"
@ -410,15 +397,6 @@ class Client(Methods):
if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL):
await self.invoke(raw.functions.updates.GetState())
async def _wait_for_update_login_token(self):
"""
Wait for an UpdateLoginToken update from Telegram.
"""
while True:
update, _, _ = await self.dispatcher.updates_queue.get()
if isinstance(update, raw.types.UpdateLoginToken):
break
async def authorize(self) -> User:
if self.bot_token:
return await self.sign_in_bot(self.bot_token)
@ -426,60 +404,52 @@ class Client(Methods):
print(f"Welcome to Pyrogram (version {__version__})")
print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n"
f"under the terms of the {__license__}.\n")
if not self.use_qrcode:
while True:
try:
if not self.phone_number:
while True:
print("Enter 'qrcode' if you want to login with qrcode.")
value = await ainput("Enter phone number or bot token: ")
if not value:
continue
if value.lower() == "qrcode":
self.use_qrcode = True
break
confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower()
if confirm == "y":
break
if ":" in value:
self.bot_token = value
return await self.sign_in_bot(value)
else:
self.phone_number = value
if not self.use_qrcode:
sent_code = await self.send_code(self.phone_number)
except BadRequest as e:
print(e.MESSAGE)
self.phone_number = None
self.bot_token = None
else:
break
if not self.use_qrcode:
sent_code_descriptions = {
enums.SentCodeType.APP: "Telegram app",
enums.SentCodeType.SMS: "SMS",
enums.SentCodeType.CALL: "phone call",
enums.SentCodeType.FLASH_CALL: "phone flash call",
enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS",
enums.SentCodeType.EMAIL_CODE: "email code"
}
print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}")
while True:
if not self.use_qrcode and not self.phone_code:
try:
if not self.phone_number:
while True:
value = await ainput("Enter phone number or bot token: ")
if not value:
continue
confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower()
if confirm == "y":
break
if ":" in value:
self.bot_token = value
return await self.sign_in_bot(value)
else:
self.phone_number = value
sent_code = await self.send_code(self.phone_number)
except BadRequest as e:
print(e.MESSAGE)
self.phone_number = None
self.bot_token = None
else:
break
sent_code_descriptions = {
enums.SentCodeType.APP: "Telegram app",
enums.SentCodeType.SMS: "SMS",
enums.SentCodeType.CALL: "phone call",
enums.SentCodeType.FLASH_CALL: "phone flash call",
enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS",
enums.SentCodeType.EMAIL_CODE: "email code"
}
print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}")
while True:
if not self.phone_code:
self.phone_code = await ainput("Enter confirmation code: ")
try:
if self.use_qrcode:
signed_in = await self.sign_in_qrcode()
else:
signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code)
signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code)
except BadRequest as e:
print(e.MESSAGE)
self.phone_code = None
@ -518,18 +488,33 @@ class Client(Methods):
print(e.MESSAGE)
self.password = None
else:
if self.use_qrcode and isinstance(signed_in, types.LoginToken):
time_out = signed_in.expires - datetime.timestamp(datetime.now())
try:
await asyncio.wait_for(self._wait_for_update_login_token(), timeout=time_out)
except asyncio.TimeoutError:
print("QR code expired, Requesting new Qr code...")
continue
break
if isinstance(signed_in, User):
return signed_in
while True:
first_name = await ainput("Enter first name: ")
last_name = await ainput("Enter last name (empty to skip): ")
try:
signed_up = await self.sign_up(
self.phone_number,
sent_code.phone_code_hash,
first_name,
last_name
)
except BadRequest as e:
print(e.MESSAGE)
else:
break
if isinstance(signed_in, TermsOfService):
print("\n" + signed_in.text + "\n")
await self.accept_terms_of_service(signed_in.id)
return signed_up
def set_parse_mode(self, parse_mode: Optional["enums.ParseMode"]):
"""Set the parse mode to be used globally by the client.
@ -825,7 +810,7 @@ class Client(Methods):
if session_empty:
if not self.api_id or not self.api_hash:
raise AttributeError("The API key is required for new authorizations. "
"More info: https://pyrofork.wulan17.dev/main/start/auth")
"More info: https://pyrofork.wulan17.top/main/start/auth")
await self.storage.api_id(self.api_id)
@ -903,7 +888,7 @@ class Client(Methods):
count = 0
if not include:
for current_root, _, filenames in os.walk(root.replace(".", "/")):
for current_root, dirnames, filenames in os.walk(root.replace(".", "/")):
namespace = current_root.replace("/", ".").replace("\\", ".")
if "__pycache__" in namespace:
continue
@ -968,7 +953,7 @@ class Client(Methods):
)
count += 1
except Exception:
except Exception as e:
pass
else:
for path, handlers in include:
@ -1057,7 +1042,7 @@ class Client(Methods):
)
count += 1
except Exception:
except Exception as e:
pass
if handlers is None:
@ -1121,7 +1106,7 @@ class Client(Methods):
async def handle_download(self, packet):
file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet
_ = os.makedirs(directory, exist_ok=True) if not in_memory else None
os.makedirs(directory, exist_ok=True) if not in_memory else None
temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
file = BytesIO() if in_memory else open(temp_file_path, "wb")

View file

@ -18,7 +18,3 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from .connection import Connection
__all__ = [
"Connection"
]

View file

@ -52,7 +52,7 @@ class Connection:
self.protocol: Optional[TCP] = None
async def connect(self) -> None:
for _ in range(Connection.MAX_CONNECTION_ATTEMPTS):
for i in range(Connection.MAX_CONNECTION_ATTEMPTS):
self.protocol = self.protocol_factory(ipv6=self.ipv6, proxy=self.proxy)
try:

View file

@ -18,6 +18,3 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from .tcp import *
__all__ = []
__all__.extend(tcp.__all__)

View file

@ -23,13 +23,3 @@ from .tcp_abridged_o import TCPAbridgedO
from .tcp_full import TCPFull
from .tcp_intermediate import TCPIntermediate
from .tcp_intermediate_o import TCPIntermediateO
__all__ = [
"TCP",
"Proxy",
"TCPAbridged",
"TCPAbridgedO",
"TCPFull",
"TCPIntermediate",
"TCPIntermediateO"
]

View file

@ -21,6 +21,7 @@ import asyncio
import ipaddress
import logging
import socket
from concurrent.futures import ThreadPoolExecutor
from typing import Tuple, Dict, TypedDict, Optional
import socks

View file

@ -55,7 +55,7 @@ except ImportError:
log.warning(
"TgCrypto is missing! "
"Pyrogram will work the same, but at a much slower speed. "
"More info: https://pyrofork.wulan17.dev/main/topics/speedups"
"More info: https://pyrofork.wulan17.top/main/topics/speedups"
)

View file

@ -71,7 +71,7 @@ def unpack(
message = Message.read(data)
except KeyError as e:
if e.args[0] == 0:
raise ConnectionError("Received empty data. Check your internet connection.")
raise ConnectionError(f"Received empty data. Check your internet connection.")
left = data.read().hex()

View file

@ -55,7 +55,7 @@ def decompose(pq: int) -> int:
while g == 1:
x = y
for _ in range(r):
for i in range(r):
y = (pow(y, 2, pq) + c) % pq
k = 0
@ -63,7 +63,7 @@ def decompose(pq: int) -> int:
while k < r and g == 1:
ys = y
for _ in range(min(m, r - k)):
for i in range(min(m, r - k)):
y = (pow(y, 2, pq) + c) % pq
q = q * (abs(x - y)) % pq

View file

@ -21,10 +21,9 @@ import asyncio
import inspect
import logging
from collections import OrderedDict
from typing import Any
import pyrogram
from pyrogram import raw, types, utils
from pyrogram import errors, raw, types, utils
from pyrogram.handlers.handler import Handler
from pyrogram.handlers import (
BotBusinessConnectHandler,
@ -95,12 +94,7 @@ class Dispatcher:
def __init__(self, client: "pyrogram.Client"):
self.client = client
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.loop = loop
self.loop = asyncio.get_event_loop()
self.handler_worker_tasks = []
self.locks_list = []
@ -272,7 +266,7 @@ class Dispatcher:
async def start(self):
if not self.client.no_updates:
for _ in range(self.client.workers):
for i in range(self.client.workers):
self.locks_list.append(asyncio.Lock())
self.handler_worker_tasks.append(
@ -343,88 +337,59 @@ class Dispatcher:
async def handler_worker(self, lock: asyncio.Lock):
while True:
packet = await self.updates_queue.get()
if packet is None:
break
await self._process_packet(packet, lock)
try:
await self._handle_packet(packet, lock)
except pyrogram.StopPropagation:
pass
except Exception as e:
log.exception(e)
finally:
self.updates_queue.task_done()
async def _handle_packet(self, packet, lock: asyncio.Lock):
update, users, chats = packet
parser = self.update_parsers.get(type(update))
parsed_update, handler_type = (
await parser(update, users, chats)
if parser is not None else (None, type(None))
)
async with lock:
await self._dispatch_to_handlers(update, users, chats, parsed_update, handler_type)
async def _dispatch_to_handlers(
self, update, users, chats, parsed_update, handler_type,
):
for group in self.groups.values():
for handler in group:
args = await self._match_handler(
handler, update, users, chats, parsed_update, handler_type,
)
if args is None:
continue
try:
await self._execute_handler(handler, *args)
except pyrogram.StopPropagation:
raise
except pyrogram.ContinuePropagation:
continue
except Exception as error:
if parsed_update is not None:
await self._handle_exception(parsed_update, error)
break
async def _match_handler(
self, handler, update, users, chats, parsed_update, handler_type,
async def _process_packet(
self,
packet: tuple[raw.core.TLObject, dict[int, types.Update], dict[int, types.Update]],
lock: asyncio.Lock,
):
try:
if isinstance(handler, handler_type):
if await handler.check(self.client, parsed_update):
return (parsed_update,)
elif isinstance(handler, RawUpdateHandler):
if await handler.check(self.client, update):
return (update, users, chats)
update, users, chats = packet
parser = self.update_parsers.get(type(update))
if parser is not None:
parsed_result = parser(update, users, chats)
if inspect.isawaitable(parsed_result):
parsed_update, handler_type = await parsed_result
else:
parsed_update, handler_type = parsed_result
else:
parsed_update, handler_type = (None, type(None))
async with lock:
for group in self.groups.values():
for handler in group:
try:
if parsed_update is not None:
if isinstance(handler, handler_type) and await handler.check(
self.client, parsed_update
):
await self._execute_callback(handler, parsed_update)
break
elif isinstance(handler, RawUpdateHandler):
await self._execute_callback(handler, update, users, chats)
break
except (pyrogram.StopPropagation, pyrogram.ContinuePropagation) as e:
if isinstance(e, pyrogram.StopPropagation):
raise
except Exception as exception:
if parsed_update is not None:
await self._handle_exception(parsed_update, exception)
except pyrogram.StopPropagation:
pass
except Exception as e:
log.exception(e)
finally:
self.updates_queue.task_done()
return None
async def _execute_handler(self, handler, *args: Any):
if inspect.iscoroutinefunction(handler.callback):
await handler.callback(self.client, *args)
else:
await self.loop.run_in_executor(
self.client.executor,
handler.callback,
self.client,
*args
)
async def _handle_exception(
self, parsed_update: types.Update, exception: Exception,
):
async def _handle_exception(self, parsed_update: types.Update, exception: Exception):
handled_error = False
for error_handler in self.error_handlers:
try:
if await error_handler.check(
self.client, parsed_update, exception,
):
if await error_handler.check(self.client, parsed_update, exception):
handled_error = True
break
except pyrogram.StopPropagation:
@ -436,3 +401,11 @@ class Dispatcher:
if not handled_error:
log.exception("Unhandled exception: %s", exception)
async def _execute_callback(self, handler: Handler, *args):
if inspect.iscoroutinefunction(handler.callback):
await handler.callback(self.client, *args)
else:
await self.client.loop.run_in_executor(
self.client.executor, handler.callback, self.client, *args
)

View file

@ -27,11 +27,9 @@ from .chat_type import ChatType
from .client_platform import ClientPlatform
from .folder_color import FolderColor
from .gift_attribute_type import GiftAttributeType
from .gift_for_resale_order import GiftForResaleOrder
from .listerner_types import ListenerTypes
from .message_entity_type import MessageEntityType
from .message_media_type import MessageMediaType
from .message_origin_type import MessageOriginType
from .message_service_type import MessageServiceType
from .messages_filter import MessagesFilter
from .next_code_type import NextCodeType
@ -56,11 +54,9 @@ __all__ = [
'ClientPlatform',
'FolderColor',
'GiftAttributeType',
'GiftForResaleOrder',
'ListenerTypes',
'MessageEntityType',
'MessageMediaType',
'MessageOriginType',
'MessageServiceType',
'MessagesFilter',
'NextCodeType',

View file

@ -35,13 +35,13 @@ class ChatEventAction(AutoName):
"The linked chat has been changed (see ``old_linked_chat`` and ``new_linked_chat``)"
# LOCATION_CHANGED = auto()
# ""
""
PHOTO_CHANGED = auto()
"The chat photo has been changed (see ``old_photo`` and ``new_photo``)"
# STICKER_SET_CHANGED = auto()
# ""
""
TITLE_CHANGED = auto()
"the chat title has been changed (see ``old_title`` and ``new_title``)"
@ -56,7 +56,7 @@ class ChatEventAction(AutoName):
"a message has been deleted (see ``deleted_message``)"
# VOICE_CHAT_DISCARDED = auto()
# ""
""
MESSAGE_EDITED = auto()
"a message has been edited (see ``old_message`` and ``new_message``)"
@ -77,13 +77,13 @@ class ChatEventAction(AutoName):
"a member joined by themselves. (see ``user``)"
# MEMBER_JOINED_BY_LINK = auto()
# ""
""
MEMBER_LEFT = auto()
"a member left by themselves. (see ``user``)"
# MEMBER_MUTED = auto()
# ""
""
ADMINISTRATOR_PRIVILEGES_CHANGED = auto()
"a chat member has been promoted/demoted or their administrator privileges has changed (see ``old_administrator_privileges`` and ``new_administrator_privileges``)"
@ -92,19 +92,19 @@ class ChatEventAction(AutoName):
"a chat member has been restricted/unrestricted or banned/unbanned, or their permissions has changed (see ``old_member_permissions`` and ``new_member_permissions``)"
# MEMBER_UNMUTED = auto()
# ""
""
# MEMBER_VOLUME_CHANGED = auto()
# ""
""
# VIDEO_CHAT_STARTED = auto()
# ""
""
POLL_STOPPED = auto()
"a poll has been stopped (see ``stopped_poll``)"
# VOICE_CHAT_SETTINGS_CHANGED = auto()
# ""
""
INVITES_ENABLED = auto()
"the chat invitation has been enabled or disabled (see ``invites_enabled``)"

View file

@ -39,9 +39,3 @@ class ChatType(AutoName):
CHANNEL = auto()
"Chat is a channel"
FORUM = auto()
"Chat is a forum"
MONOFORUM = auto()
"Chat is a monoforum"

View file

@ -1,35 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from enum import auto
from .auto_name import AutoName
class GiftForResaleOrder(AutoName):
"""Describes order in which upgraded gifts for resale will be sorted. Used in :meth:`~pyrogram.Client.search_gifts_for_resale`."""
PRICE = auto()
"The gifts will be sorted by their price from the lowest to the highest"
CHANGE_DATE = auto()
"The gifts will be sorted by the last date when their price was changed from the newest to the oldest"
NUMBER = auto()
"The gifts will be sorted by their number from the smallest to the largest"

View file

@ -84,6 +84,3 @@ class MessageMediaType(AutoName):
PAID_MEDIA = auto()
"Paid media"
TODO = auto()
"To-Do list media"

View file

@ -1,42 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from enum import auto
from .auto_name import AutoName
class MessageOriginType(AutoName):
"""Message origin type enumeration used in :obj:`~pyrogram.types.MessageOrigin`."""
CHANNEL = auto()
"The message was originally a post in a channel"
CHAT = auto()
"The message was originally sent on behalf of a chat"
HIDDEN_USER = auto()
"The message was originally sent by a user, which is hidden by their privacy settings"
IMPORT = auto()
"The message was imported from a foreign chat service"
USER = auto()
"The message was originally sent by a known user"

View file

@ -132,12 +132,3 @@ class MessageServiceType(AutoName):
SCREENSHOT_TAKEN = auto()
"Screenshot taken"
PAID_MESSAGE_PRICE_CHANGED = auto()
"Paid message price changed"
TODO_TASKS_ADDED = auto()
"To-Do tasks added"
TODO_TASKS_COMPLETION = auto()
"To-Do tasks completion/incompletion"

View file

@ -17,15 +17,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
EXCEPTION_AVAIL = False
try:
from .exceptions import *
except ImportError:
pass
else:
EXCEPTION_AVAIL = True
from .exceptions import *
from .pyromod import *
from .rpc_error import RPCError, UnknownError
from .rpc_error import UnknownError
class BadMsgNotification(Exception):
@ -71,15 +65,3 @@ class CDNFileHashMismatch(SecurityError):
def __init__(self, msg: str = None):
super().__init__("A CDN file hash mismatch has occurred." if msg is None else msg)
__all__ = [
"BadMsgNotification",
"SecurityError",
"SecurityCheckMismatch",
"CDNFileHashMismatch",
"RPCError",
"UnknownError"
]
if EXCEPTION_AVAIL:
__all__.extend(exceptions.__all__)
__all__.extend(pyromod.__all__)

View file

@ -1,4 +1,3 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
@ -251,7 +250,7 @@ react = create(reaction_filter)
# region forwarded_filter
async def forwarded_filter(_, __, m: Message):
return bool(m.forward_origin)
return bool(m.forward_date)
forwarded = create(forwarded_filter)
@ -796,7 +795,7 @@ from_scheduled = create(from_scheduled_filter)
# region linked_channel_filter
async def linked_channel_filter(_, __, m: Message):
return bool((m.forward_origin and m.forward_origin.chat) and not m.from_user)
return bool(m.forward_from_chat and not m.from_user)
linked_channel = create(linked_channel_filter)
@ -890,12 +889,7 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "
command_re = re.compile(r"([\"'])(.*?)(?<!\\)\1|(\S+)")
async def func(flt, client: pyrogram.Client, message: Message):
usernames = []
username = client.me.username or ""
if client.me.usernames:
usernames.append(username)
for user in client.me.usernames:
usernames.append(user.username)
text = message.text or message.caption
message.command = None
@ -909,24 +903,6 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "
without_prefix = text[len(prefix):]
for cmd in flt.commands:
if usernames:
for username in usernames:
if not re.match(rf"^(?:{cmd}(?:@?{username})?)(?:\s|$)", without_prefix,
flags=re.IGNORECASE if not flt.case_sensitive else 0):
continue
without_command = re.sub(rf"{cmd}(?:@?{username})?\s?", "", without_prefix, count=1,
flags=re.IGNORECASE if not flt.case_sensitive else 0)
# match.groups are 1-indexed, group(1) is the quote, group(2) is the text
# between the quotes, group(3) is unquoted, whitespace-split text
# Remove the escape character from the arguments
message.command = [cmd] + [
re.sub(r"\\([\"'])", r"\1", m.group(2) or m.group(3) or "")
for m in command_re.finditer(without_command)
]
return True
if not re.match(rf"^(?:{cmd}(?:@?{username})?)(?:\s|$)", without_prefix,
flags=re.IGNORECASE if not flt.case_sensitive else 0):
continue
@ -1034,22 +1010,12 @@ class user(Filter, set):
)
async def __call__(self, _, message: Message):
is_usernames_in_filters = False
if message.from_user and message.from_user.usernames:
for username in message.from_user.usernames:
if (
username.username in self
or username.username.lower() in self
):
is_usernames_in_filters = True
break
return (message.from_user
and (message.from_user.id in self
or (message.from_user.username
and message.from_user.username.lower() in self)
or ("me" in self
and message.from_user.is_self))
or is_usernames_in_filters)
and message.from_user.is_self)))
# noinspection PyPep8Naming
@ -1077,15 +1043,6 @@ class chat(Filter, set):
async def __call__(self, _, message: Union[Message, Story]):
if isinstance(message, Story):
is_usernames_in_filters = False
if message.sender_chat and message.sender_chat.usernames:
for username in message.sender_chat.usernames:
if (
username.username in self
or username.username.lower() in self
):
is_usernames_in_filters = True
break
return (
message.sender_chat
and (
@ -1104,17 +1061,8 @@ class chat(Filter, set):
and message.from_user.username.lower() in self
)
)
) or is_usernames_in_filters
)
else:
is_usernames_in_filters = False
if message.chat and message.chat.usernames:
for username in message.chat.usernames:
if (
username.username in self
or username.username.lower() in self
):
is_usernames_in_filters = True
break
return (message.chat
and (message.chat.id in self
or (message.chat.username
@ -1122,10 +1070,7 @@ class chat(Filter, set):
or ("me" in self
and message.from_user
and message.from_user.is_self
and not message.outgoing))
or (is_usernames_in_filters
and not message.outgoing)
)
and not message.outgoing)))
# noinspection PyPep8Naming
@ -1136,7 +1081,6 @@ class topic(Filter, set):
Parameters:
topics (``int`` | ``list``):
Pass one or more topic ids to filter messages in specific topics.
Pass 1 for general topic.
Defaults to None (no topics).
"""
@ -1148,6 +1092,4 @@ class topic(Filter, set):
)
async def __call__(self, _, message: Message):
if message.is_topic_message and not message.topic:
return 1 in self
return message.topic and message.topic.id in self

View file

@ -33,6 +33,7 @@ from .error_handler import ErrorHandler
from .inline_query_handler import InlineQueryHandler
from .message_handler import MessageHandler
from .poll_handler import PollHandler
from .pre_checkout_query_handler import PreCheckoutQueryHandler
from .purchased_paid_media_handler import PurchasedPaidMediaHandler
from .raw_update_handler import RawUpdateHandler
from .user_status_handler import UserStatusHandler
@ -41,31 +42,3 @@ from .message_reaction_updated_handler import MessageReactionUpdatedHandler
from .message_reaction_count_updated_handler import MessageReactionCountUpdatedHandler
from .pre_checkout_query_handler import PreCheckoutQueryHandler
from .shipping_query_handler import ShippingQueryHandler
__all__ = [
"BotBusinessConnectHandler",
"BotBusinessMessageHandler",
"CallbackQueryHandler",
"ChatJoinRequestHandler",
"ChatMemberUpdatedHandler",
"ConversationHandler",
"ChosenInlineResultHandler",
"DeletedMessagesHandler",
"DeletedBotBusinessMessagesHandler",
"DisconnectHandler",
"EditedMessageHandler",
"EditedBotBusinessMessageHandler",
"ErrorHandler",
"InlineQueryHandler",
"MessageHandler",
"PollHandler",
"PreCheckoutQueryHandler",
"PurchasedPaidMediaHandler",
"RawUpdateHandler",
"UserStatusHandler",
"StoryHandler",
"MessageReactionUpdatedHandler",
"MessageReactionCountUpdatedHandler",
"PreCheckoutQueryHandler",
"ShippingQueryHandler",
]

View file

@ -61,7 +61,6 @@ class ConversationHandler(MessageHandler, CallbackQueryHandler):
return True
@staticmethod
# pylint: disable=method-hidden
async def callback(_, __):
pass

View file

@ -58,4 +58,5 @@ class DeletedBotBusinessMessagesHandler(Handler):
for message in messages:
if await super().check(client, message):
return True
return False
else:
return False

View file

@ -58,4 +58,5 @@ class DeletedMessagesHandler(Handler):
for message in messages:
if await super().check(client, message):
return True
return False
else:
return False

View file

@ -13,15 +13,4 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyromod. If not, see <https://www.gnu.org/licenses/>.
"""
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply
__all__ = [
"ikb",
"bki",
"ntb",
"btn",
"kb",
"kbtn",
"array_chunk",
"force_reply"
]
from .helpers import ikb, bki, ntb, btn, kb, kbtn, array_chunk, force_reply

View file

@ -101,9 +101,9 @@ def kb(rows=None, **kwargs):
line = []
for button in row:
button_type = type(button)
if isinstance(button_type, str):
if button_type == str:
button = KeyboardButton(button)
elif isinstance(button_type, dict):
elif button_type == dict:
button = KeyboardButton(**button)
line.append(button)

View file

@ -23,14 +23,12 @@ from .bots import Bots
from .chats import Chats
from .contacts import Contacts
from .decorators import Decorators
from .forums import Forums
from .invite_links import InviteLinks
from .messages import Messages
from .password import Password
from .pyromod import Pyromod
from .stickers import Stickers
from .payments import Payments
from .phone import Phone
from .users import Users
from .utilities import Utilities
from .business import TelegramBusiness
@ -44,9 +42,7 @@ class Methods(
Password,
Pyromod,
Payments,
Phone,
Chats,
Forums,
Stickers,
Users,
Messages,

View file

@ -23,6 +23,7 @@ import inspect
import io
import logging
import math
import os
from hashlib import md5
from pathlib import PurePath
from typing import Union, BinaryIO, Callable

View file

@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from .accept_terms_of_service import AcceptTermsOfService
from .check_password import CheckPassword
from .connect import Connect
from .disconnect import Disconnect
@ -30,11 +31,12 @@ from .send_code import SendCode
from .send_recovery_code import SendRecoveryCode
from .sign_in import SignIn
from .sign_in_bot import SignInBot
from .sign_in_qrcode import SignInQrcode
from .sign_up import SignUp
from .terminate import Terminate
class Auth(
AcceptTermsOfService,
CheckPassword,
Connect,
Disconnect,
@ -48,7 +50,7 @@ class Auth(
SendRecoveryCode,
SignIn,
SignInBot,
SignInQrcode,
SignUp,
Terminate
):
pass

View file

@ -1,4 +1,5 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
@ -16,31 +17,29 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import List, Union
import pyrogram
from pyrogram import raw
class GetSimilarBots:
async def get_similar_bots(
class AcceptTermsOfService:
async def accept_terms_of_service(
self: "pyrogram.Client",
bot: Union[int, str]
) -> List["pyrogram.types.User"]:
"""Get a list of bots similar to the target bot.
terms_of_service_id: str
) -> bool:
"""Accept the given terms of service.
.. include:: /_includes/usable-by/users.rst
Parameters:
bot (``int`` | ``str``):
Unique identifier (int) or username (str) of the target bot.
Returns:
List of :obj:`~pyrogram.types.User`: On success.
terms_of_service_id (``str``):
The terms of service identifier.
"""
peer = await self.resolve_peer(bot)
r = await self.invoke(raw.functions.bots.GetBotRecommendations(bot=peer))
return pyrogram.types.List([
pyrogram.types.User._parse(self, u)
for u in r.users
])
r = await self.invoke(
raw.functions.help.AcceptTermsOfService(
id=raw.types.DataJSON(
data=terms_of_service_id
)
)
)
return bool(r)

View file

@ -35,7 +35,6 @@ class Connect:
Raises:
ConnectionError: In case you try to connect an already connected client.
"""
# pylint: disable=access-member-before-definition
if self.is_connected:
raise ConnectionError("Client is already connected")

View file

@ -30,7 +30,6 @@ class Disconnect:
ConnectionError: In case you try to disconnect an already disconnected client or in case you try to
disconnect a client that needs to be terminated first.
"""
# pylint: disable=access-member-before-definition
if not self.is_connected:
raise ConnectionError("Client is already disconnected")

View file

@ -16,6 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import List
import pyrogram
from pyrogram import raw, types

View file

@ -41,7 +41,6 @@ class Initialize:
if not self.is_connected:
raise ConnectionError("Can't initialize a disconnected client")
# pylint: disable=access-member-before-definition
if self.is_initialized:
raise ConnectionError("Client is already initialized")

View file

@ -61,7 +61,6 @@ class SendCode:
)
)
except (PhoneMigrate, NetworkMigrate) as e:
# pylint: disable=access-member-before-definition
await self.session.stop()
await self.storage.dc_id(e.value)

View file

@ -23,7 +23,6 @@ from typing import Union
import pyrogram
from pyrogram import raw
from pyrogram import types
from pyrogram.errors import PhoneNumberUnoccupied
log = logging.getLogger(__name__)
@ -50,13 +49,15 @@ class SignIn:
The valid confirmation code you received (either as Telegram message or as SMS in your phone number).
Returns:
:obj:`~pyrogram.types.User` | bool: On success, in case the
authorization completed, the user is returned.
:obj:`~pyrogram.types.User` | :obj:`~pyrogram.types.TermsOfService` | bool: On success, in case the
authorization completed, the user is returned. In case the phone number needs to be registered first AND the
terms of services accepted (with :meth:`~pyrogram.Client.accept_terms_of_service`), an object containing
them is returned. In case the phone number needs to be registered, but the terms of services don't need to
be accepted, False is returned instead.
Raises:
BadRequest: In case the arguments are invalid.
SessionPasswordNeeded: In case a password is needed to sign in.
PhoneNumberUnoccupied: In case the phone number is not registered on Telegram.
"""
phone_number = phone_number.strip(" +")
@ -69,7 +70,10 @@ class SignIn:
)
if isinstance(r, raw.types.auth.AuthorizationSignUpRequired):
raise PhoneNumberUnoccupied("The phone number is not registered on Telegram. Please use official Telegram app to register it.")
if r.terms_of_service:
return types.TermsOfService._parse(terms_of_service=r.terms_of_service)
return False
else:
await self.storage.user_id(r.user.id)
await self.storage.is_bot(False)

View file

@ -58,7 +58,6 @@ class SignInBot:
)
)
except UserMigrate as e:
# pylint: disable=access-member-before-definition
await self.session.stop()
await self.storage.dc_id(e.value)

View file

@ -1,111 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
from base64 import b64encode
from typing import Union
import pyrogram
from pyrogram import raw
from pyrogram import types
from pyrogram.session import Session, Auth
log = logging.getLogger(__name__)
class SignInQrcode:
async def sign_in_qrcode(
self: "pyrogram.Client"
) -> Union["types.User", "types.LoginToken"]:
"""Authorize a user in Telegram with a QR code.
.. include:: /_includes/usable-by/users.rst
Returns:
:obj:`~pyrogram.types.User` | :obj:`pyrogram.types.LoginToken`, in case the
authorization completed, the user is returned. In case the QR code is
not scanned, a login token is returned.
Raises:
ImportError: In case the qrcode library is not installed.
SessionPasswordNeeded: In case a password is needed to sign in.
"""
try:
import qrcode
except ImportError:
raise ImportError("qrcode is missing! "
"Please install it with `pip install qrcode`")
r = await self.session.invoke(
raw.functions.auth.ExportLoginToken(
api_id=self.api_id,
api_hash=self.api_hash,
except_ids=[]
)
)
if isinstance(r, raw.types.auth.LoginToken):
base64_token = b64encode(r.token).decode("utf-8")
login_url = f"tg://login?token={base64_token}"
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(login_url)
qr.make(fit=True)
print("Scan the QR code with your Telegram app.")
qr.print_ascii()
return types.LoginToken._parse(r)
if isinstance(r, raw.types.auth.LoginTokenSuccess):
await self.storage.user_id(r.authorization.user.id)
await self.storage.is_bot(False)
return types.User._parse(self, r.authorization.user)
if isinstance(r, raw.types.auth.LoginTokenMigrateTo):
# pylint: disable=access-member-before-definition
await self.session.stop()
await self.storage.dc_id(r.dc_id)
await self.storage.auth_key(
await Auth(
self, await self.storage.dc_id(),
await self.storage.test_mode()
).create()
)
self.session = Session(
self, await self.storage.dc_id(),
await self.storage.auth_key(), await self.storage.test_mode()
)
await self.session.start()
r = await self.session.invoke(
raw.functions.auth.ImportLoginToken(
token=r.token
)
)
if isinstance(r, raw.types.auth.LoginTokenSuccess):
await self.storage.user_id(r.authorization.user.id)
await self.storage.is_bot(False)
return types.User._parse(self, r.authorization.user)
raise pyrogram.exceptions.RPCError(
"Unknown response type from Telegram API"
)

View file

@ -0,0 +1,74 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
import pyrogram
from pyrogram import raw
from pyrogram import types
log = logging.getLogger(__name__)
class SignUp:
async def sign_up(
self: "pyrogram.Client",
phone_number: str,
phone_code_hash: str,
first_name: str,
last_name: str = ""
) -> "types.User":
"""Register a new user in Telegram.
.. include:: /_includes/usable-by/users.rst
Parameters:
phone_number (``str``):
Phone number in international format (includes the country prefix).
phone_code_hash (``str``):
Code identifier taken from the result of :meth:`~pyrogram.Client.send_code`.
first_name (``str``):
New user first name.
last_name (``str``, *optional*):
New user last name. Defaults to "" (empty string, no last name).
Returns:
:obj:`~pyrogram.types.User`: On success, the new registered user is returned.
Raises:
BadRequest: In case the arguments are invalid.
"""
phone_number = phone_number.strip(" +")
r = await self.invoke(
raw.functions.auth.SignUp(
phone_number=phone_number,
first_name=first_name,
last_name=last_name,
phone_code_hash=phone_code_hash
)
)
await self.storage.user_id(r.user.id)
await self.storage.is_bot(False)
return types.User._parse(self, r.user)

View file

@ -37,7 +37,6 @@ class Terminate:
Raises:
ConnectionError: In case you try to terminate a client that is already terminated.
"""
# pylint: disable=access-member-before-definition
if not self.is_initialized:
raise ConnectionError("Client is already terminated")

View file

@ -37,7 +37,6 @@ from .set_bot_info import SetBotInfo
from .set_chat_menu_button import SetChatMenuButton
from .set_game_score import SetGameScore
from .get_owned_bots import GetOwnedBots
from .get_similar_bots import GetSimilarBots
class Bots(
@ -61,6 +60,5 @@ class Bots(
AnswerWebAppQuery,
GetCollectibleItemInfo,
GetOwnedBots,
GetSimilarBots,
):
pass

View file

@ -28,24 +28,17 @@ class GetCollectibleItemInfo:
phone_number: str = None
) -> "types.CollectibleInfo":
"""Returns information about a given collectible item that was purchased at https://fragment.com
.. include:: /_includes/usable-by/users.rst
You must use exactly one of ``username`` OR ``phone_number``.
Parameters:
username (``str``, *optional*):
Describes a collectible username that can be purchased at https://fragment.com
phone_number (``str``, *optional*):
Describes a collectible phone number that can be purchased at https://fragment.com
Returns:
:obj:`~pyrogram.types.CollectibleInfo`: On success, a collectible info is returned.
Example:
.. code-block:: python
username = await app.get_collectible_item_info(username="nerd")
print(username)
"""

View file

@ -102,17 +102,6 @@ class SendInlineBotResult:
"""
quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values()
reply_to = await utils.get_reply_to(
client=self,
reply_to_message_id=reply_to_message_id,
reply_to_chat_id=reply_to_chat_id,
reply_to_story_id=reply_to_story_id,
message_thread_id=message_thread_id,
quote_text=quote_text,
quote_entities=quote_entities,
quote_offset=quote_offset,
parse_mode=parse_mode,
)
r = await self.invoke(
raw.functions.messages.SendInlineBotResult(
peer=await self.resolve_peer(chat_id),
@ -120,7 +109,15 @@ class SendInlineBotResult:
id=result_id,
random_id=self.rnd_id(),
silent=disable_notification or None,
reply_to=reply_to,
reply_to=utils.get_reply_to(
reply_to_message_id=reply_to_message_id,
reply_to_peer=await self.resolve_peer(reply_to_chat_id) if reply_to_chat_id else None,
reply_to_story_id=reply_to_story_id,
message_thread_id=message_thread_id,
quote_text=quote_text,
quote_entities=quote_entities,
quote_offset=quote_offset,
),
schedule_date=utils.datetime_to_timestamp(schedule_date),
)
)

View file

@ -1,6 +1,5 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
@ -19,20 +18,12 @@
from .answer_pre_checkout_query import AnswerPreCheckoutQuery
from .answer_shipping_query import AnswerShippingQuery
from .delete_business_messages import DeleteBusinessMessages
from .get_business_connection import GetBusinessConnection
from .get_business_account_gifts import GetBusinessAccountGifts
from .get_business_account_star_balance import GetBusinessAccountStarBalance
from .transfer_business_account_stars import TransferBusinessAccountStars
class TelegramBusiness(
AnswerPreCheckoutQuery,
AnswerShippingQuery,
DeleteBusinessMessages,
GetBusinessConnection,
GetBusinessAccountGifts,
GetBusinessAccountStarBalance,
TransferBusinessAccountStars,
):
pass

View file

@ -1,71 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import Iterable, Union
import pyrogram
from pyrogram import raw
class DeleteBusinessMessages:
async def delete_business_messages(
self: "pyrogram.Client",
business_connection_id: str,
message_ids: Union[int, Iterable[int]]
) -> int:
"""Delete messages on behalf of a business account.
.. note::
Requires the `can_delete_sent_messages` business bot right to delete messages sent by the bot itself,
or the `can_delete_all_messages` business bot right to delete any message.
.. include:: /_includes/usable-by/bots.rst
Parameters:
business_connection_id (``str``):
Unique identifier of business connection on behalf of which to send the request.
message_ids (``int`` | Iterable of ``int``):
An iterable of message identifiers to delete (integers) or a single message id.
All messages must be from the same chat.
Returns:
``int``: Amount of affected messages
Example:
.. code-block:: python
# Delete one message
await app.delete_business_messages(connection_id, message_id)
# Delete multiple messages at once
await app.delete_business_messages(connection_id, list_of_message_ids)
"""
message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
r = await self.invoke(
raw.functions.messages.DeleteMessages(
id=message_ids,
revoke=True
),
business_connection_id=business_connection_id
)
return r.pts_count

View file

@ -1,129 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
import pyrogram
from pyrogram import raw, types
class GetBusinessAccountGifts:
async def get_business_account_gifts(
self: "pyrogram.Client",
business_connection_id: str,
exclude_unsaved: Optional[bool] = None,
exclude_saved: Optional[bool] = None,
exclude_unlimited: Optional[bool] = None,
exclude_limited: Optional[bool] = None,
exclude_upgraded: Optional[bool] = None,
sort_by_price: Optional[bool] = None,
limit: int = 0,
offset: str = "",
):
"""Return the gifts received and owned by a managed business account.
.. note::
Requires the `can_view_gifts_and_stars` business bot right.
.. include:: /_includes/usable-by/bots.rst
Parameters:
business_connection_id (``str``):
Unique identifier of business connection on behalf of which to send the request.
exclude_unsaved (``bool``, *optional*):
Pass True to exclude gifts that arent saved to the accounts profile page.
exclude_saved (``bool``, *optional*):
Pass True to exclude gifts that are saved to the accounts profile page.
exclude_unlimited (``bool``, *optional*):
Pass True to exclude gifts that can be purchased an unlimited number of times.
exclude_limited (``bool``, *optional*):
Pass True to exclude gifts that can be purchased a limited number of times.
exclude_upgraded (``bool``, *optional*):
Pass True to exclude upgraded gifts.
sort_by_price (``bool``, *optional*):
Pass True to sort results by gift price instead of send date. Sorting is applied before pagination.
offset (``str``, *optional*):
Offset of the first entry to return as received from the previous request.
limit (``int``, *optional*):
The maximum number of gifts to be returned.
Returns:
``Generator``: A generator yielding :obj:`~pyrogram.types.Gift` objects.
Example:
.. code-block:: python
async for gift in app.get_business_account_gifts(connection_id):
print(gift)
"""
current = 0
total = abs(limit) or (1 << 31) - 1
limit = min(100, total)
connection_info = await self.get_business_connection(business_connection_id)
while True:
r = await self.invoke(
raw.functions.payments.GetSavedStarGifts(
peer=await self.resolve_peer(connection_info.user.id),
offset=offset,
limit=limit,
exclude_unsaved=exclude_unsaved,
exclude_saved=exclude_saved,
exclude_unlimited=exclude_unlimited,
exclude_limited=exclude_limited,
exclude_unique=exclude_upgraded,
sort_by_value=sort_by_price
),
sleep_threshold=60,
business_connection_id=business_connection_id
)
users = {i.id: i for i in r.users}
chats = {i.id: i for i in r.chats}
user_star_gifts = [
await types.Gift._parse_saved(self, gift, users, chats)
for gift in r.gifts
]
if not user_star_gifts:
return
for gift in user_star_gifts:
yield gift
current += 1
if current >= total:
return
offset = r.next_offset
if not offset:
return

View file

@ -1,61 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional, Union
import pyrogram
from pyrogram import raw
class GetBusinessAccountStarBalance:
async def get_business_account_star_balance(
self: "pyrogram.Client",
business_connection_id: str,
) -> int:
"""Return the amount of Telegram Stars owned by a managed business account.
.. note::
Requires the `can_view_gifts_and_stars` business bot right.
.. include:: /_includes/usable-by/bots.rst
Parameters:
business_connection_id (``str``):
Unique identifier of business connection on behalf of which to send the request.
Returns:
``int``: On success, the current stars balance is returned.
Example:
.. code-block:: python
# Get stars balance
await app.get_business_account_star_balance("connection_id")
"""
connection_info = await self.get_business_connection(business_connection_id)
r = await self.invoke(
raw.functions.payments.GetStarsStatus(
peer=await self.resolve_peer(connection_info.user.id),
),
business_connection_id=business_connection_id
)
return r.balance.amount

View file

@ -16,8 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
from typing import Union, List
import pyrogram
from pyrogram import types, raw
from pyrogram import types, utils, raw
class GetBusinessConnection:

View file

@ -1,72 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
class TransferBusinessAccountStars:
async def transfer_business_account_stars(
self: "pyrogram.Client",
business_connection_id: str,
star_count: int,
) -> bool:
"""Transfers Telegram Stars from the business account balance to the bots balance.
.. note::
Requires the `can_transfer_stars` business bot right.
.. include:: /_includes/usable-by/users.rst
Parameters:
business_connection_id (``str``):
Unique identifier of the business connection.
star_count (``int`` | ``str``):
Number of Telegram Stars to transfer, 1-10000.
Returns:
``bool``: On success, True is returned.
"""
# Why telegram won't let us just use InputPeerSelf :(
if self.me:
bot_id = self.me.id
else:
bot_id = (
await self.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()]))
)[0].id
invoice = raw.types.InputInvoiceBusinessBotTransferStars(
bot=await self.resolve_peer(bot_id), stars=star_count
)
payment_form = await self.invoke(
raw.functions.payments.GetPaymentForm(invoice=invoice),
business_connection_id=business_connection_id,
)
await self.invoke(
raw.functions.payments.SendStarsForm(
form_id=payment_form.form_id,
invoice=invoice,
),
business_connection_id=business_connection_id,
)
return True

View file

@ -21,14 +21,24 @@ from .add_chat_members import AddChatMembers
from .archive_chats import ArchiveChats
from .ban_chat_member import BanChatMember
from .create_channel import CreateChannel
from .create_forum_topic import CreateForumTopic
from .create_group import CreateGroup
from .create_supergroup import CreateSupergroup
from .close_forum_topic import CloseForumTopic
from .close_general_topic import CloseGeneralTopic
from .delete_channel import DeleteChannel
from .delete_chat_photo import DeleteChatPhoto
from .delete_folder import DeleteFolder
from .delete_forum_topic import DeleteForumTopic
from .delete_supergroup import DeleteSupergroup
from .delete_user_history import DeleteUserHistory
from .edit_forum_topic import EditForumTopic
from .edit_general_topic import EditGeneralTopic
from .export_folder_link import ExportFolderLink
from .reopen_forum_topic import ReopenForumTopic
from .reopen_general_topic import ReopenGeneralTopic
from .hide_general_topic import HideGeneralTopic
from .unhide_general_topic import UnhideGeneralTopic
from .get_chat import GetChat
from .get_chat_event_log import GetChatEventLog
from .get_chat_member import GetChatMember
@ -38,6 +48,8 @@ from .get_chat_online_count import GetChatOnlineCount
from .get_dialogs import GetDialogs
from .get_dialogs_count import GetDialogsCount
from .get_folders import GetFolders
from .get_forum_topics import GetForumTopics
from .get_forum_topics_by_id import GetForumTopicsByID
from .get_send_as_chats import GetSendAsChats
from .join_chat import JoinChat
from .leave_chat import LeaveChat
@ -54,7 +66,6 @@ from .set_chat_title import SetChatTitle
from .set_chat_username import SetChatUsername
from .set_send_as_chat import SetSendAsChat
from .set_slow_mode import SetSlowMode
from .transfer_chat_ownership import TransferChatOwnership
from .unarchive_chats import UnarchiveChats
from .unban_chat_member import UnbanChatMember
from .unpin_all_chat_messages import UnpinAllChatMessages
@ -85,16 +96,28 @@ class Chats(
SetChatPermissions,
GetDialogsCount,
GetFolders,
GetForumTopics,
GetForumTopicsByID,
ArchiveChats,
UnarchiveChats,
CreateGroup,
CreateSupergroup,
CreateChannel,
CreateForumTopic,
CloseForumTopic,
CloseGeneralTopic,
AddChatMembers,
DeleteChannel,
DeleteFolder,
DeleteForumTopic,
DeleteSupergroup,
EditForumTopic,
EditGeneralTopic,
ExportFolderLink,
ReopenForumTopic,
ReopenGeneralTopic,
HideGeneralTopic,
UnhideGeneralTopic,
SetAdministratorTitle,
SetSlowMode,
DeleteUserHistory,
@ -105,7 +128,6 @@ class Chats(
GetSendAsChats,
SetSendAsChat,
SetChatProtectedContent,
TransferChatOwnership,
UpdateColor,
UpdateFolder
):

View file

@ -117,4 +117,5 @@ class BanChatMember:
{i.id: i for i in r.users},
{i.id: i for i in r.chats}
)
return True
else:
return True

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -48,7 +48,7 @@ class CloseForumTopic:
await app.close_forum_topic(chat_id, topic_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=topic_id,
closed=True

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -44,7 +44,7 @@ class CloseGeneralTopic:
await app.close_general_topic(chat_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=1,
closed=True

View file

@ -56,7 +56,7 @@ class CreateForumTopic:
await app.create_forum_topic("Topic Title")
"""
r = await self.invoke(
raw.functions.messages.CreateForumTopic(
raw.functions.channels.CreateForumTopic(
channel=await self.resolve_peer(chat_id),
title=title,
random_id=self.rnd_id(),

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -49,7 +49,7 @@ class DeleteForumTopic:
"""
try:
await self.invoke(
raw.functions.messages.DeleteTopicHistory(
raw.functions.channels.DeleteTopicHistory(
channel=await self.resolve_peer(chat_id),
top_msg_id=topic_id
)

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -56,7 +56,7 @@ class EditForumTopic:
await app.edit_forum_topic(chat_id,topic_id,"New Topic Title")
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=topic_id,
title=title,

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -48,7 +48,7 @@ class EditGeneralTopic:
await app.edit_general_topic(chat_id,"New Topic Title")
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=1,
title=title

View file

@ -25,7 +25,7 @@ class ExportFolderLink:
async def export_folder_link(
self: "pyrogram.Client",
folder_id: int
) -> "pyrogram.types.ExportedFolderLink":
) -> str:
"""Export link to a user's folder.
.. include:: /_includes/usable-by/users.rst
@ -35,7 +35,7 @@ class ExportFolderLink:
Unique identifier (int) of the target folder.
Returns:
:obj:`~pyrogram.types.ExportedFolderLink` objects.
``str``: On success, a link to the folder as string is returned.
Example:
.. code-block:: python
@ -67,4 +67,4 @@ class ExportFolderLink:
)
)
return types.ExportedFolderLink._parse(r)
return r.invite.url

View file

@ -77,7 +77,8 @@ class GetChatMember:
else:
if member.user.id == user.user_id:
return member
raise UserNotParticipant
else:
raise UserNotParticipant
elif isinstance(chat, raw.types.InputPeerChannel):
r = await self.invoke(
raw.functions.channels.GetParticipant(

View file

@ -21,7 +21,7 @@ from typing import AsyncGenerator, Optional
import pyrogram
from pyrogram import types, raw, utils
from pyrogram.errors import ChannelPrivate, PeerIdInvalid
from pyrogram.errors import ChannelPrivate
class GetDialogs:
@ -80,7 +80,7 @@ class GetDialogs:
chat_id = utils.get_peer_id(message.peer_id)
try:
messages[chat_id] = await types.Message._parse(self, message, users, chats)
except (ChannelPrivate, PeerIdInvalid):
except ChannelPrivate:
continue
dialogs = []

View file

@ -81,7 +81,7 @@ class GetFolders:
users.update({i.id: i for i in r.users})
chats.update({i.id: i for i in r.chats})
folders = types.List([types.Folder._parse(self, folder, users, chats) for folder in raw_folders])
folders = types.List(types.Folder._parse(self, folder, users, chats) for folder in raw_folders)
if not folders:
return None

View file

@ -23,16 +23,18 @@ from typing import Union, Optional, AsyncGenerator
import pyrogram
from pyrogram import raw
from pyrogram import types
from pyrogram import utils
log = logging.getLogger(__name__)
class GetForumTopicsCount:
async def get_forum_topics_count(
class GetForumTopics:
async def get_forum_topics(
self: "pyrogram.Client",
chat_id: Union[int, str]
chat_id: Union[int, str],
limit: int = 0
) -> Optional[AsyncGenerator["types.ForumTopic", None]]:
"""Get forum topics count from a chat.
"""Get one or more topic from a chat.
.. include:: /_includes/usable-by/users.rst
@ -41,14 +43,18 @@ class GetForumTopicsCount:
Unique identifier (int) or username (str) of the target chat.
You can also use chat public link in form of *t.me/<username>* (str).
limit (``int``, *optional*):
Limits the number of topics to be retrieved.
Returns:
``int``: On success, the count of forum topics is returned.
``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ForumTopic` objects is returned.
Example:
.. code-block:: python
# get all forum topics count
app.get_forum_topics_count(chat_id)
# get all forum topics
async for topic in app.get_forum_topics(chat_id):
print(topic)
Raises:
ValueError: In case of invalid arguments.
@ -56,8 +62,9 @@ class GetForumTopicsCount:
peer = await self.resolve_peer(chat_id)
rpc = raw.functions.messages.GetForumTopics(channel=peer, offset_date=0, offset_id=0, offset_topic=0, limit=0)
rpc = raw.functions.channels.GetForumTopics(channel=peer, offset_date=0, offset_id=0, offset_topic=0, limit=limit)
r = await self.invoke(rpc, sleep_threshold=-1)
return r.count
for _topic in r.topics:
yield types.ForumTopic._parse(_topic)

View file

@ -23,6 +23,7 @@ from typing import Union, List, Iterable
import pyrogram
from pyrogram import raw
from pyrogram import types
from pyrogram import utils
log = logging.getLogger(__name__)
@ -62,7 +63,7 @@ class GetForumTopicsByID:
Raises:
ValueError: In case of invalid arguments.
"""
ids, _ = (
ids, ids_type = (
(topic_ids, int) if topic_ids
else (None, None)
)
@ -76,7 +77,7 @@ class GetForumTopicsByID:
ids = list(ids) if is_iterable else [ids]
ids = [i for i in ids]
rpc = raw.functions.messages.GetForumTopicsByID(channel=peer, topics=ids)
rpc = raw.functions.channels.GetForumTopicsByID(channel=peer, topics=ids)
r = await self.invoke(rpc, sleep_threshold=-1)

View file

@ -17,6 +17,7 @@
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -43,7 +44,7 @@ class HideGeneralTopic:
await app.hide_general_topic(chat_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=1,
hidden=True

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -48,7 +48,7 @@ class ReopenForumTopic:
await app.reopen_forum_topic(chat_id, topic_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=topic_id,
closed=False

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -44,7 +44,7 @@ class ReopenGeneralTopic:
await app.reopen_general_topic(chat_id, topic_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=1,
closed=False

View file

@ -164,4 +164,5 @@ class SetChatPhoto:
{i.id: i for i in r.users},
{i.id: i for i in r.chats}
)
return True
else:
return True

View file

@ -1,83 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from typing import Union
import pyrogram
from pyrogram import raw, utils
class TransferChatOwnership:
async def transfer_chat_ownership(
self: "pyrogram.Client",
chat_id: Union[int, str],
user_id: Union[int, str],
password: str,
) -> bool:
"""Transfer the owner of a chat or channel to another user.
.. note:
Requires owner privileges.
.. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
Unique identifier for the target chat in form of a *t.me/joinchat/* link, identifier (int) or username
of the target channel/supergroup (in the format @username).
user_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the new owner.
For a contact that exists in your Telegram address book you can use his phone number (str).
password (``str``):
The 2-step verification password of the current user.
Returns:
``bool``: True on success.
Raises:
ValueError: In case of invalid parameters.
RPCError: In case of a Telegram RPC error.
Example:
.. code-block:: python
await app.transfer_chat_ownership(chat_id, user_id, "password")
"""
peer_channel = await self.resolve_peer(chat_id)
peer_user = await self.resolve_peer(user_id)
if not isinstance(peer_channel, raw.types.InputPeerChannel):
raise ValueError("The chat_id must belong to a channel/supergroup.")
if not isinstance(peer_user, raw.types.InputPeerUser):
raise ValueError("The user_id must belong to a user.")
r = await self.invoke(
raw.functions.channels.EditCreator(
channel=peer_channel,
user_id=peer_user,
password=utils.compute_password_check(
await self.invoke(raw.functions.account.GetPassword()), password
),
)
)
return bool(r)

View file

@ -15,9 +15,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw
from pyrogram import types
from typing import Union
@ -44,7 +44,7 @@ class UnhideGeneralTopic:
await app.unhide_general_topic(chat_id)
"""
await self.invoke(
raw.functions.messages.EditForumTopic(
raw.functions.channels.EditForumTopic(
channel=await self.resolve_peer(chat_id),
topic_id=1,
hidden=False

View file

@ -20,7 +20,8 @@
from typing import List, Union
import pyrogram
from pyrogram import enums, raw, types, utils
from pyrogram import raw
from pyrogram import enums
class UpdateFolder:
@ -28,7 +29,6 @@ class UpdateFolder:
self: "pyrogram.Client",
folder_id: int,
title: str,
title_entities: List["types.MessageEntity"] = None,
included_chats: Union[Union[int, str], List[Union[int, str]]] = None,
excluded_chats: Union[Union[int, str], List[Union[int, str]]] = None,
pinned_chats: Union[Union[int, str], List[Union[int, str]]] = None,
@ -41,8 +41,7 @@ class UpdateFolder:
exclude_read: bool = None,
exclude_archived: bool = None,
color: "enums.FolderColor" = None,
emoji: str = None,
parse_mode: "pyrogram.enums.ParseMode" = pyrogram.enums.ParseMode.DEFAULT
emoji: str = None
) -> bool:
"""Create or update a user's folder.
@ -55,9 +54,6 @@ class UpdateFolder:
title (``str``):
Folder title.
title_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
Entities for the folder title.
included_chats (``int`` | ``str`` | List of ``int`` or ``str``, *optional*):
Users or chats that should added in the folder
You can pass an ID (int), username (str) or phone number (str).
@ -102,9 +98,6 @@ class UpdateFolder:
Color type.
Pass :obj:`~pyrogram.enums.FolderColor` to set folder color.
parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
The parse mode to use for the title.
Returns:
``bool``: True, on success.
@ -114,8 +107,6 @@ class UpdateFolder:
# Create or update folder
app.update_folder(folder_id, title="New folder", included_chats="me")
"""
title_text, title_entities = (await utils.parse_text_entities(self, title, parse_mode, title_entities)).values()
if not isinstance(included_chats, list):
included_chats = [included_chats] if included_chats else []
if not isinstance(excluded_chats, list):
@ -128,10 +119,7 @@ class UpdateFolder:
id=folder_id,
filter=raw.types.DialogFilter(
id=folder_id,
title=raw.types.TextWithEntities(
text=title_text,
entities=title_entities or []
),
title=title,
pinned_peers=[
await self.resolve_peer(user_id)
for user_id in pinned_chats
@ -158,4 +146,4 @@ class UpdateFolder:
)
)
return bool(r)
return r

View file

@ -24,33 +24,26 @@ from pyrogram.filters import Filter
class OnError:
def on_error(
self=None,
errors=None,
group: int = 0,
) -> Callable:
def on_error(self=None, errors=None) -> Callable:
"""Decorator for handling new errors.
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
:obj:`~pyrogram.handlers.ErrorHandler`.
:obj:`~pyrogram.handlers.MessageHandler`.
Parameters:
errors (:obj:`~Exception`, *optional*):
Pass one or more errors to allow only a subset of errors to be passed
in your function.
group (``int``, *optional*):
The group identifier, defaults to 0.
"""
def decorator(func: Callable) -> Callable:
if isinstance(self, pyrogram.Client):
self.add_handler(pyrogram.handlers.ErrorHandler(func, errors), group)
self.add_handler(pyrogram.handlers.ErrorHandler(func, errors), 0)
elif isinstance(self, Filter) or self is None:
if not hasattr(func, "handlers"):
func.handlers = []
func.handlers.append((pyrogram.handlers.ErrorHandler(func, self), group))
func.handlers.append((pyrogram.handlers.ErrorHandler(func, self), 0))
return func

View file

@ -16,7 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from typing import Callable
from typing import Callable, Optional, Union
import pyrogram
from pyrogram.filters import Filter

View file

@ -1,49 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from .create_forum_topic import CreateForumTopic
from .close_forum_topic import CloseForumTopic
from .close_general_topic import CloseGeneralTopic
from .delete_forum_topic import DeleteForumTopic
from .edit_forum_topic import EditForumTopic
from .edit_general_topic import EditGeneralTopic
from .reopen_forum_topic import ReopenForumTopic
from .reopen_general_topic import ReopenGeneralTopic
from .hide_general_topic import HideGeneralTopic
from .unhide_general_topic import UnhideGeneralTopic
from .get_forum_topics import GetForumTopics
from .get_forum_topics_by_id import GetForumTopicsByID
from .get_forum_topics_count import GetForumTopicsCount
class Forums(
GetForumTopics,
GetForumTopicsByID,
GetForumTopicsCount,
CreateForumTopic,
CloseForumTopic,
CloseGeneralTopic,
DeleteForumTopic,
EditForumTopic,
EditGeneralTopic,
ReopenForumTopic,
ReopenGeneralTopic,
HideGeneralTopic,
UnhideGeneralTopic,
):
pass

View file

@ -1,116 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import logging
from typing import Union, Optional, AsyncGenerator
import pyrogram
from pyrogram import raw
from pyrogram import types
log = logging.getLogger(__name__)
async def get_chunk(
client: "pyrogram.Client",
chat_id: Union[int, str],
offset_date: int,
offset_id: int,
offset_topic: int,
limit: int
):
peer = await client.resolve_peer(chat_id)
r = await client.invoke(
raw.functions.messages.GetForumTopics(
channel=peer,
offset_date=offset_date,
offset_id=offset_id,
offset_topic=offset_topic,
limit=limit
),
sleep_threshold=-1
)
return r.topics
class GetForumTopics:
async def get_forum_topics(
self: "pyrogram.Client",
chat_id: Union[int, str],
limit: int = 0
) -> Optional[AsyncGenerator["types.ForumTopic", None]]:
"""Get forum topics from a chat.
.. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
You can also use chat public link in form of *t.me/<username>* (str).
limit (``int``, *optional*):
Limits the number of topics to be retrieved.
By default, no limit is applied and all topics are returned.
Returns:
``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ForumTopic` objects is returned.
Example:
.. code-block:: python
# get all forum topics
async for topic in app.get_forum_topics(chat_id):
print(topic)
Raises:
ValueError: In case of invalid arguments.
"""
current = 0
offset_date = 0
offset_id = 0
offset_topic = 0
total = abs(limit) or (1 << 31) - 1
chunk_limit = min(100, total)
while True:
topics = await get_chunk(
client=self,
chat_id=chat_id,
offset_date=offset_date,
offset_id=offset_id,
offset_topic=offset_topic,
limit=chunk_limit
)
if not topics:
return
last_topic = topics[-1]
offset_date = int(last_topic.date) if last_topic.date else 0
offset_id = last_topic.top_message if last_topic.top_message else 0
offset_topic = last_topic.id
for topic in topics:
yield types.ForumTopic._parse(topic)
current += 1
if current >= total:
return

View file

@ -17,10 +17,8 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from .add_task_to_todo import AddTaskToTodo
from .copy_media_group import CopyMediaGroup
from .copy_message import CopyMessage
from .delete_chat_history import DeleteChatHistory
from .delete_messages import DeleteMessages
from .delete_scheduled_messages import DeleteScheduledMessages
from .download_media import DownloadMedia
@ -53,7 +51,6 @@ from .search_global_hashtag_messages import SearchGlobalHashtagMessages
from .search_global_hashtag_messages_count import SearchGlobalHashtagMessagesCount
from .search_messages import SearchMessages
from .search_messages_count import SearchMessagesCount
from .set_todo_tasks_completion import SetTodoTasksCompletion
from .send_animation import SendAnimation
from .send_audio import SendAudio
from .send_cached_media import SendCachedMedia
@ -68,7 +65,6 @@ from .send_photo import SendPhoto
from .send_poll import SendPoll
from .send_reaction import SendReaction
from .send_sticker import SendSticker
from .send_todo import SendTodo
from .send_venue import SendVenue
from .send_video import SendVideo
from .send_video_note import SendVideoNote
@ -78,12 +74,9 @@ from .start_bot import StartBot
from .stop_poll import StopPoll
from .stream_media import StreamMedia
from .vote_poll import VotePoll
from .transcribe_audio import TranscribeAudio
from .translate_text import TranslateText
class Messages(
AddTaskToTodo,
DeleteChatHistory,
DeleteMessages,
DeleteScheduledMessages,
EditMessageCaption,
@ -97,7 +90,6 @@ class Messages(
GetMessages,
GetMessageReadParticipants,
GetScheduledMessages,
SetTodoTasksCompletion,
SendAudio,
SendChatAction,
SendContact,
@ -108,7 +100,6 @@ class Messages(
SendMessage,
SendPhoto,
SendSticker,
SendTodo,
SendVenue,
SendVideo,
SendVideoNote,
@ -142,7 +133,6 @@ class Messages(
GetDiscussionRepliesCount,
StreamMedia,
GetCustomEmojiStickers,
TranscribeAudio,
TranslateText,
StartBot
):

View file

@ -1,82 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram import raw, types, utils
from typing import Union, List
class AddTaskToTodo:
async def add_task_to_todo(
self: "pyrogram.Client",
chat_id: Union[int, str],
message_id: Union[int, str],
tasks: List["types.InputTodoTask"],
parse_mode: str = None
) -> "types.Message":
"""Add tasks to a todo list.
Parameters:
chat_id (``int`` | ``str``):
Unique identifier for the target chat or username of the target channel.
message_id (``int`` | ``str``):
Unique identifier for the target message or username of the target channel.
tasks (List of :obj:`~pyrogram.types.InputTodoTask`):
List of tasks to be added to the todo list.
parse_mode (``str``, *optional*):
The parse mode to use for formatting the text.
entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
Entities in the title of the todo list.
"""
tasks_list = []
get_message = await self.get_messages(chat_id, message_id)
if not isinstance(get_message, types.Message):
raise ValueError("The message must be a valid Message object.")
todo_list = get_message.todo
last_task_id = max((task.id for task in todo_list.tasks), default=0)
for i, task in enumerate(tasks):
task_title, task_entities = (await utils.parse_text_entities(self, task.title, parse_mode, task.entities)).values()
tasks_list.append(
raw.types.TodoItem(
id=last_task_id + i + 1,
title=raw.types.TextWithEntities(
text=task_title,
entities=task_entities or []
)
)
)
r = await self.invoke(
raw.functions.messages.AppendTodoList(
peer=await self.resolve_peer(chat_id),
msg_id=message_id,
list=tasks_list
)
)
for update in r.updates:
if isinstance(update, (raw.types.UpdateNewMessage,
raw.types.UpdateNewChannelMessage,
raw.types.UpdateNewScheduledMessage,
raw.types.UpdateBotNewBusinessMessage)):
return types.Message._parse(self, update.message, update, r.users, r.chats)

View file

@ -34,7 +34,6 @@ class CopyMediaGroup:
has_spoilers: Union[List[bool], bool] = None,
disable_notification: bool = None,
message_thread_id: int = None,
send_as: Union[int, str] = None,
reply_to_message_id: int = None,
reply_to_chat_id: Union[int, str] = None,
reply_to_story_id: int = None,
@ -45,8 +44,6 @@ class CopyMediaGroup:
schedule_date: datetime = None,
invert_media: bool = None,
protect_content: bool = None,
allow_paid_broadcast: bool = None,
message_effect_id: int = None,
) -> List["types.Message"]:
"""Copy a media group by providing one of the message ids.
@ -76,9 +73,6 @@ class CopyMediaGroup:
If a ``string`` is passed, it becomes a caption only for the first media.
If a list of ``string`` passed, each element becomes caption for each media element.
You can pass ``None`` in list to keep the original caption (see examples below).
has_spoilers (``bool``, *optional*):
Pass True if the photo needs to be covered with a spoiler animation.
disable_notification (``bool``, *optional*):
Sends the message silently.
@ -88,10 +82,6 @@ class CopyMediaGroup:
Unique identifier for the target message thread (topic) of the forum.
For supergroups only.
send_as (``int`` | ``str``):
Unique identifier (int) or username (str) of the chat or channel to send the message as.
You can use this to send the message on behalf of a chat or channel where you have appropriate permissions.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
@ -123,12 +113,6 @@ class CopyMediaGroup:
protect_content (``bool``, *optional*):
Protects the contents of the sent message from forwarding and saving
allow_paid_broadcast (``bool``, *optional*):
Pass True to allow the message to ignore regular broadcast limits for a small fee; for bots only
message_effect_id (``int`` ``64-bit``, *optional*):
Unique identifier of the message effect to be added to the message; for private chats only.
Returns:
List of :obj:`~pyrogram.types.Message`: On success, a list of copied messages is returned.
@ -180,38 +164,28 @@ class CopyMediaGroup:
**await self.parser.parse(
captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else
captions if isinstance(captions, str) and i == 0 else
message.caption if (
message.caption
and message.caption != "None"
and not isinstance(captions, str)
) else ""
)
message.caption if message.caption and message.caption != "None" and not type(
captions) is str else "")
)
)
reply_to = await utils.get_reply_to(
client=self,
reply_to_message_id=reply_to_message_id,
message_thread_id=message_thread_id,
reply_to_chat_id=reply_to_chat_id,
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
quote_offset=quote_offset,
)
r = await self.invoke(
raw.functions.messages.SendMultiMedia(
peer=await self.resolve_peer(chat_id),
multi_media=multi_media,
silent=disable_notification or None,
reply_to=reply_to,
send_as=await self.resolve_peer(send_as) if send_as else None,
reply_to=utils.get_reply_to(
reply_to_message_id=reply_to_message_id,
message_thread_id=message_thread_id,
reply_to_peer=await self.resolve_peer(reply_to_chat_id) if reply_to_chat_id else None,
reply_to_story_id=reply_to_story_id,
quote_text=quote_text,
quote_entities=quote_entities,
quote_offset=quote_offset,
),
schedule_date=utils.datetime_to_timestamp(schedule_date),
noforwards=protect_content,
invert_media=invert_media,
allow_paid_floodskip=allow_paid_broadcast,
effect=message_effect_id,
invert_media=invert_media
),
sleep_threshold=60
)
@ -228,4 +202,4 @@ class CopyMediaGroup:
users=r.users,
chats=r.chats
)
)
)

View file

@ -1,102 +0,0 @@
# Pyrofork - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
#
# This file is part of Pyrofork.
#
# Pyrofork is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrofork is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime
import logging
from typing import Union
import pyrogram
from pyrogram import raw
from pyrogram import utils
log = logging.getLogger(__name__)
class DeleteChatHistory:
async def delete_chat_history(
self: "pyrogram.Client",
chat_id: Union[int, str],
max_id: int = 0,
revoke: bool = None,
just_clear = None,
min_date: datetime = None,
max_date: datetime = None,
) -> int:
"""Delete the history of a chat.
.. include:: /_includes/usable-by/users.rst
Parameters:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
max_id (``int``, *optional*):
Maximum ID of message to delete.
revoke (``bool``, *optional*):
Deletes messages history for everyone.
Required ``True`` if using in channel.
just_clear (``bool``, *optional*):
If True, clear history for the current user, without actually removing chat.
For private and simple group chats only.
min_date (:py:obj:`~datetime.datetime`, *optional*):
Delete all messages newer than this time.
For private and simple group chats only.
max_date (:py:obj:`~datetime.datetime`, *optional*):
Delete all messages older than this time.
For private and simple group chats only.
Returns:
``int``: Amount of affected messages
Example:
.. code-block:: python
# Delete all messages in channel
await app.delete_chat_history(chat_id, revoke=True)
"""
peer = await self.resolve_peer(chat_id)
if isinstance(peer, raw.types.InputPeerChannel):
r = await self.invoke(
raw.functions.channels.DeleteHistory(
channel=raw.types.InputChannel(
channel_id=peer.channel_id,
access_hash=peer.access_hash
),
max_id=max_id,
for_everyone=revoke
)
)
else:
r = await self.invoke(
raw.functions.messages.DeleteHistory(
peer=peer,
max_id=max_id,
just_clear=just_clear,
revoke=revoke,
min_date=utils.datetime_to_timestamp(min_date),
max_date=utils.datetime_to_timestamp(max_date)
)
)
return len(r.updates[0].messages) if isinstance(peer, raw.types.InputPeerChannel) else r.pts_count

View file

@ -150,17 +150,6 @@ class DownloadMedia:
directory, file_name = os.path.split(file_name)
file_name = file_name or media_file_name or ""
# Sanitize file name
# CWE-22: Path Traversal
if file_name:
# Remove any path components, keeping only the basename
file_name = os.path.basename(file_name)
# Remove null bytes which could cause issues
file_name = file_name.replace('\x00', '')
# Handle edge cases
if not file_name or file_name in ('.', '..'):
file_name = ""
if not os.path.isabs(file_name):
directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)

Some files were not shown because too many files have changed in this diff Show more