Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Tweet Errors #202

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions twikit/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
Unauthorized,
UserNotFound,
UserUnavailable,
CreateTweetDuplicate,
CreateTweetMaxLengthReached,
raise_exceptions_from_response
)
from ..geo import Place, _places_from_response
Expand Down Expand Up @@ -1238,6 +1240,8 @@ async def create_tweet(
reply_to, attachment_url, community_id, share_with_followers,
richtext_options, edit_tweet_id, limit_mode
)
if not response['data']:
self._switch_error(response)
if is_note_tweet:
_result = response['data']['notetweet_create']['tweet_results']
else:
Expand Down Expand Up @@ -4237,3 +4241,21 @@ async def _update_subscriptions(
async def _get_user_state(self) -> Literal['normal', 'bounced', 'suspended']:
response, _ = await self.v11.user_state()
return response['userState']

def _switch_error(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider refactoring error handling logic

The current implementation of _switch_error mixes different responsibilities. Consider using a dictionary to map error messages to exception types, which would make it easier to maintain and extend in the future.

    def _switch_error(self, response: dict) -> None:
        error_map = {
            'InvalidCredentials': InvalidCredentialsError,
            'InvalidToken': InvalidTokenError,
            'Unauthorized': UnauthorizedError,
            # Add more mappings as needed
        }
        error_type = response.get('error', {}).get('type')
        if error_type in error_map:
            raise error_map[error_type](response['error']['message'])
        raise TwikitError(response['error']['message'])

self,
response: dict
):
error_map = {
186: CreateTweetDuplicate,
187: CreateTweetMaxLengthReached
}
message = response.get('errors', [])[0].get('message', '')
error_msg: str = message[message.index(": ")+2:message.index(". ")]

Comment on lines +4253 to +4255
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error message parsing to prevent exceptions

Extracting error_msg using string slicing with message.index can raise a ValueError if the expected substrings ": " or ". " are not found in the message. This can occur if the error message format changes or is different than expected. Consider using safer methods like str.partition, str.split, or handling exceptions to avoid unexpected crashes.

Apply this diff to make error parsing more robust:

 message = response.get('errors', [])[0].get('message', '')
-error_msg: str = message[message.index(": ")+2:message.index(". ")]
+colon_index = message.find(": ")
+period_index = message.find(". ")
+if colon_index != -1 and period_index != -1 and colon_index < period_index:
+    error_msg = message[colon_index + 2 : period_index]
+else:
+    error_msg = message
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message = response.get('errors', [])[0].get('message', '')
error_msg: str = message[message.index(": ")+2:message.index(". ")]
message = response.get('errors', [])[0].get('message', '')
colon_index = message.find(": ")
period_index = message.find(". ")
if colon_index != -1 and period_index != -1 and colon_index < period_index:
error_msg = message[colon_index + 2 : period_index]
else:
error_msg = message

error_code = response.get('errors', [])[0].get('extensions', {}).get('code', '')
if error_code in error_map:
raise error_map[error_code](error_msg)
Comment on lines +4253 to +4258
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for empty 'errors' list in response to avoid IndexError

The code assumes that response['errors'] contains at least one error. If the errors list is empty, accessing [0] will raise an IndexError. To prevent this, check if the list has elements before accessing the first one.

Apply this diff to safely access the error information:

-errors = response.get('errors', [])[0]
+errors = response.get('errors', [])
+if not errors:
+    # Handle case where errors list is empty
+    return
+error_info = errors[0]
-message = response.get('errors', [])[0].get('message', '')
+message = error_info.get('message', '')
+error_code = error_info.get('extensions', {}).get('code', '')

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +4256 to +4258
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure error codes are consistently typed for correct mapping

The error_code extracted from the response is likely a string, whereas the keys in error_map are integers. This can cause the condition if error_code in error_map: to fail even when the code exists in the map. Consider converting error_code to an integer or changing the keys in error_map to strings to ensure proper exception handling.

Apply this diff to ensure consistent data types:

 error_map = {
-    186: CreateTweetDuplicate,
-    187: CreateTweetMaxLengthReached
+    '186': CreateTweetDuplicate,
+    '187': CreateTweetMaxLengthReached
 }

 error_code = response.get('errors', [])[0].get('extensions', {}).get('code', '')

Committable suggestion skipped: line range outside the PR's diff.

# * Print <response> to reach unknown errors in 2024-09-04
print(response)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Replace print with logging for unknown errors

Using print for logging unknown errors is not ideal for production code. Consider using a proper logging mechanism instead, which would provide more control over log levels and output destinations.

import logging

logger = logging.getLogger(__name__)

# ... (in the relevant function)
logger.error(f"Unknown error occurred: {response}")


15 changes: 15 additions & 0 deletions twikit/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ class AccountLocked(TwitterException):
Exception raised when the account is locked (very likey is Arkose challenge).
"""

class CreateTweetFailed(TwitterException):
"""
Exception raised when create_tweet fails.
"""

class CreateTweetDuplicate(CreateTweetFailed):
"""
Exception raised when create_tweet create a duplicate tweet in a short period of time.
"""

class CreateTweetMaxLengthReached(CreateTweetFailed):
"""
Exception raised when create_tweet tries to create a tweet that exceeds the maximum allowed length. (240)
"""

ERROR_CODE_TO_EXCEPTION: dict[int, TwitterException] = {
187: DuplicateTweet,
324: InvalidMedia
Expand Down