From 565b8db3d6870fbf972fe016e12819d484435392 Mon Sep 17 00:00:00 2001 From: kalanakt Date: Sat, 27 Jan 2024 23:17:37 +0530 Subject: [PATCH] fix:pylin:autopep8:flake --- .pylintrc | 571 ++++++++++++++++++++++++ Uploader/__init__.py | 0 Uploader/button.py | 108 ++--- Uploader/callbacks.py | 8 +- Uploader/commands.py | 6 +- Uploader/dl_button.py | 65 ++- Uploader/echo.py | 30 +- Uploader/functions/display_progress.py | 46 +- Uploader/functions/help_Nekmo_ffmpeg.py | 96 ++-- Uploader/functions/help_uploadbot.py | 31 +- Uploader/functions/help_ytdl.py | 19 +- Uploader/functions/ran_text.py | 10 +- Uploader/script.py | 3 +- Uploader/thumbunali.py | 8 +- Uploader/utitles.py | 29 +- Uploader/youtube.py | 60 ++- config.py | 3 +- pyproject.toml | 6 + 18 files changed, 901 insertions(+), 198 deletions(-) create mode 100644 .pylintrc create mode 100644 Uploader/__init__.py create mode 100644 pyproject.toml diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..c73e4729 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,571 @@ +[MAIN] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Files or directories to be skipped. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. +ignore-patterns=^\.# + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + pylint.extensions.check_elif, + pylint.extensions.bad_builtin, + pylint.extensions.docparams, + pylint.extensions.for_any_all, + pylint.extensions.set_membership, + pylint.extensions.code_style, + pylint.extensions.overlapping_exceptions, + pylint.extensions.typing, + pylint.extensions.redefined_variable_type, + pylint.extensions.comparison_placement, + pylint.extensions.mccabe, + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=0 + +# 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 + +# 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= + +# Minimum supported python version +py-version = 3.7.2 + +# 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 + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# 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= + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +# confidence= + +# 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= + use-symbolic-message-instead, + useless-suppression, + +# 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= + attribute-defined-outside-init, + invalid-name, + missing-docstring, + protected-access, + too-few-public-methods, + # handled by black + format, + # We anticipate #3512 where it will become optional + fixme, + cyclic-import, + import-error, + C0116, + C0114 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). 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= + +# Activate the evaluation score. +score=yes + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + + +[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= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=6 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-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 + +# 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= + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.* + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=120 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# 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 + +# 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 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[BASIC] + +# 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= + +# 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= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,}$ + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# 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. +#class-const-rgx= + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,}$ + +# Regular expression which can overwrite the naming style set by typevar-naming-style. +#typevar-rgx= + +# Regular expression which should only match function or class names that do +# not require a docstring. Use ^(?!__init__$)_ to also check __init__. +no-docstring-rgx=__.*__ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# List of decorators that define properties, such as abc.abstractproperty. +property-classes=abc.abstractproperty + + +[TYPECHECK] + +# Regex pattern to define which classes are considered mixins if ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*MixIn + +# List of module names for which member attributes should not be checked +# (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= + +# 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=SQLObject, optparse.Values, thread._local, _thread._local + +# 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=REQUEST,acl_users,aq_parent,argparse.Namespace + +# List of decorators that create context managers from functions, such as +# contextlib.contextmanager. +contextmanager-decorators=contextlib.contextmanager + +# 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 + +# 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 + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:,pragma:,# noinspection + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file=.pyenchant_pylint_custom_dict.txt + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=2 + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=10 + +# Maximum number of locals for function / method body +max-locals=25 + +# Maximum number of return / yield for function / method body +max-returns=11 + +# Maximum number of branch for function / method body +max-branches=27 + +# Maximum number of statements in function / method body +max-statements=100 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# List of qualified class names to ignore when counting class parents (see R0901). +ignored-parents= + +# Maximum number of attributes for a class (see R0902). +max-attributes=11 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=25 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# List of regular expressions of class ancestor names to +# ignore when counting public methods (see R0903). +exclude-too-few-public-methods= + +max-complexity=10 + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp,__post_init__ + +# 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 + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# 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 + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in 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= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception + + +[TYPING] + +# Set to ``no`` if the app / library does **NOT** need to support runtime +# introspection of type annotations. If you use type annotations +# **exclusively** for type checking of an application, you're probably fine. +# For libraries, evaluate if some users what to access the type hints at +# runtime first, e.g., through ``typing.get_type_hints``. Applies to Python +# versions 3.7 - 3.9 +runtime-typing = no + + +[DEPRECATED_BUILTINS] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,input + + +[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 + + +[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 + + +[CODE_STYLE] + +# Max line length for which to sill emit suggestions. Used to prevent optional +# suggestions which would get split by a code formatter (e.g., black). Will +# default to the setting for ``max-line-length``. +#max-line-length-suggestions= diff --git a/Uploader/__init__.py b/Uploader/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Uploader/button.py b/Uploader/button.py index 35913833..91254cbe 100644 --- a/Uploader/button.py +++ b/Uploader/button.py @@ -1,5 +1,3 @@ -"""module for youtube download""" - import logging import os import json @@ -7,12 +5,14 @@ import shutil import asyncio from datetime import datetime + from Uploader.functions.display_progress import humanbytes, progress_for_pyrogram from Uploader.functions.ran_text import random_char from Uploader.script import Translation from Uploader.utitles import Mdata01, Mdata02, Mdata03 from config import Config +# Set up logging configuration logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) @@ -20,46 +20,39 @@ logging.getLogger("pyrogram").setLevel(logging.WARNING) -logging.basicConfig( - level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - async def youtube_dl_call_back(_bot, update): + # Constants + AD_STRING_TO_REPLACE = "please report this issue on https://github.com/kalanakt/All-Url-Uploader/issues" + cb_data = update.data - # youtube_dl extractors tg_send_type, youtube_dl_format, youtube_dl_ext, ranom = cb_data.split("|") print(cb_data) random1 = random_char(5) save_ytdl_json_path = ( - Config.DOWNLOAD_LOCATION + "/" + - str(update.from_user.id) + f"{ranom}" + ".json" + Config.DOWNLOAD_LOCATION + "/" + str(update.from_user.id) + f"{ranom}" + ".json" ) + try: with open(save_ytdl_json_path, "r", encoding="utf8") as f: response_json = json.load(f) except FileNotFoundError as e: - await update.message.edit(f"error: {e}") + await update.message.edit(f"Error: {e}") await update.message.delete() return False + youtube_dl_url = update.message.reply_to_message.text custom_file_name = ( - str(response_json.get("title")) + "_" + - youtube_dl_format + "." + youtube_dl_ext + str(response_json.get("title")) + "_" + youtube_dl_format + "." + youtube_dl_ext ) youtube_dl_username = None youtube_dl_password = None + if "|" in youtube_dl_url: url_parts = youtube_dl_url.split("|") if len(url_parts) == 2: - youtube_dl_url = url_parts[0] - custom_file_name = url_parts[1] + youtube_dl_url, custom_file_name = map(str.strip, url_parts) elif len(url_parts) == 4: - youtube_dl_url = url_parts[0] - custom_file_name = url_parts[1] - youtube_dl_username = url_parts[2] - youtube_dl_password = url_parts[3] + youtube_dl_url, custom_file_name, youtube_dl_username, youtube_dl_password = map(str.strip, url_parts) else: for entity in update.message.reply_to_message.entities: if entity.type == "text_link": @@ -68,17 +61,10 @@ async def youtube_dl_call_back(_bot, update): o = entity.offset length = entity.length youtube_dl_url = youtube_dl_url[o: o + length] - if youtube_dl_url is not None: - youtube_dl_url = youtube_dl_url.strip() - if custom_file_name is not None: - custom_file_name = custom_file_name.strip() - # https://stackoverflow.com/a/761825/4723940 - if youtube_dl_username is not None: - youtube_dl_username = youtube_dl_username.strip() - if youtube_dl_password is not None: - youtube_dl_password = youtube_dl_password.strip() - logger.info(youtube_dl_url) - logger.info(custom_file_name) + + # Cleaning up inputs + youtube_dl_url, custom_file_name, youtube_dl_username, youtube_dl_password = map(str.strip, [youtube_dl_url, custom_file_name, youtube_dl_username, youtube_dl_password]) + else: for entity in update.message.reply_to_message.entities: if entity.type == "text_link": @@ -87,22 +73,26 @@ async def youtube_dl_call_back(_bot, update): o = entity.offset length = entity.length youtube_dl_url = youtube_dl_url[o: o + length] + await update.message.edit_caption( caption=Translation.DOWNLOAD_START.format(custom_file_name) ) description = Translation.CUSTOM_CAPTION_UL_FILE + if "fulltitle" in response_json: description = response_json["fulltitle"][:1021] - # escape Markdown and special characters + tmp_directory_for_each_user = ( - Config.DOWNLOAD_LOCATION + "/" + - str(update.from_user.id) + f"{random1}" + Config.DOWNLOAD_LOCATION + "/" + str(update.from_user.id) + f"{random1}" ) + if not os.path.isdir(tmp_directory_for_each_user): os.makedirs(tmp_directory_for_each_user) + download_directory = f"{tmp_directory_for_each_user}/{custom_file_name}" command_to_exec = [] + if tg_send_type == "audio": command_to_exec = [ "yt-dlp", @@ -120,7 +110,6 @@ async def youtube_dl_call_back(_bot, update): download_directory, ] else: - # command_to_exec = ["yt-dlp", "-f", youtube_dl_format, "--hls-prefer-ffmpeg", "--recode-video", "mp4", "-k", youtube_dl_url, "-o", download_directory] minus_f_format = youtube_dl_format if "youtu" in youtube_dl_url: minus_f_format = f"{youtube_dl_format}+bestaudio" @@ -139,33 +128,34 @@ async def youtube_dl_call_back(_bot, update): ] if Config.HTTP_PROXY != "": - command_to_exec.append("--proxy") - command_to_exec.append(Config.HTTP_PROXY) + command_to_exec.extend(["--proxy", Config.HTTP_PROXY]) + if youtube_dl_username is not None: - command_to_exec.append("--username") - command_to_exec.append(youtube_dl_username) + command_to_exec.extend(["--username", youtube_dl_username]) + if youtube_dl_password is not None: - command_to_exec.append("--password") - command_to_exec.append(youtube_dl_password) - command_to_exec.append("--no-warnings") - # command_to_exec.append("--quiet") + command_to_exec.extend(["--password", youtube_dl_password]) + + command_to_exec.extend(["--no-warnings"]) + logger.info(command_to_exec) start = datetime.now() + process = await asyncio.create_subprocess_exec( *command_to_exec, - # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - # Wait for the subprocess to finish + stdout, stderr = await process.communicate() e_response = stderr.decode().strip() t_response = stdout.decode().strip() + logger.info(e_response) logger.info(t_response) - ad_string_to_replace = "please report this issue on https://github.com/kalanakt/All-Url-Uploader/issues" - if e_response and ad_string_to_replace in e_response: - error_message = e_response.replace(ad_string_to_replace, "") + + if e_response and AD_STRING_TO_REPLACE in e_response: + error_message = e_response.replace(AD_STRING_TO_REPLACE, "") await update.message.edit_caption(text=error_message) return False @@ -179,17 +169,15 @@ async def youtube_dl_call_back(_bot, update): end_one = datetime.now() time_taken_for_download = (end_one - start).seconds file_size = Config.TG_MAX_FILE_SIZE + 1 + try: file_size = os.stat(download_directory).st_size except FileNotFoundError: - download_directory = os.path.splitext( - download_directory)[0] + "." + "mkv" - # https://stackoverflow.com/a/678242/4723940 + download_directory = os.path.splitext(download_directory)[0] + "." + "mkv" file_size = os.stat(download_directory).st_size download_location = f"{Config.DOWNLOAD_LOCATION}/{update.from_user.id}.jpg" - thumb = download_location if os.path.isfile( - download_location) else None + thumb = download_location if os.path.isfile(download_location) else None if file_size > Config.TG_MAX_FILE_SIZE: await update.message.edit_caption( @@ -201,11 +189,12 @@ async def youtube_dl_call_back(_bot, update): await update.message.edit_caption( caption=Translation.UPLOAD_START.format(custom_file_name) ) + start_time = time.time() + if tg_send_type == "video": width, height, duration = await Mdata01(download_directory) await update.message.reply_video( - # chat_id=update.message.chat.id, video=download_directory, caption=description, duration=duration, @@ -213,7 +202,6 @@ async def youtube_dl_call_back(_bot, update): height=height, supports_streaming=True, thumb=thumb, - # reply_to_message_id=update.id, progress=progress_for_pyrogram, progress_args=( Translation.UPLOAD_START, @@ -224,12 +212,10 @@ async def youtube_dl_call_back(_bot, update): elif tg_send_type == "audio": duration = await Mdata03(download_directory) await update.message.reply_audio( - # chat_id=update.message.chat.id, audio=download_directory, caption=description, duration=duration, thumb=thumb, - # reply_to_message_id=update.id, progress=progress_for_pyrogram, progress_args=( Translation.UPLOAD_START, @@ -240,12 +226,10 @@ async def youtube_dl_call_back(_bot, update): elif tg_send_type == "vm": width, duration = await Mdata02(download_directory) await update.message.reply_video_note( - # chat_id=update.message.chat.id, video_note=download_directory, duration=duration, length=width, thumb=thumb, - # reply_to_message_id=update.id, progress=progress_for_pyrogram, progress_args=( Translation.UPLOAD_START, @@ -255,11 +239,8 @@ async def youtube_dl_call_back(_bot, update): ) else: await update.message.reply_document( - # chat_id=update.message.chat.id, document=download_directory, caption=description, - # parse_mode=enums.ParseMode.HTML, - # reply_to_message_id=update.id, thumb=thumb, progress=progress_for_pyrogram, progress_args=( @@ -273,11 +254,12 @@ async def youtube_dl_call_back(_bot, update): time_taken_for_upload = (end_two - end_one).seconds shutil.rmtree(tmp_directory_for_each_user) + await update.message.edit_caption( caption=Translation.AFTER_SUCCESSFUL_UPLOAD_MSG_WITH_TS.format( time_taken_for_download, time_taken_for_upload ) ) - logger.info("Downloaded in: : %s", str(time_taken_for_download)) + logger.info("Downloaded in: %s", str(time_taken_for_download)) logger.info("Uploaded in: %s", str(time_taken_for_upload)) diff --git a/Uploader/callbacks.py b/Uploader/callbacks.py index f1dcd431..d3fe2948 100644 --- a/Uploader/callbacks.py +++ b/Uploader/callbacks.py @@ -1,9 +1,12 @@ +"""Module for handling Pyrogram callback queries""" + +import logging +from pyrogram import Client from Uploader.dl_button import ddl_call_back from Uploader.button import youtube_dl_call_back from Uploader.script import Translation -from pyrogram import Client -import logging +# Set up logging configuration logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) @@ -36,6 +39,5 @@ async def button(bot, update): await youtube_dl_call_back(bot, update) elif "=" in update.data: await ddl_call_back(bot, update) - else: await update.message.delete() diff --git a/Uploader/commands.py b/Uploader/commands.py index 74d119ae..ae739c9b 100644 --- a/Uploader/commands.py +++ b/Uploader/commands.py @@ -6,7 +6,7 @@ @Client.on_message( filters.command("start") & filters.private, ) -async def start_bot(_, m: Message): +async def start_bot(_bot, m: Message): return await m.reply_text( Translation.START_TEXT.format(m.from_user.first_name), reply_markup=Translation.START_BUTTONS, @@ -18,7 +18,7 @@ async def start_bot(_, m: Message): @Client.on_message( filters.command("help") & filters.private, ) -async def help_bot(_, m: Message): +async def help_bot(_bot, m: Message): return await m.reply_text( Translation.HELP_TEXT, reply_markup=Translation.HELP_BUTTONS, @@ -29,7 +29,7 @@ async def help_bot(_, m: Message): @Client.on_message( filters.command("about") & filters.private, ) -async def aboutme(_, m: Message): +async def aboutme(_bot, m: Message): return await m.reply_text( Translation.ABOUT_TEXT, reply_markup=Translation.ABOUT_BUTTONS, diff --git a/Uploader/dl_button.py b/Uploader/dl_button.py index 3ddc65f0..e46a431f 100644 --- a/Uploader/dl_button.py +++ b/Uploader/dl_button.py @@ -1,17 +1,18 @@ +"""Module for handling download callback and related functions""" + import os import time import aiohttp import asyncio - +import logging from datetime import datetime - from Uploader.functions.display_progress import ( progress_for_pyrogram, humanbytes, TimeFormatter, ) -from Uploader.utitles import * from Uploader.script import Translation +from Uploader.utitles import Mdata01, Mdata02, Mdata03 from config import Config logging.basicConfig( @@ -21,11 +22,12 @@ logging.getLogger("pyrogram").setLevel(logging.WARNING) -async def ddl_call_back(bot, update): # sourcery skip: low-code-quality +async def ddl_call_back(bot, update): cb_data = update.data tg_send_type, youtube_dl_format, youtube_dl_ext = cb_data.split("=") youtube_dl_url = update.message.reply_to_message.text custom_file_name = os.path.basename(youtube_dl_url) + if " " in youtube_dl_url: url_parts = youtube_dl_url.split(" * ") if len(url_parts) == 2: @@ -37,8 +39,8 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality youtube_dl_url = entity.url elif entity.type == "url": o = entity.offset - l = entity.length - youtube_dl_url = youtube_dl_url[o: o + l] + length = entity.length + youtube_dl_url = youtube_dl_url[o: o + length] if youtube_dl_url is not None: youtube_dl_url = youtube_dl_url.strip() if custom_file_name is not None: @@ -49,28 +51,32 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality youtube_dl_url = entity.url elif entity.type == "url": o = entity.offset - l = entity.length - youtube_dl_url = youtube_dl_url[o: o + l] + length = entity.length + youtube_dl_url = youtube_dl_url[o: o + length] + description = custom_file_name + if f".{youtube_dl_ext}" not in custom_file_name: custom_file_name += f".{youtube_dl_ext}" + logger.info(youtube_dl_url) logger.info(custom_file_name) + start = datetime.now() + await bot.edit_message_text( text=Translation.DOWNLOAD_START.format(custom_file_name), chat_id=update.message.chat.id, message_id=update.message.id, ) - tmp_directory_for_each_user = ( - f"{Config.DOWNLOAD_LOCATION}/{str(update.from_user.id)}" - ) + tmp_directory_for_each_user = f"{Config.DOWNLOAD_LOCATION}/{str(update.from_user.id)}" if not os.path.isdir(tmp_directory_for_each_user): os.makedirs(tmp_directory_for_each_user) + download_directory = f"{tmp_directory_for_each_user}/{custom_file_name}" - command_to_exec = [] + async with aiohttp.ClientSession() as session: c_time = time.time() try: @@ -90,19 +96,18 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality chat_id=update.message.chat.id, message_id=update.message.id, ) - return False + if os.path.exists(download_directory): - save_ytdl_json_path = ( - f"{Config.DOWNLOAD_LOCATION}/{str(update.message.chat.id)}.json" - ) + save_ytdl_json_path = f"{Config.DOWNLOAD_LOCATION}/{str(update.message.chat.id)}.json" download_location = f"{Config.DOWNLOAD_LOCATION}/{update.from_user.id}.jpg" - thumb = download_location if os.path.isfile( - download_location) else None + thumb = download_location if os.path.isfile(download_location) else None if os.path.exists(save_ytdl_json_path): os.remove(save_ytdl_json_path) + end_one = datetime.now() + await bot.edit_message_text( text=Translation.UPLOAD_START, chat_id=update.message.chat.id, @@ -110,11 +115,13 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality ) file_size = Config.TG_MAX_FILE_SIZE + 1 + try: file_size = os.stat(download_directory).st_size - except FileNotFoundError as exc: + except FileNotFoundError: download_directory = f"{os.path.splitext(download_directory)[0]}.mkv" file_size = os.stat(download_directory).st_size + if file_size > Config.TG_MAX_FILE_SIZE: await bot.edit_message_text( chat_id=update.message.chat.id, @@ -124,6 +131,7 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality else: start_time = time.time() + if tg_send_type == "video": width, height, duration = await Mdata01(download_directory) await bot.send_video( @@ -194,12 +202,15 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality ) end_two = datetime.now() + try: os.remove(download_directory) except Exception: pass + time_taken_for_download = (end_one - start).seconds time_taken_for_upload = (end_two - end_one).seconds + await bot.edit_message_text( text=Translation.AFTER_SUCCESSFUL_UPLOAD_MSG_WITH_TS.format( time_taken_for_download, time_taken_for_upload @@ -223,34 +234,41 @@ async def ddl_call_back(bot, update): # sourcery skip: low-code-quality async def download_coroutine(bot, session, url, file_name, chat_id, message_id, start): downloaded = 0 display_message = "" + async with session.get(url, timeout=Config.PROCESS_MAX_TIMEOUT) as response: total_length = int(response.headers["Content-Length"]) content_type = response.headers["Content-Type"] + if "text" in content_type and total_length < 500: return await response.release() + with open(file_name, "wb") as f_handle: while True: chunk = await response.content.read(Config.CHUNK_SIZE) + if not chunk: break + f_handle.write(chunk) downloaded += Config.CHUNK_SIZE now = time.time() diff = now - start + if round(diff % 5.0) == 0 or downloaded == total_length: percentage = downloaded * 100 / total_length speed = downloaded / diff elapsed_time = round(diff) * 1000 - time_to_completion = ( - round((total_length - downloaded) / speed) * 1000 - ) + time_to_completion = round((total_length - downloaded) / speed) * 1000 estimated_total_time = elapsed_time + time_to_completion + try: current_message = """**Download Status** +Percentage : {} URL: {} File Size: {} Downloaded: {} ETA: {}""".format( + percentage, url, humanbytes(total_length), humanbytes(downloaded), @@ -261,7 +279,10 @@ async def download_coroutine(bot, session, url, file_name, chat_id, message_id, await bot.edit_message_text( chat_id, message_id, text=current_message ) + display_message = current_message + except Exception as e: logger.info(str(e)) + return await response.release() diff --git a/Uploader/echo.py b/Uploader/echo.py index b896cfd8..5f665573 100644 --- a/Uploader/echo.py +++ b/Uploader/echo.py @@ -33,10 +33,8 @@ async def echo(bot, update): reply_markup=InlineKeyboardMarkup( [ [ - InlineKeyboardButton( - "Audio 🎵", callback_data="ytdl_audio"), - InlineKeyboardButton( - "Video 🎬", callback_data="ytdl_video"), + InlineKeyboardButton("Audio 🎵", callback_data="ytdl_audio"), + InlineKeyboardButton("Video 🎬", callback_data="ytdl_video"), ] ] ), @@ -91,8 +89,7 @@ async def echo(bot, update): Config.HTTP_PROXY, ] else: - command_to_exec = ["yt-dlp", "--no-warnings", - "--allow-dynamic-mpd", "-j", url] + command_to_exec = ["yt-dlp", "--no-warnings", "--allow-dynamic-mpd", "-j", url] if youtube_dl_username is not None: command_to_exec.append("--username") command_to_exec.append(youtube_dl_username) @@ -202,12 +199,9 @@ async def echo(bot, update): else: size = 0 - cb_string_video = "{}|{}|{}|{}".format( - "video", format_id, format_ext, randem - ) - cb_string_file = "{}|{}|{}|{}".format( - "file", format_id, format_ext, randem - ) + cb_string_video = f"video |{format_id}|{format_ext}|{randem}" + cb_string_file = f"fille |{format_id}|{format_ext}|{randem}" + if format_string is not None and not ("audio only" in format_string): ikeyboard = [ InlineKeyboardButton( @@ -231,12 +225,9 @@ async def echo(bot, update): ] inline_keyboard.append(ikeyboard) if duration is not None: - cb_string_64 = "{}|{}|{}|{}".format( - "audio", "64k", "mp3", randem) - cb_string_128 = "{}|{}|{}|{}".format( - "audio", "128k", "mp3", randem) - cb_string = "{}|{}|{}|{}".format( - "audio", "320k", "mp3", randem) + cb_string_64 = "{}|{}|{}|{}".format("audio", "64k", "mp3", randem) + cb_string_128 = "{}|{}|{}|{}".format("audio", "128k", "mp3", randem) + cb_string = "{}|{}|{}|{}".format("audio", "320k", "mp3", randem) inline_keyboard.append( [ InlineKeyboardButton( @@ -263,8 +254,7 @@ async def echo(bot, update): else: format_id = response_json["format_id"] format_ext = response_json["ext"] - cb_string_file = "{}|{}|{}|{}".format( - "file", format_id, format_ext, randem) + cb_string_file = "{}|{}|{}|{}".format("file", format_id, format_ext, randem) cb_string_video = "{}|{}|{}|{}".format( "video", format_id, format_ext, randem ) diff --git a/Uploader/functions/display_progress.py b/Uploader/functions/display_progress.py index be63375e..08ad3114 100644 --- a/Uploader/functions/display_progress.py +++ b/Uploader/functions/display_progress.py @@ -10,10 +10,22 @@ async def progress_for_pyrogram(current, total, ud_type, message, start): + """ + Display progress for a Pyrogram file upload or download. + + Parameters: + - current (int): Current progress value. + - total (int): Total value (completion point). + - ud_type (str): Type of upload/download (e.g., "Uploading", "Downloading"). + - message: The Pyrogram message to edit. + - start: The start time of the operation. + + Returns: + None + """ now = time.time() diff = now - start if round(diff % 10.00) == 0 or current == total: - # if round(current / total * 100, 0) % 5 == 0: percentage = current * 100 / total speed = current / diff elapsed_time = round(diff) * 1000 @@ -33,13 +45,12 @@ async def progress_for_pyrogram(current, total, ud_type, message, start): humanbytes(current), humanbytes(total), humanbytes(speed), - # elapsed_time if elapsed_time != '' else "0 s", estimated_total_time if estimated_total_time != "" else "0 s", ) try: await message.edit(text=f"{ud_type}\n {tmp}") except Exception as e: - logger.info(f"Error {e}") + logger.info("Error %s", e) return @@ -47,6 +58,15 @@ async def progress_for_pyrogram(current, total, ud_type, message, start): def huanbytes(size_in_bytes) -> str: + """ + Convert size in bytes to human-readable format. + + Parameters: + - size_in_bytes (int): Size in bytes. + + Returns: + str: Human-readable size. + """ if size_in_bytes is None: return "0B" index = 0 @@ -60,8 +80,15 @@ def huanbytes(size_in_bytes) -> str: def humanbytes(size): - # https://stackoverflow.com/a/49361727/4723940 - # 2**10 = 1024 + """ + Convert size to human-readable format. + + Parameters: + - size (int): Size in bytes. + + Returns: + str: Human-readable size. + """ if not size: return "" power = 2**10 @@ -74,6 +101,15 @@ def humanbytes(size): def TimeFormatter(milliseconds: int) -> str: + """ + Format time in milliseconds to a human-readable string. + + Parameters: + - milliseconds (int): Time in milliseconds. + + Returns: + str: Formatted time string. + """ seconds, milliseconds = divmod(milliseconds, 1000) minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) diff --git a/Uploader/functions/help_Nekmo_ffmpeg.py b/Uploader/functions/help_Nekmo_ffmpeg.py index 8e6fffb4..af3b99b7 100644 --- a/Uploader/functions/help_Nekmo_ffmpeg.py +++ b/Uploader/functions/help_Nekmo_ffmpeg.py @@ -12,10 +12,21 @@ async def place_water_mark(input_file, output_file, water_mark_file): + """ + Place a watermark on the input file and save the result to the output file. + + Parameters: + - input_file (str): Path to the input file. + - output_file (str): Path to save the output file. + - water_mark_file (str): Path to the watermark file. + + Returns: + str: Path to the watermarked output file. + """ watermarked_file = f"{output_file}.watermark.png" metadata = extractMetadata(createParser(input_file)) width = metadata.get("width") - # https://stackoverflow.com/a/34547184/4723940 + # Command to shrink the watermark file shrink_watermark_file_genertor_command = [ "ffmpeg", "-i", @@ -26,17 +37,16 @@ async def place_water_mark(input_file, output_file, water_mark_file): watermarked_file, ] - # print(shrink_watermark_file_genertor_command) process = await asyncio.create_subprocess_exec( *shrink_watermark_file_genertor_command, - # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - # Wait for the subprocess to finish stdout, stderr = await process.communicate() - e_response = stderr.decode().strip() - t_response = stdout.decode().strip() + stderr.decode().strip() + stdout.decode().strip() + + # Command to overlay the watermark on the input file commands_to_execute = [ "ffmpeg", "-i", @@ -44,29 +54,32 @@ async def place_water_mark(input_file, output_file, water_mark_file): "-i", watermarked_file, "-filter_complex", - # https://stackoverflow.com/a/16235519 - # "\"[0:0] scale=400:225 [wm]; [wm][1:0] overlay=305:0 [out]\"", - # "-map \"[out]\" -b:v 896k -r 20 -an ", '"overlay=(main_w-overlay_w):(main_h-overlay_h)"', - # "-vf \"drawtext=text='@FFMovingPictureExpertGroupBOT':x=W-(W/2):y=H-(H/2):fontfile=" + Config.FONT_FILE + ":fontsize=12:fontcolor=white:shadowcolor=black:shadowx=5:shadowy=5\"", output_file, ] - # print(commands_to_execute) process = await asyncio.create_subprocess_exec( *commands_to_execute, - # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - # Wait for the subprocess to finish stdout, stderr = await process.communicate() - e_response = stderr.decode().strip() - t_response = stdout.decode().strip() + stderr.decode().strip() + stdout.decode().strip() return output_file async def take_screen_shot(video_file, output_directory, ttl): - # https://stackoverflow.com/a/13891070/4723940 + """ + Take a screenshot from a video file at a specified time and save it to the output directory. + + Parameters: + - video_file (str): Path to the video file. + - output_directory (str): Directory to save the screenshot. + - ttl (int): Time to take the screenshot in seconds. + + Returns: + str: Path to the saved screenshot file. + """ out_put_file_name = output_directory + "/" + str(time.time()) + ".jpg" file_genertor_command = [ "ffmpeg", @@ -78,27 +91,31 @@ async def take_screen_shot(video_file, output_directory, ttl): "1", out_put_file_name, ] - # width = "90" process = await asyncio.create_subprocess_exec( *file_genertor_command, - # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - # Wait for the subprocess to finish stdout, stderr = await process.communicate() - e_response = stderr.decode().strip() - t_response = stdout.decode().strip() + stderr.decode().strip() + stdout.decode().strip() return out_put_file_name if os.path.lexists(out_put_file_name) else None -# https://github.com/Nekmo/telegram-upload/blob/master/telegram_upload/video.py#L26 +async def cult_small_video(video_file, output_directory, start_time, end_time): + """ + Cut a small portion of a video file and save it to the output directory. + Parameters: + - video_file (str): Path to the video file. + - output_directory (str): Directory to save the cut video. + - start_time (int): Start time of the cut in seconds. + - end_time (int): End time of the cut in seconds. -async def cult_small_video(video_file, output_directory, start_time, end_time): - # https://stackoverflow.com/a/13891070/4723940 - out_put_file_name = output_directory + \ - "/" + str(round(time.time())) + ".mp4" + Returns: + str: Path to the saved cut video file. + """ + out_put_file_name = output_directory + "/" + str(round(time.time())) + ".mp4" file_genertor_command = [ "ffmpeg", "-i", @@ -115,20 +132,32 @@ async def cult_small_video(video_file, output_directory, start_time, end_time): ] process = await asyncio.create_subprocess_exec( *file_genertor_command, - # stdout must a pipe to be accessible as process.stdout stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) - # Wait for the subprocess to finish stdout, stderr = await process.communicate() - e_response = stderr.decode().strip() - t_response = stdout.decode().strip() + stderr.decode().strip() + stdout.decode().strip() return out_put_file_name if os.path.lexists(out_put_file_name) else None async def generate_screen_shots( video_file, output_directory, is_watermarkable, wf, min_duration, no_of_photos ): + """ + Generate screen shots from a video file and optionally apply a watermark. + + Parameters: + - video_file (str): Path to the video file. + - output_directory (str): Directory to save the screen shots. + - is_watermarkable (bool): Whether to apply a watermark. + - wf (str): Path to the watermark file. + - min_duration (int): Minimum duration of the video to generate screen shots. + - no_of_photos (int): Number of screen shots to generate. + + Returns: + List[str]: List of paths to the generated screen shots. + """ metadata = extractMetadata(createParser(video_file)) duration = 0 if metadata is not None and metadata.has("duration"): @@ -144,8 +173,7 @@ async def generate_screen_shots( ss_img = await place_water_mark( ss_img, f"{output_directory}/{str(time.time())}.jpg", wf ) - images.append(ss_img) - return images - else: - return None + return images + + return None diff --git a/Uploader/functions/help_uploadbot.py b/Uploader/functions/help_uploadbot.py index 2b4e86cd..2de6db48 100644 --- a/Uploader/functions/help_uploadbot.py +++ b/Uploader/functions/help_uploadbot.py @@ -12,24 +12,50 @@ def DetectFileSize(url): - r = requests.get(url, allow_redirects=True, stream=True) + """ + Detect the file size of a remote file by sending a HEAD request. + + Parameters: + - url (str): URL of the remote file. + + Returns: + int: Size of the file in bytes. + """ + r = requests.head(url, allow_redirects=True) return int(r.headers.get("content-length", 0)) def DownLoadFile(url, file_name, chunk_size, client, ud_type, message_id, chat_id): + """ + Download a file from a given URL and display the download progress. + + Parameters: + - url (str): URL of the file to be downloaded. + - file_name (str): Path to save the downloaded file. + - chunk_size (int): Size of each download chunk. + - client: Pyrogram client (optional). + - ud_type (str): Type of the download (e.g., "File", "Video"). + - message_id: ID of the message to update the download progress. + - chat_id: ID of the chat to update the download progress. + + Returns: + str: Path to the downloaded file. + """ if os.path.exists(file_name): os.remove(file_name) if not url: return file_name + r = requests.get(url, allow_redirects=True, stream=True) - # https://stackoverflow.com/a/47342052/4723940 total_size = int(r.headers.get("content-length", 0)) downloaded_size = 0 + with open(file_name, "wb") as fd: for chunk in r.iter_content(chunk_size=chunk_size): if chunk: fd.write(chunk) downloaded_size += chunk_size + if client is not None and ((total_size // downloaded_size) % 5) == 0: time.sleep(0.3) try: @@ -38,7 +64,6 @@ def DownLoadFile(url, file_name, chunk_size, client, ud_type, message_id, chat_i message_id, text=f"{ud_type}: {humanbytes(downloaded_size)} of {humanbytes(total_size)}", ) - except Exception as e: logger.info(f"Error: {e}") return diff --git a/Uploader/functions/help_ytdl.py b/Uploader/functions/help_ytdl.py index bdcdc8ad..852d8258 100644 --- a/Uploader/functions/help_ytdl.py +++ b/Uploader/functions/help_ytdl.py @@ -1,6 +1,5 @@ import os import logging - from urllib.parse import urlparse logging.basicConfig( @@ -10,12 +9,30 @@ def get_file_extension_from_url(url): + """ + Get the file extension from a URL. + + Parameters: + - url (str): URL of the file. + + Returns: + str: File extension. + """ url_path = urlparse(url).path basename = os.path.basename(url_path) return basename.split(".")[-1] def get_resolution(info_dict): + """ + Get the width and height of a video from its info dictionary. + + Parameters: + - info_dict (dict): Dictionary containing information about the video. + + Returns: + tuple: Width and height of the video. + """ width = 0 height = 0 diff --git a/Uploader/functions/ran_text.py b/Uploader/functions/ran_text.py index e5c8c89a..061807be 100644 --- a/Uploader/functions/ran_text.py +++ b/Uploader/functions/ran_text.py @@ -3,7 +3,13 @@ def random_char(y): - return "".join(random.choice(string.ascii_letters) for _ in range(y)) + """ + Generate a random string of specified length. + Parameters: + - y (int): Length of the random string. -ran = random_char(5) + Returns: + str: Random string. + """ + return "".join(random.choice(string.ascii_letters) for _ in range(y)) \ No newline at end of file diff --git a/Uploader/script.py b/Uploader/script.py index 51f8d5a8..0d1888f0 100644 --- a/Uploader/script.py +++ b/Uploader/script.py @@ -1,8 +1,7 @@ from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton -class Translation(object): - +class Translation(): START_TEXT = """ Hi {} diff --git a/Uploader/thumbunali.py b/Uploader/thumbunali.py index edc1930a..32b65d6f 100644 --- a/Uploader/thumbunali.py +++ b/Uploader/thumbunali.py @@ -1,12 +1,10 @@ import os - from pyrogram import Client, filters - from config import Config @Client.on_message(filters.photo & filters.incoming & filters.private) -async def save_photo(bot, message): +async def save_photo(_bot, message): download_location = f"{Config.DOWNLOAD_LOCATION}/{message.from_user.id}.jpg" await message.download(file_name=download_location) @@ -14,7 +12,7 @@ async def save_photo(bot, message): @Client.on_message(filters.command("thumb") & filters.incoming & filters.private) -async def send_photo(bot, message): +async def send_photo(_bot, message): download_location = f"{Config.DOWNLOAD_LOCATION}/{message.from_user.id}.jpg" if os.path.isfile(download_location): @@ -29,7 +27,7 @@ async def send_photo(bot, message): @Client.on_message(filters.command("delthumb") & filters.incoming & filters.private) -async def delete_photo(bot, message): +async def delete_photo(_bot, message): download_location = f"{Config.DOWNLOAD_LOCATION}/{message.from_user.id}.jpg" if os.path.isfile(download_location): os.remove(download_location) diff --git a/Uploader/utitles.py b/Uploader/utitles.py index 63c552e0..f8ea260f 100644 --- a/Uploader/utitles.py +++ b/Uploader/utitles.py @@ -1,9 +1,7 @@ import logging - from hachoir.parser import createParser from hachoir.metadata import extractMetadata - logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) @@ -12,6 +10,15 @@ async def Mdata01(download_directory): + """ + Extract metadata information for video files. + + Parameters: + - download_directory (str): The path to the video file. + + Returns: + Tuple[int, int, int]: Tuple containing width, height, and duration. + """ width = 0 height = 0 duration = 0 @@ -28,6 +35,15 @@ async def Mdata01(download_directory): async def Mdata02(download_directory): + """ + Extract metadata information for video files. + + Parameters: + - download_directory (str): The path to the video file. + + Returns: + Tuple[int, int]: Tuple containing width and duration. + """ width = 0 duration = 0 metadata = extractMetadata(createParser(download_directory)) @@ -41,6 +57,15 @@ async def Mdata02(download_directory): async def Mdata03(download_directory): + """ + Extract metadata information for audio files. + + Parameters: + - download_directory (str): The path to the audio file. + + Returns: + int: Duration of the audio file. + """ metadata = extractMetadata(createParser(download_directory)) return ( metadata.get("duration").seconds diff --git a/Uploader/youtube.py b/Uploader/youtube.py index f3574ad0..bfed65c6 100644 --- a/Uploader/youtube.py +++ b/Uploader/youtube.py @@ -31,8 +31,7 @@ async def callback_query_ytdl_audio(_, callback_query): ydl.process_info(info_dict) # upload audio_file = ydl.prepare_filename(info_dict) - task = asyncio.create_task( - send_audio(message, info_dict, audio_file)) + task = asyncio.create_task(send_audio(message, info_dict, audio_file)) while not task.done(): await asyncio.sleep(3) await message.reply_chat_action(enums.ChatAction.UPLOAD_DOCUMENT) @@ -43,34 +42,34 @@ async def callback_query_ytdl_audio(_, callback_query): await callback_query.message.reply_to_message.delete() await callback_query.message.delete() - async def send_audio(message: Message, info_dict, audio_file): - basename = audio_file.rsplit(".", 1)[-2] - if info_dict["ext"] == "webm": - audio_file_weba = f"{basename}.weba" - os.rename(audio_file, audio_file_weba) - audio_file = audio_file_weba - thumbnail_url = info_dict["thumbnail"] - thumbnail_file = f"{basename}.{get_file_extension_from_url(thumbnail_url)}" - download_location = f"{Config.DOWNLOAD_LOCATION}/{message.from_user.id}.jpg" - thumb = download_location if os.path.isfile( - download_location) else None - webpage_url = info_dict["webpage_url"] - title = info_dict["title"] or "" - caption = f'{title}' - duration = int(float(info_dict["duration"])) - performer = info_dict["uploader"] or "" - await message.reply_audio( - audio_file, - caption=caption, - duration=duration, - performer=performer, - title=title, - parse_mode=enums.ParseMode.HTML, - thumb=thumb, - ) - os.remove(audio_file) - os.remove(thumbnail_file) +async def send_audio(message: Message, info_dict, audio_file): + basename = audio_file.rsplit(".", 1)[-2] + if info_dict["ext"] == "webm": + audio_file_weba = f"{basename}.weba" + os.rename(audio_file, audio_file_weba) + audio_file = audio_file_weba + thumbnail_url = info_dict["thumbnail"] + thumbnail_file = f"{basename}.{get_file_extension_from_url(thumbnail_url)}" + download_location = f"{Config.DOWNLOAD_LOCATION}/{message.from_user.id}.jpg" + thumb = download_location if os.path.isfile(download_location) else None + webpage_url = info_dict["webpage_url"] + title = info_dict["title"] or "" + caption = f'{title}' + duration = int(float(info_dict["duration"])) + performer = info_dict["uploader"] or "" + await message.reply_audio( + audio_file, + caption=caption, + duration=duration, + performer=performer, + title=title, + parse_mode=enums.ParseMode.HTML, + thumb=thumb, + ) + + os.remove(audio_file) + os.remove(thumbnail_file) async def send_video(message: Message, info_dict, video_file): @@ -117,8 +116,7 @@ async def callback_query_ytdl_video(_, callback_query): ydl.process_info(info_dict) # upload video_file = ydl.prepare_filename(info_dict) - task = asyncio.create_task( - send_video(message, info_dict, video_file)) + task = asyncio.create_task(send_video(message, info_dict, video_file)) while not task.done(): await asyncio.sleep(3) await message.reply_chat_action(enums.ChatAction.UPLOAD_DOCUMENT) diff --git a/config.py b/config.py index 68230428..05ea99ef 100644 --- a/config.py +++ b/config.py @@ -37,6 +37,5 @@ class Config(object): OWNER_ID = os.environ.get("OWNER_ID") ADL_BOT_RQ = {} - AUTH_USERS = list({int(x) - for x in os.environ.get("AUTH_USERS", "0").split()}) + AUTH_USERS = list({int(x) for x in os.environ.get("AUTH_USERS", "0").split()}) AUTH_USERS.append(OWNER_ID) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..ab77cc74 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[tool.autopep8] +max_line_length = 150 +ignore = "E402,W503,C0116,C0114" +in-place = true +recursive = true +aggressive = 3