Fix: Telegram Bot Crash On Download For Users Without Username

by Alex Johnson 63 views

Introduction

This article addresses a critical bug in the MoviePilot application that causes it to crash when a user without a public Telegram username attempts to download media via the Telegram Bot. The issue stems from a Pydantic validation error that occurs when the application incorrectly handles user IDs. This article provides a detailed explanation of the problem, the steps to reproduce it, the expected and actual behaviors, and the underlying cause. Understanding and resolving this issue is crucial for ensuring the stability and usability of MoviePilot for all users, regardless of their Telegram username settings.

Confirmation

  • [x] My version is the latest version, and my version number is the same as version.
  • [x] I have searched in issue and confirmed that my question has not been raised.
  • [x] I have searched in Telegram channel and confirmed that my question has not been raised.
  • [x] I have modified the title and replaced the description in the title with the problem I encountered.

Current Program Version

v2.8.7

Running Environment

Docker

Problem Type

Main program running issue

Problem Description

Bug Description

The core of the issue lies in how MoviePilot handles Telegram users who haven't set up a public username (@username). When such a user searches for media using the Telegram Bot and clicks a download selection button (like "Download 1"), the MoviePilot backend service encounters an unhandled pydantic_core.ValidationError. This validation error leads to the service crashing and restarting, disrupting the user's experience and potentially affecting other operations. Let's dive deeper into the technical aspects to fully grasp the problem.

At the heart of this bug is a data type mismatch during the creation of a Notification object. The application inadvertently passes the user's integer-based userid to the username field, which is designed to accept only string values. This mismatch triggers Pydantic's data model validation, resulting in the aforementioned pydantic_core.ValidationError. Pydantic, a widely-used Python library for data validation and settings management, enforces strict type checking to ensure data integrity. In this case, it correctly identifies that an integer is being passed where a string is expected, leading to the validation failure.

This seemingly small oversight has significant consequences. The crash not only interrupts the download process for the user but also reflects a broader issue of error handling within the application. A robust application should be able to gracefully handle unexpected scenarios, such as missing usernames, without crashing. Instead, MoviePilot's current implementation lacks this crucial error handling, making it vulnerable to crashes under specific user conditions. Addressing this bug is therefore essential to improve the application's overall reliability and user experience. By implementing proper error handling and data validation, MoviePilot can become more resilient and user-friendly, providing a smoother experience for all users, regardless of their Telegram settings.

Steps to Reproduce

  1. Use a Telegram account that does not have a public @username set.
  2. Search for any movie or TV series in the MoviePilot Bot.
  3. In the returned search results message with buttons, click any download button (e.g., "Download 1").
  4. Observe the backend logs; the MoviePilot service will immediately crash and throw a pydantic_core.ValidationError exception.

Expected Behavior

Ideally, even if a user hasn't set a public Telegram username, clicking the download button should initiate the download process and send appropriate notifications without causing the program to crash. The application should gracefully handle the scenario where the username is None, perhaps by using the string representation of the userid as an alternative. This involves implementing error handling mechanisms that can catch the absence of a username and proceed with the download using the available user identifier, such as the userid. This approach would ensure that all users, regardless of their username settings, can seamlessly use the download functionality.

Furthermore, a robust system should provide informative feedback to the user even if an error occurs behind the scenes. Instead of silently failing, the application could display a message indicating that the download process has been initiated or, if there's an issue, provide a user-friendly error message suggesting possible solutions or workarounds. This level of user-centric design is essential for building trust and ensuring a positive experience, even in the face of technical challenges. By implementing these measures, MoviePilot can significantly enhance its reliability and user satisfaction.

Actual Behavior

The program crashes, leading to service interruption. The logs display an uncaught pydantic_core.ValidationError. As shown in the image, clicking the download selection button results in no response, and the logs indicate a specific error.

This behavior highlights a critical issue in the application's error handling mechanism. The crash, caused by the pydantic_core.ValidationError, not only interrupts the intended download process but also indicates a lack of robustness in the application's design. An ideal application should be capable of gracefully handling such validation errors without crashing, ensuring that the user experience remains uninterrupted. The fact that the program crashes outright points to a need for improved error handling and data validation practices within the codebase.

To address this, developers should implement try-except blocks to catch potential ValidationError exceptions and handle them appropriately. This might involve logging the error for debugging purposes, displaying a user-friendly error message, or attempting an alternative approach to retrieve the user's identification. By implementing such measures, MoviePilot can become more resilient to unexpected input and provide a smoother, more reliable experience for its users. The goal is to ensure that even in the face of unforeseen issues, the application continues to function without crashing, maintaining its utility and user satisfaction.

Image

System Logs and Configuration Files During the Problem

