From ffe4b74c92debfc97f0bd3d0f41871e9b6bcbbc1 Mon Sep 17 00:00:00 2001 From: Denis Rozhnovskiy Date: Tue, 26 Nov 2024 16:13:51 +0500 Subject: [PATCH] chore: reformat code and version bump to 0.2.0 --- .run/Dockerfile.run.xml | 84 ++--- docs/docker.md | 331 ++++++------------ pyproject.toml | 2 +- pytmbot/adapters/docker/_adapter.py | 8 +- pytmbot/adapters/docker/container_manager.py | 14 +- pytmbot/adapters/docker/containers_info.py | 4 +- pytmbot/adapters/docker/images_info.py | 2 +- pytmbot/adapters/docker/updates.py | 4 +- pytmbot/adapters/docker/utils.py | 2 +- pytmbot/db/influxdb_interface.py | 51 ++- pytmbot/exceptions.py | 4 +- pytmbot/globals.py | 2 +- .../auth_processing/auth_processing.py | 6 +- .../auth_processing/qrcode_processing.py | 8 +- .../auth_processing/twofa_processing.py | 6 +- pytmbot/handlers/bot_handlers/about.py | 2 +- pytmbot/handlers/bot_handlers/echo.py | 6 +- pytmbot/handlers/bot_handlers/navigation.py | 2 +- pytmbot/handlers/bot_handlers/plugins.py | 8 +- pytmbot/handlers/bot_handlers/start.py | 2 +- pytmbot/handlers/bot_handlers/updates.py | 10 +- .../handlers/docker_handlers/containers.py | 2 +- pytmbot/handlers/docker_handlers/docker.py | 2 +- pytmbot/handlers/docker_handlers/images.py | 6 +- .../docker_handlers/inline/container_info.py | 16 +- .../handlers/docker_handlers/inline/logs.py | 2 +- .../handlers/docker_handlers/inline/manage.py | 8 +- .../docker_handlers/inline/manage_action.py | 26 +- pytmbot/handlers/handler_manager.py | 4 +- pytmbot/handlers/handlers_util/docker.py | 2 +- .../handlers/server_handlers/filesystem.py | 8 +- .../handlers/server_handlers/inline/swap.py | 2 +- .../handlers/server_handlers/load_average.py | 2 +- pytmbot/handlers/server_handlers/memory.py | 8 +- pytmbot/handlers/server_handlers/network.py | 2 +- pytmbot/handlers/server_handlers/process.py | 8 +- pytmbot/handlers/server_handlers/sensors.py | 14 +- pytmbot/handlers/server_handlers/server.py | 2 +- pytmbot/handlers/server_handlers/uptime.py | 2 +- pytmbot/keyboards/keyboards.py | 8 +- pytmbot/logs.py | 3 +- pytmbot/middleware/access_control.py | 2 +- pytmbot/middleware/rate_limit.py | 2 +- pytmbot/middleware/session_manager.py | 8 +- pytmbot/middleware/session_wrapper.py | 4 +- pytmbot/models/settings_model.py | 26 +- pytmbot/parsers/_parser.py | 10 +- pytmbot/parsers/compiler.py | 8 +- pytmbot/plugins/monitor/methods.py | 155 +++++--- pytmbot/plugins/monitor/models.py | 2 +- pytmbot/plugins/monitor/plugin.py | 8 +- pytmbot/plugins/outline/methods.py | 4 +- pytmbot/plugins/outline/plugin.py | 24 +- pytmbot/plugins/plugin_manager.py | 6 +- pytmbot/plugins/plugins_core.py | 2 +- pytmbot/pytmbot_instance.py | 59 ++-- pytmbot/utils/utilities.py | 10 +- pytmbot/webhook.py | 11 +- tests/test_psutil_adapter.py | 2 +- tools/install.sh | 2 +- 60 files changed, 497 insertions(+), 533 deletions(-) diff --git a/.run/Dockerfile.run.xml b/.run/Dockerfile.run.xml index 43b23b33..7847e781 100644 --- a/.run/Dockerfile.run.xml +++ b/.run/Dockerfile.run.xml @@ -1,44 +1,44 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/docker.md b/docs/docker.md index e8d3fa38..1da076ea 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,288 +1,160 @@ # pyTMbot Docker Image -Welcome to the Docker Hub page for **pyTMbot**! This page provides information about the Docker image for pyTMbot, a -versatile Telegram bot for managing Docker containers and monitoring server status. +Welcome to the **pyTMbot** Docker Hub page! This guide will walk you through setting up and running pyTMbot step-by-step, ensuring a smooth experience from initial configuration to deployment. ## ๐Ÿ‹ Image Overview - **Image Name:** `orenlab/pytmbot` - **Tags:** - - `latest` - The latest stable release image based on Alpine Linux. - - `0.X.X` - Specific stable release versions based on Alpine Linux. - - `alpine-dev` - Latest development version based on Alpine Linux. + - `latest` - The latest stable release image based on Alpine Linux. + - `0.X.X` - Specific stable release versions based on Alpine Linux. + - `alpine-dev` - Latest development version based on Alpine Linux. -## ๐Ÿš€ Quick Start +## ๐Ÿš€ Step-by-Step Setup -### Using Docker Compose +### 1๏ธโƒฃ Preparing for Deployment -1. **Create a `docker-compose.yml` File:** +Before we begin, ensure you have Docker and Docker Compose installed on your system. If not, please refer to the [Docker documentation](https://docs.docker.com/get-docker/) for installation instructions. -```yaml -services: - - pytmbot: - # Lightweight Alpine-based image with dev environment for pyTMbot - image: orenlab/pytmbot:0.2.0 - container_name: pytmbot - # Restart the container only on failure for reliability - restart: on-failure - # Set timezone for proper timestamp handling - environment: - - TZ=Asia/Yekaterinburg - volumes: - # Read-only access to Docker socket for container management - - /var/run/docker.sock:/var/run/docker.sock:ro - # Read-only bot configuration file to prevent modifications - - /root/pytmbot.yaml:/opt/app/pytmbot.yaml:ro - # Prevent the process in the container from gaining additional privileges - security_opt: - - no-new-privileges - # Make the container's filesystem read-only to reduce risks of modification or attack - read_only: true - # Drop all capabilities to minimize potential attacks - cap_drop: - - ALL - pid: host # Use the host's PID namespace for monitoring processes (use with caution) - # Logging - logging: - options: - max-size: "10m" - max-file: "3" - # Run command - command: --plugins monitor # Bot start parameters - ``` +### 2๏ธโƒฃ Generating the Authentication Salt -2. **Start the Container:** - - ```bash - docker-compose up -d - ``` - -### Using Docker CLI - -To launch the Docker container directly: +To securely configure the bot, you'll need a unique salt value for Time-Based One-Time Passwords (TOTP). Run the following command to generate it: ```bash -sudo docker run -d \ --v /var/run/docker.sock:/var/run/docker.sock:ro \ --v /root/pytmbot.yaml:/opt/app/pytmbot.yaml:ro \ ---env TZ="Asia/Yekaterinburg" \ ---restart=always \ ---name=pytmbot \ ---pid=host \ ---security-opt=no-new-privileges \ -orenlab/pytmbot:0.2.0 --plugins monitor +sudo docker run --rm orenlab/pytmbot:latest --salt ``` -## ๐Ÿ—‚๏ธ Configuration (pytmbot.yaml) +Save the generated salt for later use in the `pytmbot.yaml` configuration. -Before running the bot, configure the `pytmbot.yaml` file with the necessary settings: +### 3๏ธโƒฃ Configuring the Bot + +Create a `pytmbot.yaml` file to define your bot's settings. Hereโ€™s how: ```bash sudo -i cd /root -touch pytmbot.yaml nano pytmbot.yaml ``` -Hereโ€™s a sample configuration: +#### Example Configuration File ```yaml -################################################################ # General Bot Settings -################################################################ -# Bot Token Configuration bot_token: - # Production bot token. - prod_token: - - 'YOUR_PROD_BOT_TOKEN' # Replace with your actual production bot token. - # Development bot token. Optional for production bot. - dev_bot_token: - - 'YOUR_DEV_BOT_TOKEN' # Replace with your development bot token (if needed). - -# Chat ID Configuration + prod_token: ['YOUR_PROD_BOT_TOKEN'] # Replace with your production bot token. chat_id: - # Global chat ID. Used for all notifications from the plugin. - global_chat_id: - - 'YOUR_CHAT_ID' # Replace with your actual chat ID for notifications. - -# Access Control Settings + global_chat_id: ['YOUR_CHAT_ID'] # Replace with your Telegram chat ID. access_control: - # User IDs allowed to access the bot. - allowed_user_ids: + allowed_user_ids: ['123456789'] + allowed_admins_ids: ['987654321'] +auth_salt: ['YOUR_GENERATED_SALT'] - # Admin IDs allowed to access the bot. - allowed_admins_ids: - - # Salt used for generating TOTP (Time-Based One-Time Password) secrets and verifying TOTP codes. - auth_salt: - - 'YOUR_AUTH_SALT' # Replace with the salt for TOTP. - -################################################################ # Docker Settings -################################################################ docker: - # Docker socket. Usually: unix:///var/run/docker.sock. - host: - - 'unix:///var/run/docker.sock' # Path to the Docker socket. - # Debug Docker client (to many logs in debug mode with enabled Monitor plugin and Docker containers count monitoring) + host: ['unix:///var/run/docker.sock'] debug_docker_client: false -################################################################ -# Webhook Configuration -################################################################ -webhook_config: - # Webhook URL - url: - - 'YOUR_WEBHOOK_URL' # Replace with your actual webhook URL. - # Webhook port - webhook_port: - - 443 # Port for external webhook requests. - local_port: - - 5001 # Local port for internal requests. - cert: - - 'YOUR_CERTIFICATE' # Path to the SSL certificate (if using HTTPS). - cert_key: - - 'YOUR_CERTIFICATE_KEY' # Path to the SSL certificate's private key (if using HTTPS). - -################################################################ # Plugins Configuration -################################################################ plugins_config: - # Configuration for the Monitor plugin monitor: - # Threshold settings - tracehold: - # CPU usage thresholds in percentage - cpu_usage_threshold: - - 80 # Threshold for CPU usage. - # Memory usage thresholds in percentage - memory_usage_threshold: - - 80 # Threshold for memory usage. - # Disk usage thresholds in percentage - disk_usage_threshold: - - 80 # Threshold for disk usage. - # CPU temperature thresholds in degrees Celsius - cpu_temperature_threshold: - - 85 # Threshold for CPU temperature. - # GPU temperature thresholds in degrees Celsius - gpu_temperature_threshold: - - 90 # Threshold for GPU temperature. - # Disk temperature thresholds in degrees Celsius - disk_temperature_threshold: - - 60 # Threshold for disk temperature. - # Maximum number of notifications for each type of overload - max_notifications: - - 3 # Maximum number of notifications sent for a single event. - # Check interval in seconds - check_interval: - - 5 # Interval for system status checks. - # Reset notification count after X minutes - reset_notification_count: - - 5 # Time in minutes to reset the notification count. - # Number of attempts to retry monitoring startup in case of failure - retry_attempts: - - 3 # Number of retry attempts. - # Interval (in seconds) between retry attempts - retry_interval: - - 10 # Interval between retry attempts. - # Monitor Docker images and containers - monitor_docker: True # True - Monitor Docker images and containers. False - Do not monitor Docker. - - # Configuration for the Outline plugin + cpu_usage_threshold: [80] + memory_usage_threshold: [80] + check_interval: [10] + retry_attempts: [3] outline: - # Outline API settings - api_url: - - 'YOUR_OUTLINE_API_URL' # Replace with your actual Outline API URL. - # Certificate fingerprint - cert: - - 'YOUR_OUTLINE_CERT' # Replace with the actual path to your certificate. - -################################################################ -# InfluxDB Settings -################################################################ -influxdb: - # InfluxDB host - url: - - 'YOUR_INFLUXDB_URL' # URL of your InfluxDB server. - # InfluxDB token - token: - - 'YOUR_INFLUXDB_TOKEN' # Replace with your actual InfluxDB token. - # InfluxDB organization name - org: - - 'YOUR_INFLUXDB_ORG' # Replace with your actual organization name in InfluxDB. - # InfluxDB bucket name - bucket: - - 'YOUR_INFLUXDB_BUCKET' # Replace with your actual bucket name in InfluxDB. - # InfluxDB debug mode - debug_mode: false # Set to true to enable debug mode. + api_url: ['YOUR_OUTLINE_API_URL'] + cert: ['YOUR_OUTLINE_CERT'] ``` -### ๐Ÿ“‹ Explanation of Configuration Fields - -- **bot_token**: Set your bot tokens here for production and development. -- **access_control**: Define which user IDs have access to the bot and specify admin IDs. -- **auth_salt**: Used for generating TOTP secrets. -- **docker**: Specify the Docker socket for communication. -- **webhook_config**: Configure the webhook server. -- **plugins_config**: Configure the plugins, including thresholds and retry settings for monitoring. -- **influxdb**: Configure the InfluxDB server (required for Monitor Plugin). +Refer to the **Plugins Configuration** section below for additional plugin examples. -**Note on `auth_salt` Parameter:** +### 4๏ธโƒฃ Creating the `docker-compose.yml` File -The bot supports random salt generation. To generate a unique salt, run the following command in a separate terminal -window: +Now, create a `docker-compose.yml` file to define the container configuration: - ```bash - sudo docker run --rm orenlab/pytmbot:0.2.0 --salt - ``` +```yaml +services: + pytmbot: + image: orenlab/pytmbot:latest + container_name: pytmbot + restart: on-failure + environment: + - TZ=Asia/Yekaterinburg + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /root/pytmbot.yaml:/opt/app/pytmbot.yaml:ro + security_opt: + - no-new-privileges + read_only: true + cap_drop: + - ALL + pid: host + logging: + options: + max-size: "10m" + max-file: "3" + command: --plugins monitor,outline +``` -This command will display a unique salt value and delete the container automatically. +### 5๏ธโƒฃ Deploying the Container -Alternatively, you can use the official script to configure the bot to run on a host or inside a Docker container. +Start the bot using Docker Compose: ```bash -sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/orenlab/pytmbot/refs/heads/master/tools/install.sh)" +docker-compose up -d ``` -## ๐Ÿ”Œ Plugins - -**pyTMbot** supports a plugin system to extend its functionality. Plugins are configured in the `pytmbot.yaml` file and -can be enabled via Docker Compose or Docker CLI. +Alternatively, you can launch the container directly with the Docker CLI: -### Supported Plugins +```bash +sudo docker run -d \ +-v /var/run/docker.sock:/var/run/docker.sock:ro \ +-v /root/pytmbot.yaml:/opt/app/pytmbot.yaml:ro \ +--env TZ="Asia/Yekaterinburg" \ +--restart=always \ +--name=pytmbot \ +--pid=host \ +--security-opt=no-new-privileges \ +orenlab/pytmbot:latest --plugins monitor,outline +``` -- Extend functionality through custom plugins with simple configuration. -- Support multiple plugins: - - **Monitor Plugin:** Monitor CPU, memory, temperature _(only for Linux)_, disk usage, and detect changes in Docker - containers and images. The plugin sends notifications for various monitored parameters, including new containers - and images, ensuring timely awareness of system status. - - **2FA Plugin:** Two-factor authentication for added security using QR codes and TOTP. - - **Outline VPN Plugin:** Monitor your [Outline VPN](https://getoutline.org/) server directly from Telegram. +## ๐Ÿ”Œ Plugins Configuration -Refer to [plugins.md](https://github.com/orenlab/pytmbot/blob/master/docs/plugins.md) for more information on adding and -managing plugins. +pyTMbot supports an extensive plugin system to extend its functionality. Below are examples for commonly used plugins: -### How to Enable Plugins +### Monitor Plugin -1. **Add Plugin Configuration to `docker-compose.yml`:** +The **Monitor Plugin** tracks system metrics and Docker events. Hereโ€™s an example configuration: - For multiple plugins: +```yaml +plugins_config: + monitor: + cpu_usage_threshold: [80] + memory_usage_threshold: [80] + disk_usage_threshold: [85] + check_interval: [5] + max_notifications: [3] + retry_attempts: [3] + retry_interval: [10] + monitor_docker: true +``` - ```yaml - command: --plugins monitor,outline - ``` +### Outline VPN Plugin -2. **Configure Plugins in `pytmbot.yaml`:** +To monitor your [Outline VPN](https://getoutline.org/), configure the plugin as follows: - External plugin configurations should be placed under `plugins_config` in `pytmbot.yaml`. +```yaml +plugins_config: + outline: + api_url: ['YOUR_OUTLINE_API_URL'] + cert: ['YOUR_OUTLINE_CERT'] +``` - For more details on configuring plugins, refer - to [plugins.md](https://github.com/orenlab/pytmbot/blob/master/docs/plugins.md). +For more detailed plugin configurations, visit the [plugins documentation](https://github.com/orenlab/pytmbot/blob/master/docs/plugins.md). ## ๐Ÿ› ๏ธ Updating the Image -To update to the latest image version: +Keep your pyTMbot image up to date by following these steps: 1. **Stop and Remove the Current Container:** @@ -291,24 +163,23 @@ To update to the latest image version: sudo docker rm pytmbot ``` -2. **Remove the Outdated Image:** +2. **Pull the Latest Image:** ```bash - sudo docker rmi orenlab/pytmbot + sudo docker pull orenlab/pytmbot:latest ``` -3. **Pull the Latest Image and Start the Container:** +3. **Restart the Container:** ```bash - sudo docker pull orenlab/pytmbot:latest docker-compose up -d ``` -## ๐Ÿ‘พ Support, source code, questions and discussions +## ๐Ÿ‘พ Support & Resources -- Support: https://github.com/orenlab/pytmbot/issues -- Source code: [https://github.com/orenlab/pytmbot/](https://github.com/orenlab/pytmbot/) -- Discussions: [https://github.com/orenlab/pytmbot/discussions](https://github.com/orenlab/pytmbot/discussions) +- **Support:** [GitHub Issues](https://github.com/orenlab/pytmbot/issues) +- **Source Code:** [GitHub Repository](https://github.com/orenlab/pytmbot/) +- **Discussions:** [GitHub Discussions](https://github.com/orenlab/pytmbot/discussions) ## ๐Ÿ“œ License diff --git a/pyproject.toml b/pyproject.toml index 342741bd..0a1e2d6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyTMBot" -version = "0.2.0-rc4" +version = "0.2.0" description = "Versatile Telegram bot designed for managing Docker containers, monitoring server status, and extending its functionality through a modular plugin system. The bot operates synchronously, simplifying deployment by eliminating the need for webhooks" authors = ["Denis Rozhnovskiy "] readme = "README.md" diff --git a/pytmbot/adapters/docker/_adapter.py b/pytmbot/adapters/docker/_adapter.py index 07322df9..04442f6c 100644 --- a/pytmbot/adapters/docker/_adapter.py +++ b/pytmbot/adapters/docker/_adapter.py @@ -40,10 +40,14 @@ def __enter__(self) -> docker.DockerClient: try: self.client = docker.DockerClient(base_url=self.docker_url) if settings.docker.debug_docker_client: - bot_logger.debug(f"Docker client initialized with URL: {self.docker_url}") + bot_logger.debug( + f"Docker client initialized with URL: {self.docker_url}" + ) return self.client except docker.errors.DockerException as err: - bot_logger.error(f"Failed creating Docker client at {self.docker_url}: {err}") + bot_logger.error( + f"Failed creating Docker client at {self.docker_url}: {err}" + ) raise except Exception as err: bot_logger.error(f"Unexpected error when initializing Docker client: {err}") diff --git a/pytmbot/adapters/docker/container_manager.py b/pytmbot/adapters/docker/container_manager.py index 9f692a1b..6a5970d4 100644 --- a/pytmbot/adapters/docker/container_manager.py +++ b/pytmbot/adapters/docker/container_manager.py @@ -175,16 +175,16 @@ def __user_is_allowed_to_manage_container(user_id: int) -> bool: bool: True if the user is allowed to manage containers, False otherwise. """ return ( - user_id in settings.access_control.allowed_admins_ids - and session_manager.is_authenticated(user_id) + user_id in settings.access_control.allowed_admins_ids + and session_manager.is_authenticated(user_id) ) def managing_container( - self, - user_id: int, - container_id: Union[str, int], - action: Literal["start", "stop", "restart", "rename"], - **kwargs, + self, + user_id: int, + container_id: Union[str, int], + action: Literal["start", "stop", "restart", "rename"], + **kwargs, ): """ Manages a Docker container based on the given action. diff --git a/pytmbot/adapters/docker/containers_info.py b/pytmbot/adapters/docker/containers_info.py index 1e107aea..af82bdc5 100644 --- a/pytmbot/adapters/docker/containers_info.py +++ b/pytmbot/adapters/docker/containers_info.py @@ -60,7 +60,9 @@ def __get_container_attributes(container_id: str): try: with DockerAdapter() as adapter: if settings.docker.debug_docker_client: - bot_logger.debug(f"Retrieving container details for ID: {container_id}.") + bot_logger.debug( + f"Retrieving container details for ID: {container_id}." + ) return adapter.containers.get(container_id) except Exception as e: bot_logger.error( diff --git a/pytmbot/adapters/docker/images_info.py b/pytmbot/adapters/docker/images_info.py index abb7c9e3..6e139eb6 100644 --- a/pytmbot/adapters/docker/images_info.py +++ b/pytmbot/adapters/docker/images_info.py @@ -44,7 +44,7 @@ def fetch_image_details(): "created": set_naturaltime( datetime.fromisoformat(image.attrs.get("Created")) ) - or "N/A", + or "N/A", } for image in images ] diff --git a/pytmbot/adapters/docker/updates.py b/pytmbot/adapters/docker/updates.py index b4c3b1d6..117abae9 100644 --- a/pytmbot/adapters/docker/updates.py +++ b/pytmbot/adapters/docker/updates.py @@ -26,7 +26,7 @@ def __enter__(self) -> "DockerImageUpdater": return self def __exit__( - self, exc_type: type, exc_val: BaseException, exc_tb: TracebackType + self, exc_type: type, exc_val: BaseException, exc_tb: TracebackType ) -> None: """Cleanup code for context management.""" self.local_images = None @@ -104,7 +104,7 @@ def _compare_versions(self, current_tag: str, remote_tag: str) -> bool: except Exception as e: bot_logger.debug(f"Version comparison error: {e}") return ( - remote_tag > current_tag + remote_tag > current_tag ) # Default to lexicographic comparison if version parsing fails @staticmethod diff --git a/pytmbot/adapters/docker/utils.py b/pytmbot/adapters/docker/utils.py index 54b6bec5..05bcdba9 100644 --- a/pytmbot/adapters/docker/utils.py +++ b/pytmbot/adapters/docker/utils.py @@ -14,7 +14,7 @@ def check_container_state( - container_name: str, target_state: str = "running" + container_name: str, target_state: str = "running" ) -> Optional[str]: """ Checks the state of a Docker container. diff --git a/pytmbot/db/influxdb_interface.py b/pytmbot/db/influxdb_interface.py index cf62e0da..0689a523 100644 --- a/pytmbot/db/influxdb_interface.py +++ b/pytmbot/db/influxdb_interface.py @@ -37,7 +37,9 @@ def __init__(self, url: str, token: str, org: str, bucket: str) -> None: if not self.check_url() and not self.warning_showed: self.warning_showed = True - bot_logger.warning(f"Using non-local InfluxDB URL: {self.url}. Make sure is it secure.") + bot_logger.warning( + f"Using non-local InfluxDB URL: {self.url}. Make sure is it secure." + ) if self.debug_mode: bot_logger.debug(f"InfluxDB client initialized with URL: {self.url}") @@ -81,7 +83,9 @@ def check_url(self) -> bool: private_ip_patterns = [ re.compile(r"^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$"), # 10.x.x.x re.compile(r"^192\.168\.\d{1,3}\.\d{1,3}$"), # 192.168.x.x - re.compile(r"^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$"), # 172.16.x.x - 172.31.x.x + re.compile( + r"^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$" + ), # 172.16.x.x - 172.31.x.x ] for pattern in private_ip_patterns: @@ -92,7 +96,12 @@ def check_url(self) -> bool: return False - def write_data(self, measurement: str, fields: dict[str, float], tags: Optional[dict[str, str]] = None) -> None: + def write_data( + self, + measurement: str, + fields: dict[str, float], + tags: Optional[dict[str, str]] = None, + ) -> None: try: point = Point(measurement) @@ -105,13 +114,17 @@ def write_data(self, measurement: str, fields: dict[str, float], tags: Optional[ point = point.time(datetime.now(timezone.utc)) if self.debug_mode: - bot_logger.debug(f"Writing data to InfluxDB: measurement={measurement}, fields={fields}, tags={tags}") + bot_logger.debug( + f"Writing data to InfluxDB: measurement={measurement}, fields={fields}, tags={tags}" + ) self.write_api.write(bucket=self.bucket, record=point) except InfluxDBError as e: bot_logger.error(f"Error writing to InfluxDB: {e}") raise - def query_data(self, measurement: str, start: str, stop: str, field: str) -> List[Tuple[datetime, float]]: + def query_data( + self, measurement: str, start: str, stop: str, field: str + ) -> List[Tuple[datetime, float]]: """ Query data from InfluxDB for a specific measurement and time range. @@ -127,7 +140,7 @@ def query_data(self, measurement: str, start: str, stop: str, field: str) -> Lis try: query = ( f'from(bucket: "{self.bucket}") ' - f'|> range(start: {start}, stop: {stop}) ' + f"|> range(start: {start}, stop: {stop}) " f'|> filter(fn: (r) => r._measurement == "{measurement}") ' f'|> filter(fn: (r) => r._field == "{field}") ' f'|> yield(name: "mean")' @@ -163,9 +176,13 @@ def get_available_measurements(self) -> List[str]: bot_logger.debug(f"Running query to get measurements: {query}") tables = self.query_api.query(query, org=self.org) - measurements = [record.get_value() for table in tables for record in table.records] + measurements = [ + record.get_value() for table in tables for record in table.records + ] - bot_logger.info(f"Retrieved {len(measurements)} measurements from InfluxDB.") + bot_logger.info( + f"Retrieved {len(measurements)} measurements from InfluxDB." + ) return measurements except InfluxDBError as e: bot_logger.error(f"Error retrieving measurements from InfluxDB: {e}") @@ -184,7 +201,7 @@ def get_available_fields(self, measurement: str) -> List[str]: try: query = ( f'from(bucket: "{self.bucket}") ' - f'|> range(start: -1h) ' + f"|> range(start: -1h) " f'|> filter(fn: (r) => r._measurement == "{measurement}") ' f'|> keep(columns: ["_field"]) ' f'|> distinct(column: "_field") ' @@ -192,13 +209,21 @@ def get_available_fields(self, measurement: str) -> List[str]: ) if self.debug_mode: - bot_logger.debug(f"Running query to get fields for measurement {measurement}: {query}") + bot_logger.debug( + f"Running query to get fields for measurement {measurement}: {query}" + ) tables = self.query_api.query(query, org=self.org) - fields = [record.get_value() for table in tables for record in table.records] + fields = [ + record.get_value() for table in tables for record in table.records + ] - bot_logger.info(f"Retrieved {len(fields)} fields for measurement {measurement}.") + bot_logger.info( + f"Retrieved {len(fields)} fields for measurement {measurement}." + ) return fields except InfluxDBError as e: - bot_logger.error(f"Error retrieving fields for measurement {measurement} from InfluxDB: {e}") + bot_logger.error( + f"Error retrieving fields for measurement {measurement} from InfluxDB: {e}" + ) return [] diff --git a/pytmbot/exceptions.py b/pytmbot/exceptions.py index 6290cfe0..a7cc0255 100644 --- a/pytmbot/exceptions.py +++ b/pytmbot/exceptions.py @@ -61,7 +61,9 @@ def handle(self, exception: Exception) -> bool: if log_level == "DEBUG": # Log the full exception trace at the DEBUG level - bot_logger.opt(exception=exception).debug(f"Exception in @Telebot: {sanitized_exception}") + bot_logger.opt(exception=exception).debug( + f"Exception in @Telebot: {sanitized_exception}" + ) else: # Log only the short exception message without the trace at INFO or higher levels bot_logger.error(f"Exception in @Telebot: {sanitized_exception}") diff --git a/pytmbot/globals.py b/pytmbot/globals.py index d1aedf56..4383bbde 100644 --- a/pytmbot/globals.py +++ b/pytmbot/globals.py @@ -20,7 +20,7 @@ # pyTMBot globals initialization # Global namespace information -__version__ = "v0.2.0-rc4" +__version__ = "v0.2.0" __author__ = "Denis Rozhnovskiy " __license__ = "MIT" __repository__ = "https://github.com/orenlab/pytmbot" diff --git a/pytmbot/handlers/auth_processing/auth_processing.py b/pytmbot/handlers/auth_processing/auth_processing.py index b9fc2a4a..e724101b 100644 --- a/pytmbot/handlers/auth_processing/auth_processing.py +++ b/pytmbot/handlers/auth_processing/auth_processing.py @@ -17,7 +17,7 @@ @logged_handler_session @bot_logger.catch() def handle_unauthorized_message( - query: Union[Message, CallbackQuery], bot: TeleBot + query: Union[Message, CallbackQuery], bot: TeleBot ) -> None: """ Handles unauthorized messages received by the bot. @@ -52,7 +52,7 @@ def handle_unauthorized_message( ) with Compiler( - template_name="a_auth_required.jinja2", name=name, **emojis + template_name="a_auth_required.jinja2", name=name, **emojis ) as compiler: response = compiler.compile() @@ -98,7 +98,7 @@ def handle_access_denied(query: Union[Message, CallbackQuery], bot: TeleBot): } with Compiler( - template_name="a_access_denied.jinja2", name=user_name, **emojis + template_name="a_access_denied.jinja2", name=user_name, **emojis ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/auth_processing/qrcode_processing.py b/pytmbot/handlers/auth_processing/qrcode_processing.py index 2334190d..920f8a8a 100644 --- a/pytmbot/handlers/auth_processing/qrcode_processing.py +++ b/pytmbot/handlers/auth_processing/qrcode_processing.py @@ -40,7 +40,7 @@ def handle_qr_code_message(message: Message, bot: TeleBot) -> Optional[Message]: photo=qr_code, reply_markup=keyboard, caption="The QR code is ready. Click on the image and scan it in your 2FA app. " - "After 60 seconds it will be deleted for security reasons.", + "After 60 seconds it will be deleted for security reasons.", protect_content=True, has_spoiler=True, show_caption_above_media=True, @@ -68,9 +68,9 @@ def delete_qr_code(): } with Compiler( - template_name="b_none.jinja2", - context="Failed to generate QR code... I apologize!", - **emojis, + template_name="b_none.jinja2", + context="Failed to generate QR code... I apologize!", + **emojis, ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/auth_processing/twofa_processing.py b/pytmbot/handlers/auth_processing/twofa_processing.py index 928a890c..91f4ea55 100644 --- a/pytmbot/handlers/auth_processing/twofa_processing.py +++ b/pytmbot/handlers/auth_processing/twofa_processing.py @@ -68,7 +68,7 @@ def handle_totp_code_verification(message: Message, bot: TeleBot) -> None: return if session_manager.get_blocked_time( - user_id + user_id ) and datetime.now() < session_manager.get_blocked_time(user_id): _handle_blocked_user(message, bot) return @@ -146,7 +146,7 @@ def _send_totp_code_message(message: Message, bot: TeleBot) -> None: keyboard = keyboards.build_reply_keyboard(keyboard_type="back_keyboard") with Compiler( - template_name="a_send_totp_code.jinja2", name=name, **emojis + template_name="a_send_totp_code.jinja2", name=name, **emojis ) as compiler: response = compiler.compile() @@ -214,7 +214,7 @@ def _block_user(user_id: int) -> None: def __create_referer_keyboard( - user_id: int, + user_id: int, ) -> Union[ReplyKeyboardMarkup, InlineKeyboardMarkup]: """ Creates a referer keyboard based on the user's handler type and referer URI. diff --git a/pytmbot/handlers/bot_handlers/about.py b/pytmbot/handlers/bot_handlers/about.py index 3822f0d1..d31700a2 100644 --- a/pytmbot/handlers/bot_handlers/about.py +++ b/pytmbot/handlers/bot_handlers/about.py @@ -37,7 +37,7 @@ def handle_about_command(message: Message, bot: TeleBot) -> None: template_data = {"username": user_name, "app_version": __version__} with Compiler( - template_name="b_about_bot.jinja2", context=template_data + template_name="b_about_bot.jinja2", context=template_data ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/bot_handlers/echo.py b/pytmbot/handlers/bot_handlers/echo.py index 0d23c947..7e2a9503 100644 --- a/pytmbot/handlers/bot_handlers/echo.py +++ b/pytmbot/handlers/bot_handlers/echo.py @@ -36,9 +36,9 @@ def handle_echo(message: Message, bot: TeleBot): } with Compiler( - template_name="b_echo.jinja2", - first_name=message.from_user.first_name, - **emojis, + template_name="b_echo.jinja2", + first_name=message.from_user.first_name, + **emojis, ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/bot_handlers/navigation.py b/pytmbot/handlers/bot_handlers/navigation.py index 2f8e9017..9da2b85c 100644 --- a/pytmbot/handlers/bot_handlers/navigation.py +++ b/pytmbot/handlers/bot_handlers/navigation.py @@ -35,7 +35,7 @@ def handle_navigation(message: Message, bot: TeleBot) -> None: } with Compiler( - template_name="b_back.jinja2", first_name=first_name, **emojis + template_name="b_back.jinja2", first_name=first_name, **emojis ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/bot_handlers/plugins.py b/pytmbot/handlers/bot_handlers/plugins.py index f29b9123..ee27d630 100644 --- a/pytmbot/handlers/bot_handlers/plugins.py +++ b/pytmbot/handlers/bot_handlers/plugins.py @@ -52,10 +52,10 @@ def handle_plugins(message: Message, bot: TeleBot) -> None: # Compile the response using the template with Compiler( - template_name="b_plugins.jinja2", - first_name=first_name, - plugins=plugins, - **emojis, + template_name="b_plugins.jinja2", + first_name=first_name, + plugins=plugins, + **emojis, ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/bot_handlers/start.py b/pytmbot/handlers/bot_handlers/start.py index b640cd9c..ace43ae3 100644 --- a/pytmbot/handlers/bot_handlers/start.py +++ b/pytmbot/handlers/bot_handlers/start.py @@ -24,7 +24,7 @@ def handle_start(message: Message, bot: TeleBot) -> None: first_name = message.from_user.first_name with Compiler( - template_name="b_index.jinja2", first_name=first_name + template_name="b_index.jinja2", first_name=first_name ) as compiler: answer = compiler.compile() diff --git a/pytmbot/handlers/bot_handlers/updates.py b/pytmbot/handlers/bot_handlers/updates.py index 4e42338d..b407ffe2 100644 --- a/pytmbot/handlers/bot_handlers/updates.py +++ b/pytmbot/handlers/bot_handlers/updates.py @@ -158,11 +158,11 @@ def _render_new_update_message(update_context: dict[str, str]) -> str: } with Compiler( - template_name="b_bot_update.jinja2", - current_version=current_version, - release_date=release_date, - release_notes=release_notes, - **emojis, + template_name="b_bot_update.jinja2", + current_version=current_version, + release_date=release_date, + release_notes=release_notes, + **emojis, ) as compiler: return compiler.compile() diff --git a/pytmbot/handlers/docker_handlers/containers.py b/pytmbot/handlers/docker_handlers/containers.py index 6b0833b3..0875a2a1 100644 --- a/pytmbot/handlers/docker_handlers/containers.py +++ b/pytmbot/handlers/docker_handlers/containers.py @@ -117,7 +117,7 @@ def __compile_message() -> Tuple[str, Optional[List[str]]]: # Render the template with the context data and emojis with Compiler( - template_name=template_name, context=context, **emojis + template_name=template_name, context=context, **emojis ) as compiler: compiled_data = compiler.compile() diff --git a/pytmbot/handlers/docker_handlers/docker.py b/pytmbot/handlers/docker_handlers/docker.py index 8c2737ca..4040b1aa 100644 --- a/pytmbot/handlers/docker_handlers/docker.py +++ b/pytmbot/handlers/docker_handlers/docker.py @@ -74,7 +74,7 @@ def __compile_message(): try: with Compiler( - template_name="d_docker.jinja2", context=docker_counters, **emojis + template_name="d_docker.jinja2", context=docker_counters, **emojis ) as compiler: return compiler.compile() except Exception as error: diff --git a/pytmbot/handlers/docker_handlers/images.py b/pytmbot/handlers/docker_handlers/images.py index 60f269dc..ed45bbbd 100644 --- a/pytmbot/handlers/docker_handlers/images.py +++ b/pytmbot/handlers/docker_handlers/images.py @@ -33,9 +33,9 @@ def handle_images(message: Message, bot: TeleBot): ) with Compiler( - template_name="d_images.jinja2", - context=images, - thought_balloon=em.get_emoji("thought_balloon"), + template_name="d_images.jinja2", + context=images, + thought_balloon=em.get_emoji("thought_balloon"), ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/docker_handlers/inline/container_info.py b/pytmbot/handlers/docker_handlers/inline/container_info.py index 3132c8d9..3b9f61e6 100644 --- a/pytmbot/handlers/docker_handlers/inline/container_info.py +++ b/pytmbot/handlers/docker_handlers/inline/container_info.py @@ -42,13 +42,13 @@ def handle_containers_full_info(call: CallbackQuery, bot: TeleBot): emojis = get_emojis() with Compiler( - template_name="d_containers_full_info.jinja2", - **emojis, - container_name=container_name, - container_memory_stats=parse_container_memory_stats(container_stats), - container_cpu_stats=parse_container_cpu_stats(container_stats), - container_network_stats=parse_container_network_stats(container_stats), - container_attrs=parse_container_attrs(container_attrs), + template_name="d_containers_full_info.jinja2", + **emojis, + container_name=container_name, + container_memory_stats=parse_container_memory_stats(container_stats), + container_cpu_stats=parse_container_cpu_stats(container_stats), + container_network_stats=parse_container_network_stats(container_stats), + container_attrs=parse_container_attrs(container_attrs), ) as compiler: context = compiler.compile() @@ -64,7 +64,7 @@ def handle_containers_full_info(call: CallbackQuery, bot: TeleBot): ] if call.from_user.id in settings.access_control.allowed_admins_ids and int( - call.from_user.id + call.from_user.id ) == int(called_user_id): bot_logger.debug( f"User {call.from_user.id} is an admin. Added '__manage__' button" diff --git a/pytmbot/handlers/docker_handlers/inline/logs.py b/pytmbot/handlers/docker_handlers/inline/logs.py index 69bf5750..7a6fb663 100644 --- a/pytmbot/handlers/docker_handlers/inline/logs.py +++ b/pytmbot/handlers/docker_handlers/inline/logs.py @@ -47,7 +47,7 @@ def handle_get_logs(call: CallbackQuery, bot: TeleBot): } with Compiler( - "d_logs.jinja2", emojis=emojis, logs=logs, container_name=container_name + "d_logs.jinja2", emojis=emojis, logs=logs, container_name=container_name ) as compiler: context = compiler.compile() diff --git a/pytmbot/handlers/docker_handlers/inline/manage.py b/pytmbot/handlers/docker_handlers/inline/manage.py index 53b14f93..9ad8489b 100644 --- a/pytmbot/handlers/docker_handlers/inline/manage.py +++ b/pytmbot/handlers/docker_handlers/inline/manage.py @@ -121,10 +121,10 @@ def handle_manage_container(call: CallbackQuery, bot: TeleBot): } with Compiler( - "d_managing_containers.jinja2", - emojis=emojis, - state=state, - container_name=container_name, + "d_managing_containers.jinja2", + emojis=emojis, + state=state, + container_name=container_name, ) as compiler: context = compiler.compile() diff --git a/pytmbot/handlers/docker_handlers/inline/manage_action.py b/pytmbot/handlers/docker_handlers/inline/manage_action.py index 51b26695..ace06945 100644 --- a/pytmbot/handlers/docker_handlers/inline/manage_action.py +++ b/pytmbot/handlers/docker_handlers/inline/manage_action.py @@ -108,10 +108,10 @@ def __start_container(call: CallbackQuery, container_name: str, bot: TeleBot): """ try: if ( - container_manager.managing_container( - call.from_user.id, container_name, action="start" - ) - is None + container_manager.managing_container( + call.from_user.id, container_name, action="start" + ) + is None ): return show_handler_info( call=call, text=f"Starting {container_name}: Success", bot=bot @@ -141,10 +141,10 @@ def __stop_container(call: CallbackQuery, container_name: str, bot: TeleBot): """ try: if ( - container_manager.managing_container( - call.from_user.id, container_name, action="stop" - ) - is None + container_manager.managing_container( + call.from_user.id, container_name, action="stop" + ) + is None ): return show_handler_info( call=call, text=f"Stopping {container_name}: Success", bot=bot @@ -214,7 +214,7 @@ def __restart_container(call: CallbackQuery, container_name: str, bot: TeleBot): def __rename_container( - call: CallbackQuery, container_name: str, new_container_name: str, bot: TeleBot + call: CallbackQuery, container_name: str, new_container_name: str, bot: TeleBot ): """ Renames a Docker container based on the provided parameters. @@ -231,10 +231,10 @@ def __rename_container( if is_new_name_valid(new_container_name): try: if container_manager.managing_container( - call.from_user.id, - container_name, - action="rename", - new_container_name=new_container_name, + call.from_user.id, + container_name, + action="rename", + new_container_name=new_container_name, ): return show_handler_info( call=call, text=f"Renaming {container_name}: Success", bot=bot diff --git a/pytmbot/handlers/handler_manager.py b/pytmbot/handlers/handler_manager.py index dc5bd2c2..cf8f98c8 100644 --- a/pytmbot/handlers/handler_manager.py +++ b/pytmbot/handlers/handler_manager.py @@ -105,13 +105,13 @@ def handler_factory() -> dict[str, list[HandlerManager]]: callback=handle_qr_code_message, regexp="Get QR-code for 2FA app", func=lambda message: message.from_user.id - in settings.access_control.allowed_admins_ids, + in settings.access_control.allowed_admins_ids, ), HandlerManager( callback=handle_qr_code_message, commands=["qrcode"], func=lambda message: message.from_user.id - in settings.access_control.allowed_admins_ids, + in settings.access_control.allowed_admins_ids, ), ], } diff --git a/pytmbot/handlers/handlers_util/docker.py b/pytmbot/handlers/handlers_util/docker.py index ca449f18..b1727445 100644 --- a/pytmbot/handlers/handlers_util/docker.py +++ b/pytmbot/handlers/handlers_util/docker.py @@ -99,7 +99,7 @@ def get_sanitized_logs(container_name: str, call: CallbackQuery, token: str) -> def parse_container_memory_stats( - container_stats: Dict[str, Any] + container_stats: Dict[str, Any] ) -> Dict[str, Union[str, float]]: """ Parse the memory statistics of a container. diff --git a/pytmbot/handlers/server_handlers/filesystem.py b/pytmbot/handlers/server_handlers/filesystem.py index 5e1e7112..2f103591 100644 --- a/pytmbot/handlers/server_handlers/filesystem.py +++ b/pytmbot/handlers/server_handlers/filesystem.py @@ -36,10 +36,10 @@ def handle_file_system(message: Message, bot: TeleBot): } with Compiler( - template_name="b_fs.jinja2", - context=disk_usage, - running_in_docker=running_in_docker, - **emojis, + template_name="b_fs.jinja2", + context=disk_usage, + running_in_docker=running_in_docker, + **emojis, ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/inline/swap.py b/pytmbot/handlers/server_handlers/inline/swap.py index 09294f24..f35e27d8 100644 --- a/pytmbot/handlers/server_handlers/inline/swap.py +++ b/pytmbot/handlers/server_handlers/inline/swap.py @@ -35,7 +35,7 @@ def handle_swap_info(call: CallbackQuery, bot: TeleBot): ) with Compiler( - template_name="b_swap.jinja2", context=swap_data, **emojis + template_name="b_swap.jinja2", context=swap_data, **emojis ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/load_average.py b/pytmbot/handlers/server_handlers/load_average.py index 6fecca68..41c34113 100644 --- a/pytmbot/handlers/server_handlers/load_average.py +++ b/pytmbot/handlers/server_handlers/load_average.py @@ -36,7 +36,7 @@ def handle_load_average(message: Message, bot: TeleBot): ) with Compiler( - template_name="b_load_average.jinja2", context=load_average, **emojis + template_name="b_load_average.jinja2", context=load_average, **emojis ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/memory.py b/pytmbot/handlers/server_handlers/memory.py index e616d58c..52006256 100644 --- a/pytmbot/handlers/server_handlers/memory.py +++ b/pytmbot/handlers/server_handlers/memory.py @@ -41,10 +41,10 @@ def handle_memory(message: Message, bot: TeleBot): keyboard = keyboards.build_inline_keyboard(button_data) with Compiler( - template_name="b_memory.jinja2", - context=memory_info, - thought_balloon=em.get_emoji("thought_balloon"), - abacus=em.get_emoji("abacus"), + template_name="b_memory.jinja2", + context=memory_info, + thought_balloon=em.get_emoji("thought_balloon"), + abacus=em.get_emoji("abacus"), ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/network.py b/pytmbot/handlers/server_handlers/network.py index b453178c..4b748101 100644 --- a/pytmbot/handlers/server_handlers/network.py +++ b/pytmbot/handlers/server_handlers/network.py @@ -38,7 +38,7 @@ def handle_network(message: Message, bot: TeleBot): ) with Compiler( - template_name="b_net_io.jinja2", context=network_statistics, **emojis + template_name="b_net_io.jinja2", context=network_statistics, **emojis ) as compiler: message_text = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/process.py b/pytmbot/handlers/server_handlers/process.py index d3cc6dd7..5e35203c 100644 --- a/pytmbot/handlers/server_handlers/process.py +++ b/pytmbot/handlers/server_handlers/process.py @@ -36,10 +36,10 @@ def handle_process(message: Message, bot: TeleBot): ) with Compiler( - template_name="b_process.jinja2", - context=process_count, - running_in_docker=running_in_docker, - **emojis, + template_name="b_process.jinja2", + context=process_count, + running_in_docker=running_in_docker, + **emojis, ) as compiler: message_text = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/sensors.py b/pytmbot/handlers/server_handlers/sensors.py index 46636eda..82284a2d 100644 --- a/pytmbot/handlers/server_handlers/sensors.py +++ b/pytmbot/handlers/server_handlers/sensors.py @@ -34,16 +34,16 @@ def handle_sensors(message: Message, bot: TeleBot): return bot.send_message( message.chat.id, text="I'm sorry to inform you that I was unable to retrieve the sensor values. " - "There seems to be an issue with my system. Please try again later.", + "There seems to be an issue with my system. Please try again later.", ) with Compiler( - template_name="b_sensors.jinja2", - context=sensors_data, - thought_balloon=em.get_emoji("thought_balloon"), - thermometer=em.get_emoji("thermometer"), - exclamation=em.get_emoji("red_exclamation_mark"), - melting_face=em.get_emoji("melting_face"), + template_name="b_sensors.jinja2", + context=sensors_data, + thought_balloon=em.get_emoji("thought_balloon"), + thermometer=em.get_emoji("thermometer"), + exclamation=em.get_emoji("red_exclamation_mark"), + melting_face=em.get_emoji("melting_face"), ) as compiler: sensors_message = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/server.py b/pytmbot/handlers/server_handlers/server.py index c6ae2fa0..e622f004 100644 --- a/pytmbot/handlers/server_handlers/server.py +++ b/pytmbot/handlers/server_handlers/server.py @@ -35,7 +35,7 @@ def handle_server(message: Message, bot: TeleBot) -> None: } with Compiler( - template_name="b_server.jinja2", first_name=first_name, **emojis + template_name="b_server.jinja2", first_name=first_name, **emojis ) as compiler: response = compiler.compile() diff --git a/pytmbot/handlers/server_handlers/uptime.py b/pytmbot/handlers/server_handlers/uptime.py index 9e9e278a..b46b090e 100644 --- a/pytmbot/handlers/server_handlers/uptime.py +++ b/pytmbot/handlers/server_handlers/uptime.py @@ -44,7 +44,7 @@ def handle_uptime(message: Message, bot: TeleBot): ) with Compiler( - template_name="b_uptime.jinja2", context=uptime_data, **emojis + template_name="b_uptime.jinja2", context=uptime_data, **emojis ) as compiler: bot_answer = compiler.compile() diff --git a/pytmbot/keyboards/keyboards.py b/pytmbot/keyboards/keyboards.py index c23bae12..0b3df98e 100644 --- a/pytmbot/keyboards/keyboards.py +++ b/pytmbot/keyboards/keyboards.py @@ -81,9 +81,9 @@ def build_referer_inline_keyboard(data: str) -> InlineKeyboardMarkup: return keyboard def build_reply_keyboard( - self, - keyboard_type: Optional[str] = None, - plugin_keyboard_data: Optional[dict[str, str]] = None, + self, + keyboard_type: Optional[str] = None, + plugin_keyboard_data: Optional[dict[str, str]] = None, ) -> ReplyKeyboardMarkup: """ Constructs a ReplyKeyboardMarkup object with the specified keyboard settings. @@ -193,7 +193,7 @@ class ButtonData(NamedTuple): callback_data: str def build_inline_keyboard( - self, buttons_data: Union[List[ButtonData], ButtonData] + self, buttons_data: Union[List[ButtonData], ButtonData] ) -> InlineKeyboardMarkup: """ Constructs an InlineKeyboardMarkup object for the inline keyboard. diff --git a/pytmbot/logs.py b/pytmbot/logs.py index 8af18c2c..1a9905a9 100644 --- a/pytmbot/logs.py +++ b/pytmbot/logs.py @@ -10,7 +10,8 @@ parse_cli_args, get_inline_message_full_info, get_message_full_info, - is_running_in_docker, sanitize_exception, + is_running_in_docker, + sanitize_exception, ) diff --git a/pytmbot/middleware/access_control.py b/pytmbot/middleware/access_control.py index cb03e402..661465d7 100644 --- a/pytmbot/middleware/access_control.py +++ b/pytmbot/middleware/access_control.py @@ -72,7 +72,7 @@ def pre_process(self, message: Message, data: Any) -> Optional[CancelUpdate]: return None def post_process( - self, message: Message, data: Any, exception: Optional[Exception] + self, message: Message, data: Any, exception: Optional[Exception] ) -> None: # Implement if necessary or remove if not used. pass diff --git a/pytmbot/middleware/rate_limit.py b/pytmbot/middleware/rate_limit.py index d647ae49..45dd5496 100644 --- a/pytmbot/middleware/rate_limit.py +++ b/pytmbot/middleware/rate_limit.py @@ -74,7 +74,7 @@ def pre_process(self, message: Message, data: Any) -> Optional[CancelUpdate]: return None def post_process( - self, message: Message, data: Any, exception: Optional[Exception] + self, message: Message, data: Any, exception: Optional[Exception] ) -> None: """ Post-processes the incoming message. diff --git a/pytmbot/middleware/session_manager.py b/pytmbot/middleware/session_manager.py index fbb6ff3f..9d3c05a4 100644 --- a/pytmbot/middleware/session_manager.py +++ b/pytmbot/middleware/session_manager.py @@ -219,9 +219,9 @@ def is_authenticated(self, user_id: int) -> bool: bool: True if the user is authenticated, False otherwise. """ return ( - self.get_auth_state(user_id) == self.state_fabric.authenticated - and not self.is_blocked(user_id) - and not self.is_session_expired(user_id) + self.get_auth_state(user_id) == self.state_fabric.authenticated + and not self.is_blocked(user_id) + and not self.is_session_expired(user_id) ) def set_login_time(self, user_id: int) -> None: @@ -269,7 +269,7 @@ def is_session_expired(self, user_id: int) -> bool: return True def set_referer_uri_and_handler_type_for_user( - self, user_id: int, handler_type: str, referer_uri: str + self, user_id: int, handler_type: str, referer_uri: str ) -> None: """ Set the referer URI and handler type for a given user ID. diff --git a/pytmbot/middleware/session_wrapper.py b/pytmbot/middleware/session_wrapper.py index ffa0ffa3..02e13614 100644 --- a/pytmbot/middleware/session_wrapper.py +++ b/pytmbot/middleware/session_wrapper.py @@ -12,7 +12,7 @@ def handle_unauthorized_query( - query: Union[Message, CallbackQuery], bot: telebot.TeleBot + query: Union[Message, CallbackQuery], bot: telebot.TeleBot ) -> bool: """ Handle unauthorized queries. @@ -30,7 +30,7 @@ def handle_unauthorized_query( def access_denied_handler( - query: Union[Message, CallbackQuery], bot: telebot.TeleBot + query: Union[Message, CallbackQuery], bot: telebot.TeleBot ) -> bool: """ Handle access denied queries. diff --git a/pytmbot/models/settings_model.py b/pytmbot/models/settings_model.py index 1b17e7de..2965c0c7 100644 --- a/pytmbot/models/settings_model.py +++ b/pytmbot/models/settings_model.py @@ -93,12 +93,12 @@ class TraceholdSettings(BaseModel): disk_temperature_threshold (List[int]): Threshold for disk temperature in Celsius. """ - cpu_usage_threshold: conlist(int, min_length=1, max_length=1) = [80] - memory_usage_threshold: conlist(int, min_length=1, max_length=1) = [80] - disk_usage_threshold: conlist(int, min_length=1, max_length=1) = [80] - cpu_temperature_threshold: conlist(int, min_length=1, max_length=1) = [85] - gpu_temperature_threshold: conlist(int, min_length=1, max_length=1) = [90] - disk_temperature_threshold: conlist(int, min_length=1, max_length=1) = [60] + cpu_usage_threshold: conlist(int, min_length=1, max_length=1) + memory_usage_threshold: conlist(int, min_length=1, max_length=1) + disk_usage_threshold: conlist(int, min_length=1, max_length=1) + cpu_temperature_threshold: conlist(int, min_length=1, max_length=1) + gpu_temperature_threshold: conlist(int, min_length=1, max_length=1) + disk_temperature_threshold: conlist(int, min_length=1, max_length=1) class MonitorConfig(BaseModel): @@ -115,11 +115,11 @@ class MonitorConfig(BaseModel): """ tracehold: TraceholdSettings - max_notifications: conlist(int, min_length=1, max_length=1) = [3] - check_interval: conlist(int, min_length=1, max_length=1) = [2] - reset_notification_count: conlist(int, min_length=1, max_length=1) = [5] - retry_attempts: conlist(int, min_length=1, max_length=2) = [3] - retry_interval: conlist(int, min_length=1, max_length=2) = [10] + max_notifications: conlist(int, min_length=1, max_length=1) + check_interval: conlist(int, min_length=1, max_length=1) + reset_notification_count: conlist(int, min_length=1, max_length=1) + retry_attempts: conlist(int, min_length=1, max_length=2) + retry_interval: conlist(int, min_length=1, max_length=2) monitor_docker: bool = False @@ -158,8 +158,8 @@ class WebhookConfig(BaseModel): """ url: conlist(SecretStr) - webhook_port: conlist(int) = [443] - local_port: conlist(int) = [5001] + webhook_port: conlist(int) + local_port: conlist(int) cert: conlist(SecretStr) cert_key: conlist(SecretStr) diff --git a/pytmbot/parsers/_parser.py b/pytmbot/parsers/_parser.py index 8169360d..297d69bd 100644 --- a/pytmbot/parsers/_parser.py +++ b/pytmbot/parsers/_parser.py @@ -75,10 +75,10 @@ def __initialize_jinja_environment() -> jinja2.Environment: return env def render_templates( - self, - template_name: str, - emojis: Optional[Dict[str, str]] = None, - **kwargs: Dict[str, Any], + self, + template_name: str, + emojis: Optional[Dict[str, str]] = None, + **kwargs: Dict[str, Any], ) -> str: """ Render a Jinja2 template with the given name and context. @@ -111,7 +111,7 @@ def render_templates( ) from error def __get_template( - self, template_name: str, template_subdir: str + self, template_name: str, template_subdir: str ) -> jinja2.Template: """ Get a Jinja2 template by its name. diff --git a/pytmbot/parsers/compiler.py b/pytmbot/parsers/compiler.py index ad809e42..ce447094 100644 --- a/pytmbot/parsers/compiler.py +++ b/pytmbot/parsers/compiler.py @@ -40,10 +40,10 @@ def __enter__(self) -> "Compiler": return self def __exit__( - self, - exc_type: Optional[type], - exc_val: Optional[Exception], - exc_tb: Optional[Any], + self, + exc_type: Optional[type], + exc_val: Optional[Exception], + exc_tb: Optional[Any], ) -> None: """ Exit the runtime context related to this object. diff --git a/pytmbot/plugins/monitor/methods.py b/pytmbot/plugins/monitor/methods.py index e272c8a5..e89c2515 100644 --- a/pytmbot/plugins/monitor/methods.py +++ b/pytmbot/plugins/monitor/methods.py @@ -15,7 +15,10 @@ import psutil from telebot import TeleBot -from pytmbot.adapters.docker.containers_info import fetch_docker_counters, retrieve_containers_stats +from pytmbot.adapters.docker.containers_info import ( + fetch_docker_counters, + retrieve_containers_stats, +) from pytmbot.adapters.docker.images_info import fetch_image_details from pytmbot.db.influxdb_interface import InfluxDBInterface from pytmbot.models.settings_model import MonitorConfig @@ -30,7 +33,9 @@ class SystemMonitorPlugin(PluginCore): Sends notifications to a Telegram bot if any of the monitored resources exceed specified thresholds. """ - def __init__(self, config: "MonitorConfig", bot: TeleBot, event_threshold_duration: int = 20) -> None: + def __init__( + self, config: "MonitorConfig", bot: TeleBot, event_threshold_duration: int = 20 + ) -> None: """ Initializes the SystemMonitorPlugin with the given bot instance and configuration. @@ -59,10 +64,14 @@ def __init__(self, config: "MonitorConfig", bot: TeleBot, event_threshold_durati "cpu": self.monitor_settings.tracehold.cpu_temperature_threshold[0], "pch": self.monitor_settings.tracehold.cpu_temperature_threshold[0], "gpu": self.monitor_settings.tracehold.gpu_temperature_threshold[0], - "disk": self.monitor_settings.tracehold.disk_temperature_threshold[0] + "disk": self.monitor_settings.tracehold.disk_temperature_threshold[0], } - self.cpu_usage_threshold = self.monitor_settings.tracehold.cpu_usage_threshold[0] - self.disk_usage_threshold = self.monitor_settings.tracehold.disk_usage_threshold[0] + self.cpu_usage_threshold = self.monitor_settings.tracehold.cpu_usage_threshold[ + 0 + ] + self.disk_usage_threshold = ( + self.monitor_settings.tracehold.disk_usage_threshold[0] + ) self.load_threshold = 70.0 # InfluxDB settings @@ -74,7 +83,7 @@ def __init__(self, config: "MonitorConfig", bot: TeleBot, event_threshold_durati url=self.influxdb_url, token=self.influxdb_token, org=self.influxdb_org, - bucket=self.influxdb_bucket + bucket=self.influxdb_bucket, ) # Check if running in Docker @@ -87,10 +96,7 @@ def __init__(self, config: "MonitorConfig", bot: TeleBot, event_threshold_durati # Store previous container and image hashes self._previous_container_hashes = {} self._previous_image_hashes = {} - self._previous_counts = { - 'containers_count': 0, - 'images_count': 0 - } + self._previous_counts = {"containers_count": 0, "images_count": 0} # Initialize state tracking for event durations self.event_start_times = {} @@ -134,9 +140,7 @@ def _start_monitoring_thread(self) -> None: """ Starts the system monitoring process in a separate daemon thread. """ - monitoring_thread = threading.Thread( - target=self._monitor_system, daemon=True - ) + monitoring_thread = threading.Thread(target=self._monitor_system, daemon=True) monitoring_thread.name = "SystemMonitoringThread" monitoring_thread.start() @@ -199,10 +203,7 @@ def _monitor_system(self) -> None: "load_average_1m": load_averages[0], "load_average_5m": load_averages[1], "load_average_15m": load_averages[2], - **{ - f"disk_{key}_usage": value - for key, value in disk_usage.items() - }, + **{f"disk_{key}_usage": value for key, value in disk_usage.items()}, **{ f"temperature_{sensor}_current": temp_data["current"] for sensor, temp_data in temperatures.items() @@ -225,11 +226,15 @@ def _monitor_system(self) -> None: if self.monitor_docker: current_time = time.time() - if current_time - self.docker_counters_last_updated > self.docker_counters_update_interval: + if ( + current_time - self.docker_counters_last_updated + > self.docker_counters_update_interval + ): self._detect_docker_changes() self.docker_counters_last_updated = current_time self.bot_logger.debug( - f"Updated Docker counters: {self._previous_counts}") + f"Updated Docker counters: {self._previous_counts}" + ) fields.update( **{ @@ -250,7 +255,7 @@ def _get_platform_metadata(self) -> dict: os_info = platform.uname() return { "system": "docker" if self.is_docker else "bare-metal", - "hostname": os_info.node + "hostname": os_info.node, } def _record_metrics(self, fields: dict) -> None: @@ -264,7 +269,9 @@ def _record_metrics(self, fields: dict) -> None: except Exception as e: self.bot_logger.exception(f"Error writing metrics to InfluxDB: {e}") - def _track_event_duration(self, event_name: str, event_occurred: bool) -> Optional[float]: + def _track_event_duration( + self, event_name: str, event_occurred: bool + ) -> Optional[float]: """ Track the start time of an event and return the duration it has been active. Also detect when the event has ended to send a notification. @@ -291,7 +298,9 @@ def _track_event_duration(self, event_name: str, event_occurred: bool) -> Option self.event_start_times.pop(event_name, None) return None - def _send_resolution_notification(self, event_name: str, event_duration: float) -> None: + def _send_resolution_notification( + self, event_name: str, event_duration: float + ) -> None: """ Sends a notification when an event (e.g., high CPU usage) has been resolved. @@ -307,7 +316,7 @@ def _send_resolution_notification(self, event_name: str, event_duration: float) "cpu_temp_exceeded": "๐ŸŒก๏ธ *CPU temperature normalized* ๐ŸŒก๏ธ", "gpu_temp_exceeded": "๐ŸŒก๏ธ *GPU temperature normalized* ๐ŸŒก๏ธ", "disk_temp_exceeded": "๐ŸŒก๏ธ *Disk temperature normalized* ๐ŸŒก๏ธ", - "pch_temp_exceeded": "๐ŸŒก๏ธ *PCH temperature normalized* ๐ŸŒก๏ธ" + "pch_temp_exceeded": "๐ŸŒก๏ธ *PCH temperature normalized* ๐ŸŒก๏ธ", } # Find the appropriate description @@ -329,11 +338,18 @@ def _get_temp_threshold(self, sensor: str) -> float: """ # Return the threshold for the sensor, or a default value if sensor is unknown - return self.temperature_thresholds.get(sensor, 80.0) # 80ยฐC as a default threshold - - def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, disk_usage: dict, - temperatures: dict, - event_durations: dict) -> None: + return self.temperature_thresholds.get( + sensor, 80.0 + ) # 80ยฐC as a default threshold + + def _send_aggregated_notifications( + self, + cpu_usage: float, + memory_usage: float, + disk_usage: dict, + temperatures: dict, + event_durations: dict, + ) -> None: """ Aggregates notifications based on monitored values and sends a single message if thresholds are exceeded. @@ -347,10 +363,13 @@ def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, messages = [] # CPU usage notification - cpu_event_duration = self._track_event_duration("cpu_usage_exceeded", cpu_usage > self.cpu_usage_threshold) + cpu_event_duration = self._track_event_duration( + "cpu_usage_exceeded", cpu_usage > self.cpu_usage_threshold + ) if cpu_event_duration and cpu_event_duration >= self.event_threshold_duration: self.bot_logger.debug( - f"CPU event duration: {cpu_event_duration} seconds (threshold duration: {self.event_threshold_duration})") + f"CPU event duration: {cpu_event_duration} seconds (threshold duration: {self.event_threshold_duration})" + ) messages.append( f"๐Ÿ”ฅ High CPU Usage Detected! ๐Ÿ”ฅ\n" f"๐Ÿ’ป CPU Usage: {cpu_usage}%\n" @@ -358,9 +377,10 @@ def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, ) # Memory usage notification - mem_event_duration = self._track_event_duration("memory_usage_exceeded", - memory_usage > - self.monitor_settings.tracehold.memory_usage_threshold[0]) + mem_event_duration = self._track_event_duration( + "memory_usage_exceeded", + memory_usage > self.monitor_settings.tracehold.memory_usage_threshold[0], + ) if mem_event_duration and mem_event_duration >= self.event_threshold_duration: messages.append( f"๐Ÿšจ High Memory Usage Detected! ๐Ÿšจ\n" @@ -371,7 +391,10 @@ def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, # Disk usage notifications for disk, usage in disk_usage.items(): disk_event_duration = event_durations["disk_usage"].get(disk) - if disk_event_duration and disk_event_duration >= self.event_threshold_duration: + if ( + disk_event_duration + and disk_event_duration >= self.event_threshold_duration + ): messages.append( f"๐Ÿ’ฝ High Disk Usage Detected on {disk}! ๐Ÿ’ฝ\n" f"๐Ÿ“Š Disk Usage: {usage}%\n" @@ -381,7 +404,10 @@ def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, # Temperature notifications for sensor, temp in temperatures.items(): temp_event_duration = event_durations["temperatures"].get(sensor) - if temp_event_duration and temp_event_duration >= self.event_threshold_duration: + if ( + temp_event_duration + and temp_event_duration >= self.event_threshold_duration + ): messages.append( f"๐ŸŒก๏ธ {sensor} temperature is high: {temp}ยฐC\n" f"โฑ๏ธ Duration: {int(temp_event_duration)} seconds" @@ -389,7 +415,9 @@ def _send_aggregated_notifications(self, cpu_usage: float, memory_usage: float, if messages and self.notification_count < self.max_notifications: aggregated_message = "\n\n".join(messages) - self.bot_logger.debug(f"Monitoring aggregated notification sent: {aggregated_message}") + self.bot_logger.debug( + f"Monitoring aggregated notification sent: {aggregated_message}" + ) self._send_notification(aggregated_message) self.notification_count += 1 @@ -402,13 +430,19 @@ def _send_notification(self, message: str) -> None: """ if self.notification_count < self.max_notifications: try: - sanitized_message = message.replace(self.config.emoji_for_notification, "").replace("\n", " ") + sanitized_message = message.replace( + self.config.emoji_for_notification, "" + ).replace("\n", " ") self.bot_logger.info(f"Sending notification: {sanitized_message}") - self.bot.send_message(self.settings.chat_id.global_chat_id[0], message, parse_mode="HTML") + self.bot.send_message( + self.settings.chat_id.global_chat_id[0], message, parse_mode="HTML" + ) except Exception as e: self.bot_logger.error(f"Failed to send notification: {e}") elif not self.max_notifications_reached: - self.bot_logger.warning("Max notifications reached; no more notifications will be sent.") + self.bot_logger.warning( + "Max notifications reached; no more notifications will be sent." + ) self.max_notifications_reached = True def _adjust_check_interval(self) -> None: @@ -420,14 +454,18 @@ def _adjust_check_interval(self) -> None: f"High CPU load detected ({cpu_load}%). Increasing check interval to {self.check_interval} seconds." ) else: - self.check_interval = self.monitor_settings.check_interval[0] # Restore to normal interval + self.check_interval = self.monitor_settings.check_interval[ + 0 + ] # Restore to normal interval def _check_load_average(self) -> tuple[float, float, float]: """ Checks the current load average (1, 5, and 15 minutes). """ try: - load_avg_1, load_avg_5, load_avg_15 = psutil.getloadavg() # Get the 1, 5, and 15 minute load averages + load_avg_1, load_avg_5, load_avg_15 = ( + psutil.getloadavg() + ) # Get the 1, 5, and 15 minute load averages return load_avg_1, load_avg_5, load_avg_15 except (AttributeError, psutil.Error) as e: self.bot_logger.error(f"Error checking load average: {e}") @@ -440,7 +478,9 @@ def _check_temperatures(self) -> dict: temps = psutil.sensors_temperatures() if not temps and self.sensors_available: self.sensors_available = False - self.bot_logger.warning("No temperature sensors available on this system.") + self.bot_logger.warning( + "No temperature sensors available on this system." + ) return temperatures for name, entries in temps.items(): @@ -448,8 +488,12 @@ def _check_temperatures(self) -> dict: sensor_key = f"{name}_{entry.label or 'default'}" # Use label if available, else 'default' temperatures[sensor_key] = { "current": entry.current, - "high": entry.high if entry.high else None, # High threshold, if available - "critical": entry.critical if entry.critical else None # Critical threshold, if available + "high": ( + entry.high if entry.high else None + ), # High threshold, if available + "critical": ( + entry.critical if entry.critical else None + ), # Critical threshold, if available } return temperatures @@ -511,7 +555,9 @@ def _get_disk_usage(self) -> dict: partitions = psutil.disk_partitions(all=False) for partition in partitions: if not self._is_partition_excluded(partition.device): - disk_usage[partition.device] = psutil.disk_usage(partition.mountpoint).percent + disk_usage[partition.device] = psutil.disk_usage( + partition.mountpoint + ).percent self.last_disk_usage = disk_usage self.last_poll_time = current_time self.return_cached_disk_usage = True @@ -609,26 +655,27 @@ def _detect_docker_changes(self): # Create hashes for the new containers and images new_container_hashes = { - container['id']: container - for container in new_containers + container["id"]: container for container in new_containers } - new_image_hashes = { - image['id']: image - for image in new_images - } + new_image_hashes = {image["id"]: image for image in new_images} if self._init_mode: self._init_mode = False self.bot_logger.info( - f"Init Docker containers and images monitoring with {len(new_container_hashes)} containers and {len(new_image_hashes)} images.") + f"Init Docker containers and images monitoring with {len(new_container_hashes)} containers and {len(new_image_hashes)} images." + ) self.bot_logger.debug(f"Known containers: {new_container_hashes}") self.bot_logger.debug(f"Known images: {new_image_hashes}") else: # Detect new containers - new_container_ids = new_container_hashes.keys() - self._previous_container_hashes.keys() + new_container_ids = ( + new_container_hashes.keys() - self._previous_container_hashes.keys() + ) for container_id in new_container_ids: - self._send_detailed_container_notification(new_container_hashes[container_id]) + self._send_detailed_container_notification( + new_container_hashes[container_id] + ) # Detect new images new_image_ids = new_image_hashes.keys() - self._previous_image_hashes.keys() diff --git a/pytmbot/plugins/monitor/models.py b/pytmbot/plugins/monitor/models.py index 0651e3ee..7f108578 100644 --- a/pytmbot/plugins/monitor/models.py +++ b/pytmbot/plugins/monitor/models.py @@ -41,7 +41,7 @@ def __init__(self, retention_days: int = 7): """Initialize the class with a retention period.""" # To ensure __init__ is not called multiple times in case of repeated instantiation if not hasattr( - self, "initialized" + self, "initialized" ): # This is to ensure it's only initialized once self.retention_days = retention_days self.data = { diff --git a/pytmbot/plugins/monitor/plugin.py b/pytmbot/plugins/monitor/plugin.py index 41225a97..a3e25a7c 100644 --- a/pytmbot/plugins/monitor/plugin.py +++ b/pytmbot/plugins/monitor/plugin.py @@ -50,10 +50,10 @@ def handle_monitoring(self, message: Message) -> Message: "warning": em.get_emoji("warning"), } with Compiler( - template_name="plugin_monitor_index.jinja2", - first_name=message.from_user.first_name, - available_periods=available_periods, - **emojis, + template_name="plugin_monitor_index.jinja2", + first_name=message.from_user.first_name, + available_periods=available_periods, + **emojis, ) as compiler: response = compiler.compile() return self.bot.send_message( diff --git a/pytmbot/plugins/outline/methods.py b/pytmbot/plugins/outline/methods.py index 6e8019b6..2123ce40 100644 --- a/pytmbot/plugins/outline/methods.py +++ b/pytmbot/plugins/outline/methods.py @@ -59,8 +59,8 @@ def _fetch_key_information(self) -> AccessKeyList: return self.client.get_access_keys() def outline_action_manager( - self, - action: Literal["server_information", "traffic_information", "key_information"], + self, + action: Literal["server_information", "traffic_information", "key_information"], ) -> Union[OutlineServer, Metrics, List[OutlineKey]]: """ Manages actions based on the provided action string and returns the appropriate data. diff --git a/pytmbot/plugins/outline/plugin.py b/pytmbot/plugins/outline/plugin.py index bdb80a73..1a08480d 100644 --- a/pytmbot/plugins/outline/plugin.py +++ b/pytmbot/plugins/outline/plugin.py @@ -167,8 +167,8 @@ def handle_traffic(self, message: Message) -> Message: ) def _get_action_data( - self, - action: Literal["key_information", "server_information", "traffic_information"], + self, + action: Literal["key_information", "server_information", "traffic_information"], ) -> Optional[Dict]: """ Retrieves action data from the plugin methods and processes it. @@ -204,11 +204,11 @@ def _get_user_names(self) -> Optional[Dict[str, str]]: return None def _compile_template( - self, - template_name: str, - first_name: str, - context: Optional[Dict] = None, - **kwargs: dict[str, Any], + self, + template_name: str, + first_name: str, + context: Optional[Dict] = None, + **kwargs: dict[str, Any], ) -> str: """ Compiles the template with the provided context and first name. @@ -220,11 +220,11 @@ def _compile_template( :return: The compiled template response as a string. """ with Compiler( - template_name=template_name, - first_name=first_name, - set_naturalsize=set_naturalsize, - context=context or {}, - **kwargs, + template_name=template_name, + first_name=first_name, + set_naturalsize=set_naturalsize, + context=context or {}, + **kwargs, ) as compiler: response = compiler.compile() diff --git a/pytmbot/plugins/plugin_manager.py b/pytmbot/plugins/plugin_manager.py index e014e5e1..f539c74d 100644 --- a/pytmbot/plugins/plugin_manager.py +++ b/pytmbot/plugins/plugin_manager.py @@ -148,9 +148,9 @@ def _find_plugin_classes(module) -> List[Type[PluginInterface]]: for attribute_name in dir(module): attr = getattr(module, attribute_name) if ( - inspect.isclass(attr) - and issubclass(attr, PluginInterface) - and attr is not PluginInterface + inspect.isclass(attr) + and issubclass(attr, PluginInterface) + and attr is not PluginInterface ): plugin_classes.append(attr) return plugin_classes diff --git a/pytmbot/plugins/plugins_core.py b/pytmbot/plugins/plugins_core.py index c4e68c01..c3be014f 100644 --- a/pytmbot/plugins/plugins_core.py +++ b/pytmbot/plugins/plugins_core.py @@ -43,7 +43,7 @@ def __get_config_path(self, config_name: str) -> str: return config_path def load_plugin_external_config( - self, config_name: str, config_model: type[PluginCoreModel] + self, config_name: str, config_model: type[PluginCoreModel] ) -> PluginCoreModel: """ Loads plugin external configuration from a YAML file and creates a PluginCoreModel object. diff --git a/pytmbot/pytmbot_instance.py b/pytmbot/pytmbot_instance.py index bf6bdf55..23011744 100644 --- a/pytmbot/pytmbot_instance.py +++ b/pytmbot/pytmbot_instance.py @@ -207,8 +207,8 @@ def _setup_middleware(self, middleware_class: type, *args, **kwargs): @staticmethod def _register_handlers( - handler_factory_func: Callable[[], Dict[str, List[HandlerManager]]], - register_method: Callable, + handler_factory_func: Callable[[], Dict[str, List[HandlerManager]]], + register_method: Callable, ): """ Registers bot handlers using the provided factory function and registration method. @@ -230,7 +230,9 @@ def _register_handlers( f"Registered {sum(len(handlers) for handlers in handlers_dict.values())} handlers." ) except telebot.apihelper.ApiTelegramException as api_err: - bot_logger.error(f"Failed to register handlers: {sanitize_exception(api_err)}") + bot_logger.error( + f"Failed to register handlers: {sanitize_exception(api_err)}" + ) except Exception as err: bot_logger.exception(f"Unexpected error while registering handlers: {err}") @@ -240,6 +242,7 @@ def _start_webhook_mode(self): """ try: from pytmbot.webhook import WebhookServer + bot_logger.info("Starting webhook mode...") webhook_settings = settings.webhook_config @@ -256,7 +259,8 @@ def _start_webhook_mode(self): # Set the webhook self._set_webhook( webhook_url, - certificate_path=settings.webhook_config.cert[0].get_secret_value() or None + certificate_path=settings.webhook_config.cert[0].get_secret_value() + or None, ) bot_logger.info("Webhook successfully set.") @@ -265,7 +269,7 @@ def _start_webhook_mode(self): self.bot, self.bot.token, self.args.socket_host, - webhook_settings.local_port[0] + webhook_settings.local_port[0], ) webhook_server.run() except ImportError as import_error: @@ -273,7 +277,9 @@ def _start_webhook_mode(self): except ValueError as value_error: bot_logger.exception(f"Failed to start webhook server: {value_error}") except Exception as error: - bot_logger.exception(f"Unexpected error while starting webhook: {sanitize_exception(error)}") + bot_logger.exception( + f"Unexpected error while starting webhook: {sanitize_exception(error)}" + ) exit(1) def _set_webhook(self, webhook_url: str, certificate_path: str = None): @@ -281,13 +287,9 @@ def _set_webhook(self, webhook_url: str, certificate_path: str = None): self.bot.set_webhook( url=webhook_url, timeout=20, - allowed_updates=[ - "message", - "callback_query" - ], + allowed_updates=["message", "callback_query"], drop_pending_updates=True, certificate=certificate_path, - ) except telebot.apihelper.ApiTelegramException as error: bot_logger.error(f"Failed to set webhook: {sanitize_exception(error)}") @@ -303,7 +305,7 @@ def start_bot_instance(self) -> None: bot_instance = self._create_bot_instance() bot_logger.info("Starting bot...") - if self.args.webhook == 'True': + if self.args.webhook == "True": self._start_webhook_mode_with_error_handling() else: self._start_polling_mode_with_error_handling(bot_instance) @@ -327,7 +329,9 @@ def _start_polling_mode_with_error_handling(self, bot_instance) -> None: try: self._start_polling_mode(bot_instance) except Exception as error: - bot_logger.error(f"Unexpected error while starting polling mode: {error}. Exiting...") + bot_logger.error( + f"Unexpected error while starting polling mode: {error}. Exiting..." + ) exit(1) @staticmethod @@ -349,34 +353,39 @@ def _start_polling_mode(bot_instance: TeleBot): current_sleep_time = base_sleep_time except ssl.SSLError as ssl_error: bot_logger.critical( - f"SSL error (potential security issue): {sanitize_exception(ssl_error)}. Shutting down.") + f"SSL error (potential security issue): {sanitize_exception(ssl_error)}. Shutting down." + ) raise ssl_error except telebot.apihelper.ApiTelegramException as t_error: bot_logger.error( - f"Polling failed: {sanitize_exception(t_error)}. Retrying in {current_sleep_time} seconds.") + f"Polling failed: {sanitize_exception(t_error)}. Retrying in {current_sleep_time} seconds." + ) time.sleep(current_sleep_time) current_sleep_time = min(current_sleep_time * 2, max_sleep_time) except ( - urllib3.exceptions.ConnectionError, - urllib3.exceptions.ReadTimeoutError, - requests.exceptions.ConnectionError, - requests.exceptions.ConnectTimeout, - urllib3.exceptions.MaxRetryError, - urllib3.exceptions.NameResolutionError, - OSError, + urllib3.exceptions.ConnectionError, + urllib3.exceptions.ReadTimeoutError, + requests.exceptions.ConnectionError, + requests.exceptions.ConnectTimeout, + urllib3.exceptions.MaxRetryError, + urllib3.exceptions.NameResolutionError, + OSError, ) as conn_error: bot_logger.error( - f"Connection error: {sanitize_exception(conn_error)}. Retrying in {current_sleep_time} seconds.") + f"Connection error: {sanitize_exception(conn_error)}. Retrying in {current_sleep_time} seconds." + ) time.sleep(current_sleep_time) current_sleep_time = min(current_sleep_time * 2, max_sleep_time) except telebot.apihelper.ApiException as api_error: bot_logger.error( - f"API error: {sanitize_exception(api_error)}. Retrying in {current_sleep_time} seconds.") + f"API error: {sanitize_exception(api_error)}. Retrying in {current_sleep_time} seconds." + ) time.sleep(current_sleep_time) current_sleep_time = min(current_sleep_time * 2, max_sleep_time) except Exception as error: bot_logger.exception( - f"Unexpected error: {sanitize_exception(error)}. Retrying in {current_sleep_time} seconds.") + f"Unexpected error: {sanitize_exception(error)}. Retrying in {current_sleep_time} seconds." + ) time.sleep(current_sleep_time) current_sleep_time = min(current_sleep_time * 2, max_sleep_time) diff --git a/pytmbot/utils/utilities.py b/pytmbot/utils/utilities.py index 4dcb6f9c..8f288a09 100644 --- a/pytmbot/utils/utilities.py +++ b/pytmbot/utils/utilities.py @@ -50,8 +50,8 @@ def parse_cli_args() -> argparse.Namespace: parser.add_argument( "--webhook", - choices=['True', 'False'], - default='False', + choices=["True", "False"], + default="False", help="Start in webhook mode", ) @@ -169,7 +169,7 @@ def get_emoji(self, emoji_name: str) -> str: def split_string_into_octets( - input_string: str, delimiter: str = ":", octet_index: int = 1 + input_string: str, delimiter: str = ":", octet_index: int = 1 ) -> str: """ Extracts a specific octet from a string based on a delimiter. @@ -193,7 +193,7 @@ def split_string_into_octets( def sanitize_logs( - container_logs: Union[str, Any], callback_query: CallbackQuery, token: str + container_logs: Union[str, Any], callback_query: CallbackQuery, token: str ) -> str: """ Sanitizes Docker container logs by replacing sensitive user information @@ -255,7 +255,7 @@ def get_message_full_info(*args: Any, **kwargs: Any) -> Tuple[ def get_inline_message_full_info( - *args: Any, **kwargs: Any + *args: Any, **kwargs: Any ) -> Tuple[Union[str, None], Union[int, None], Union[bool, None]]: """ Retrieves full information for inline handlers logs. diff --git a/pytmbot/webhook.py b/pytmbot/webhook.py index 6e5b46d0..31b8b0ed 100644 --- a/pytmbot/webhook.py +++ b/pytmbot/webhook.py @@ -88,12 +88,15 @@ def run(self): """ if self.port == 80: bot_logger.critical( - "Cannot run webhook server on port 80 for security reasons. Use reverse proxy instead.") + "Cannot run webhook server on port 80 for security reasons. Use reverse proxy instead." + ) raise PyTMBotError( - "Cannot run webhook server on port 80 for security reasons. Use reverse proxy instead.") + "Cannot run webhook server on port 80 for security reasons. Use reverse proxy instead." + ) bot_logger.info( - f"Starting FastAPI webhook server on {self.host}:{self.port}...") + f"Starting FastAPI webhook server on {self.host}:{self.port}..." + ) try: uvicorn.run( @@ -104,7 +107,7 @@ def run(self): ssl_keyfile=settings.webhook_config.cert_key[0].get_secret_value(), log_level="critical", use_colors=True, - ) + ) except Exception as e: bot_logger.critical(f"Failed to start FastAPI server: {e}") raise diff --git a/tests/test_psutil_adapter.py b/tests/test_psutil_adapter.py index f76260de..88e5d93b 100644 --- a/tests/test_psutil_adapter.py +++ b/tests/test_psutil_adapter.py @@ -62,7 +62,7 @@ def test_get_memory(self, mock_set_naturalsize, mock_virtual_memory): @patch("psutil.disk_usage") @patch("pytmbot.utils.utilities.set_naturalsize") def test_get_disk_usage( - self, mock_set_naturalsize, mock_disk_usage, mock_disk_partitions + self, mock_set_naturalsize, mock_disk_usage, mock_disk_partitions ): """ Test the get_disk_usage method. diff --git a/tools/install.sh b/tools/install.sh index 8071689f..6f33421f 100644 --- a/tools/install.sh +++ b/tools/install.sh @@ -69,7 +69,7 @@ show_banner() { echo -e "${CYAN}${border_style}" echo -e "${WHITE}Starting the ${YELLOW}$(echo "${action}" | tr '[:lower:]' '[:upper:]')${WHITE} process...${NC}" echo -e "${CYAN}${border_style}" - echo -e "${WHITE}Version: 0.2.0-rc3${NC}" + echo -e "${WHITE}Version: 0.2.0${NC}" echo -e "${WHITE}Follow us on GitHub: https://github.com/orenlab/pytmbot${NC}" echo -e "${CYAN}${border_style}" echo ""