Skip to content

Handle long tweets via threading #250

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

Merged
merged 1 commit into from
May 29, 2025
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ aibtcdev-backend/

### 3. Social Media Integration
- Twitter automation and monitoring
- Tweets longer than 280 characters are automatically threaded
- Telegram bot integration
- Discord notifications
- Automated content generation
Expand Down
86 changes: 48 additions & 38 deletions services/runner/tasks/tweet_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ def __init__(self, config: Optional[RunnerConfig] = None):
self._pending_messages: Optional[List[QueueMessage]] = None
self.twitter_service = None

def _split_text_into_chunks(self, text: str, limit: int = 280) -> List[str]:
"""Split text into chunks not exceeding the limit without cutting words."""
words = text.split()
chunks = []
current = ""
for word in words:
if len(current) + len(word) + (1 if current else 0) <= limit:
current = f"{current} {word}".strip()
else:
if current:
chunks.append(current)
current = word
if current:
chunks.append(current)
return chunks

def _get_extension(self, url: str) -> str:
path = urlparse(url).path.lower()
for ext in [".png", ".jpg", ".jpeg", ".gif"]:
Expand All @@ -56,9 +72,7 @@ def _post_tweet_with_media(
reply_id: Optional[str] = None,
):
try:
headers = {
"User-Agent": "Mozilla/5.0"
}
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(image_url, headers=headers, timeout=10)
response.raise_for_status()
auth = tweepy.OAuth1UserHandler(
Expand Down Expand Up @@ -201,15 +215,6 @@ async def _validate_message(
dao_id=None,
)

# Check tweet length
if len(tweet_text) > 280: # Twitter's character limit
return TweetProcessingResult(
success=False,
message=f"Tweet exceeds character limit: {len(tweet_text)} chars",
tweet_id=message.tweet_id,
dao_id=message.dao_id,
)

# No need to modify the message structure, keep it as is
return None

Expand Down Expand Up @@ -257,30 +262,35 @@ async def _process_tweet_message(
tweet_text = re.sub(re.escape(image_url), "", original_text).strip()
tweet_text = re.sub(r"\s+", " ", tweet_text)

# Prepare tweet parameters
tweet_params = {"text": tweet_text}
if message.tweet_id:
tweet_params["reply_in_reply_to_tweet_id"] = message.tweet_id

if image_url:
tweet_response = self._post_tweet_with_media(
image_url=image_url,
text=tweet_text,
reply_id=message.tweet_id,
)
else:
tweet_response = await self.twitter_service._apost_tweet(**tweet_params)
# Split tweet text if necessary
chunks = self._split_text_into_chunks(tweet_text)
previous_tweet_id = message.tweet_id
tweet_response = None

for index, chunk in enumerate(chunks):
if index == 0 and image_url:
tweet_response = self._post_tweet_with_media(
image_url=image_url,
text=chunk,
reply_id=previous_tweet_id,
)
else:
tweet_response = await self.twitter_service._apost_tweet(
text=chunk,
reply_in_reply_to_tweet_id=previous_tweet_id,
)

if not tweet_response:
return TweetProcessingResult(
success=False,
message="Failed to send tweet",
dao_id=message.dao_id,
tweet_id=message.tweet_id,
)
if not tweet_response:
return TweetProcessingResult(
success=False,
message="Failed to send tweet",
dao_id=message.dao_id,
tweet_id=previous_tweet_id,
)

logger.info(f"Successfully posted tweet {tweet_response.id}")
logger.debug(f"Tweet ID: {tweet_response.id}")
logger.info(f"Successfully posted tweet {tweet_response.id}")
logger.debug(f"Tweet ID: {tweet_response.id}")
previous_tweet_id = tweet_response.id

# Discord Service
try:
Expand All @@ -290,18 +300,18 @@ async def _process_tweet_message(
embeds = None
if image_url:
embeds = [{"image": {"url": image_url}}]
discord_result = discord_service.send_message(tweet_text, embeds=embeds)
logger.info(
f"Discord message sent: {discord_result['success']}"
discord_result = discord_service.send_message(
tweet_text, embeds=embeds
)
logger.info(f"Discord message sent: {discord_result['success']}")

except Exception as e:
logger.warning(f"Failed to send Discord message: {str(e)}")

return TweetProcessingResult(
success=True,
message="Successfully sent tweet",
tweet_id=tweet_response.id,
tweet_id=previous_tweet_id,
dao_id=message.dao_id,
)

Expand Down