moviepilot-v2  | INFO:     <SENSITIVE_IP_ADDRESS>:0 - "GET /api/v1/dashboard/downloader HTTP/1.1" 200 OK
moviepilot-v2  | INFO:     <SENSITIVE_IP_ADDRESS>:0 - "GET /api/v1/dashboard/downloader HTTP/1.1" 200 OK
moviepilot-v2  | INFO:     <SENSITIVE_IP_ADDRESS>:0 - "GET /api/v1/dashboard/downloader HTTP/1.1" 200 OK
moviepilot-v2  | INFO:    [moviepilot] 2025-12-01 14:38:30,654 telegram.py - 收到按钮回调:download_1,用户:<SENSITIVE_USER_ID>
moviepilot-v2  | INFO:     127.0.0.1:44738 - "POST /api/v1/message?token=<SENSITIVE_API_TOKEN>&source=%E4%BA%91%E6%B5%B7 HTTP/1.1" 307 Temporary Redirect
moviepilot-v2  | INFO:     127.0.0.1:44738 - "POST /api/v1/message/?token=<SENSITIVE_API_TOKEN>&source=%E4%BA%91%E6%B5%B7 HTTP/1.1" 200 OK
moviepilot-v2  | INFO:    [moviepilot] 2025-12-01 14:38:31,329 telegram - 收到来自 <SENSITIVE_USER_NICKNAME> 的Telegram按钮回调:userid=<SENSITIVE_USER_ID>, username=None, callback_data=download_1
moviepilot-v2  | INFO:    [moviepilot] 2025-12-01 14:38:31,329 message.py - 收到用户消息内容,用户:<SENSITIVE_USER_ID>,内容:CALLBACK:download_1
moviepilot-v2  | INFO:    [moviepilot] 2025-12-01 14:38:31,330 message.py - 处理按钮回调:download_1
moviepilot-v2  | INFO:    [moviepilot] 2025-12-01 14:38:31,331 message.py - 收到用户消息内容,用户:<SENSITIVE_USER_ID>,内容:1
moviepilot-v2  | INFO:     <SENSITIVE_IP_ADDRESS>:0 - "GET /api/v1/dashboard/downloader HTTP/1.1" 200 OK
moviepilot-v2  | ERROR:    Exception in ASGI application
moviepilot-v2  | Traceback (most recent call last):
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
moviepilot-v2  |     result = await app(  # type: ignore[func-returns-value]
moviepilot-v2  |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
moviepilot-v2  |     return await self.app(scope, receive, send)
moviepilot-v2  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/fastapi/applications.py", line 1054, in __call__
moviepilot-v2  |     await super().__call__(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__
moviepilot-v2  |     await self.middleware_stack(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
moviepilot-v2  |     raise exc
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
moviepilot-v2  |     await self.app(scope, receive, _send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/middleware/cors.py", line 85, in __call__
moviepilot-v2  |     await self.app(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
moviepilot-v2  |     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
moviepilot-v2  |     raise exc
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
moviepilot-v2  |     await app(scope, receive, sender)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/routing.py", line 714, in __call__
moviepilot-v2  |     await self.middleware_stack(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/routing.py", line 734, in app
moviepilot-v2  |     await route.handle(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
moviepilot-v2  |     await self.app(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
moviepilot-v2  |     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
moviepilot-v2  |     raise exc
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
moviepilot-v2  |     await app(scope, receive, sender)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/routing.py", line 74, in app
moviepilot-v2  |     await response(scope, receive, send)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/responses.py", line 160, in __call__
moviepilot-v2  |     await self.background()
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/background.py", line 41, in __call__
moviepilot-v2  |     await task()
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/background.py", line 28, in __call__
moviepilot-v2  |     await run_in_threadpool(self.func, *self.args, **self.kwargs)
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/starlette/concurrency.py", line 37, in run_in_threadpool
moviepilot-v2  |     return await anyio.to_thread.run_sync(func)
moviepilot-v2  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/anyio/to_thread.py", line 61, in run_sync
moviepilot-v2  |     return await get_async_backend().run_sync_in_worker_thread(
moviepilot-v2  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread
moviepilot-v2  |     return await future
moviepilot-v2  |            ^^^^^^^^^^^^
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 986, in run
moviepilot-v2  |     result = context.run(func, *args)
moviepilot-v2  |              ^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  |   File "/app/app/api/endpoints/message.py", line 29, in start_message_chain
moviepilot-v2  |     MessageChain().process(body=body, form=form, args=args)
moviepilot-v2  |   File "/app/app/chain/message.py", line 131, in process
moviepilot-v2  |     self.handle_message(channel=channel, source=source, userid=userid, username=username, text=text,
moviepilot-v2  |   File "/app/app/chain/message.py", line 169, in handle_message
moviepilot-v2  |     self._handle_callback(text=text, channel=channel, source=source,
moviepilot-v2  |   File "/app/app/chain/message.py", line 590, in _handle_callback
moviepilot-v2  |     self.handle_message(channel=channel, source=source, userid=userid, username=username,
moviepilot-v2  |   File "/app/app/chain/message.py", line 363, in handle_message
moviepilot-v2  |     DownloadChain().download_single(context, channel=channel, source=source,
moviepilot-v2  |   File "/app/app/chain/download.py", line 344, in download_single
moviepilot-v2  |     Notification(
moviepilot-v2  |   File "/opt/venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
moviepilot-v2  |     validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
moviepilot-v2  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
moviepilot-v2  | pydantic_core._pydantic_core.ValidationError: 1 validation error for Notification
moviepilot-v2  | username
moviepilot-v2  |   Input should be a valid string [type=string_type, input_value=<SENSITIVE_USER_ID>, input_type=int]
moviepilot-v2  |     For further information visit https://errors.pydantic.dev/2.12/v/string_type

The provided logs offer a detailed view into the sequence of events leading up to the crash, pinpointing the exact location where the error occurs. The logs clearly show that the pydantic_core.ValidationError is raised when the Notification object is being initialized. Specifically, the error message “Input should be a valid string [type=string_type, input_value=<SENSITIVE_USER_ID>, input_type=int]” indicates that the username field, which expects a string, is receiving an integer value, which is the user's ID. This discrepancy is the root cause of the crash.

By examining the traceback, we can trace the error back to the download_single function within the DownloadChain class. This function is responsible for creating the Notification object, and it's here that the incorrect data type is being passed. The logs also reveal that the issue arises specifically when handling a callback from the Telegram bot, triggered by a user clicking a download button. This narrows down the problem area and provides valuable context for developers to focus their debugging efforts.

Furthermore, the logs include sensitive information, such as IP addresses and user IDs, which have been anonymized in the provided excerpt. In a real-world debugging scenario, this information would be crucial for identifying the specific users and interactions that trigger the bug. By carefully analyzing these logs, developers can gain a comprehensive understanding of the error and develop an effective solution.

Root Cause Analysis

The root cause of this issue is that when a Telegram user without a public username interacts with the bot and triggers a download, the username field is being populated with the user's numerical ID instead of their username (which would be None or an empty string). Pydantic's validation then correctly flags this as an invalid type, leading to the ValidationError and the subsequent crash. This highlights a critical oversight in the application's data handling logic, specifically in how it processes user information received from the Telegram API.

The issue stems from the assumption that all Telegram users will have a public username. While this is often the case, it's not a mandatory setting for Telegram accounts. When a user doesn't set a username, the API might return None or omit the username field altogether. The MoviePilot application, in its current state, doesn't adequately handle these scenarios, leading to the type mismatch and the validation error. A more robust solution would involve checking for the presence of a username and, if it's missing, using an alternative identifier or a default value that is compatible with the username field's string type requirement.

This bug also underscores the importance of thorough input validation, especially when dealing with data from external sources like APIs. Pydantic's role in this scenario is to enforce data integrity, but it can only do so if the application is designed to handle potential validation failures gracefully. By implementing proper error handling and data sanitization, developers can prevent such crashes and ensure a more stable and user-friendly application. In this specific case, a simple check for the username's existence and type before creating the Notification object could have prevented the crash and allowed the application to continue functioning smoothly.

Proposed Solution

To resolve this issue, the following steps should be taken:

  1. Implement a Check for Username: Before creating the Notification object, check if the username from the Telegram API is None or empty.
  2. Use User ID as Fallback: If the username is None or empty, use the string representation of the userid as a fallback. This ensures that a valid string is always passed to the username field.
  3. Implement Error Handling: Add a try-except block to catch pydantic_core.ValidationError exceptions. This will prevent the program from crashing and allow for graceful handling of the error.
  4. Log the Error: Within the except block, log the error details for debugging purposes. This will help in identifying and resolving similar issues in the future.
  5. Notify the User: Optionally, send a user-friendly message to the Telegram user informing them that their download request is being processed, even if their username is not available. This provides feedback to the user and improves the overall experience.

By implementing these steps, MoviePilot can effectively address the bug and prevent future crashes caused by missing Telegram usernames. This will enhance the application's stability, reliability, and user-friendliness, ensuring a smoother experience for all users.

Conclusion

In conclusion, the crash caused by the pydantic_core.ValidationError when a user without a public Telegram username attempts to download media highlights the importance of robust error handling and input validation in application development. By implementing the proposed solution, MoviePilot can prevent this specific crash and improve its overall resilience to unexpected data inputs. This fix will not only enhance the user experience but also contribute to the long-term stability and maintainability of the application. Remember to always validate external input and handle potential errors gracefully to ensure a smooth and reliable user experience.

For more information on Pydantic and data validation in Python, visit the official Pydantic documentation at https://docs.pydantic.dev/.