-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
ℹ️ => Fetching songs...
============ OPTIONS ============
- YouTube Shorts Automation
- Twitter Bot
- Affiliate Marketing
- Outreach
- Quit
=================================
Select an option: 2
ℹ️ Starting Twitter Bot...
+----+--------------------------------------+----------+-------------------+
| ID | UUID | Nickname | Account Topic |
+----+--------------------------------------+----------+-------------------+
| 1 | 4568db8e-8f05-4238-96d1-f0af9ebaff61 | rtts | music pop culture |
+----+--------------------------------------+----------+-------------------+
❓ Select an account to start: 1
============ OPTIONS ============
- Post something
- Reply to something
- Show all Posts
- Setup CRON Job
- Quit
=================================
❓ Select an option: 1
ℹ️ Generating a post...
ℹ️ Length of post: 269
ℹ️ Generating a post...
ℹ️ Length of post: 225
=> Posting to Twitter: 🎶✨ The resurgence of 90s pop i...
Traceback (most recent call last):
File "/Users/acetwotimes/MoneyPrinterV2/src/classes/Twitter.py", line 83, in post
bot.find_element(By.XPATH, "//a[@data-testid='SideNav_NewTweet_Button']").click()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/webdriver.py", line 888, in find_element
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/webdriver.py", line 429, in execute
self.error_handler.check_response(response)
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/errorhandler.py", line 232, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: //a[@data-testid='SideNav_NewTweet_Button']; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
RemoteError@chrome://remote/content/shared/RemoteError.sys.mjs:8:8
WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:197:5
NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:527:5
dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/acetwotimes/MoneyPrinterV2/src/main.py", line 438, in
main()
File "/Users/acetwotimes/MoneyPrinterV2/src/main.py", line 277, in main
twitter.post()
File "/Users/acetwotimes/MoneyPrinterV2/src/classes/Twitter.py", line 86, in post
bot.find_element(By.XPATH, "//a[@data-testid='SideNav_NewTweet_Button']").click()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/webdriver.py", line 888, in find_element
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/webdriver.py", line 429, in execute
self.error_handler.check_response(response)
File "/usr/local/anaconda3/lib/python3.11/site-packages/selenium/webdriver/remote/errorhandler.py", line 232, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: //a[@data-testid='SideNav_NewTweet_Button']; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
RemoteError@chrome://remote/content/shared/RemoteError.sys.mjs:8:8
WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:197:5
NoSuchElementError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:527:5
dom.find/</<@chrome://remote/content/shared/DOM.sys.mjs:136:16
##########################################################################
❓ Select an option: 3
+----+----------------------+-------------------------------------------------------------------+
| ID | Date | Content |
+----+----------------------+-------------------------------------------------------------------+
| 1 | 02/16/2025, 03:04:45 | 🎶✨ The resurgence of 90s pop icons in today's music scene is... |
+----+----------------------+-------------------------------------------------------------------+
============ OPTIONS ============
- Post something
- Reply to something
- Show all Posts
- Setup CRON Job
- Quit
=================================
❓ Select an option: 1
ℹ️ Generating a post...
ℹ️ Length of post: 208
=> Preparing to post on Twitter: 🎶✨ The resurgence of 90s pop i...
Failed to find the text box element.
Tweet content (printed to terminal):
🎶✨ The resurgence of 90s pop icons in today's music scene is a nostalgic treat for fans! From remixes to surprise collaborations, it's a reminder that great music never truly fades away. #90sPop #MusicRevival
x#########################################################################
so i can generate but not type or find the buttons or boxes, even tried flipping the cod so i can type first...didnt work either . any suggestions? ive been stuck on this part for a imnute, and ive done all the fixes ive found so far including using css selector . i get nada.
twitter.py code
import re
import g4f
import sys
import os
import json
from cache import *
from config import *
from status import *
from constants import *
from typing import List
from datetime import datetime
from termcolor import colored
from selenium_firefox import *
from selenium import webdriver
from selenium.common import exceptions
from selenium.webdriver.common import keys
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from webdriver_manager.firefox import GeckoDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
Import joblib for parallel processing
from joblib import Parallel, delayed
class Twitter:
"""
Class for the Bot that grows a Twitter account.
"""
def init(self, account_uuid: str, account_nickname: str, fp_profile_path: str, topic: str) -> None:
"""
Initializes the Twitter Bot.
Args:
account_uuid (str): The account UUID
account_nickname (str): The account nickname
fp_profile_path (str): The path to the Firefox profile
topic (str): The topic to generate posts about
Returns:
None
"""
self.account_uuid: str = account_uuid
self.account_nickname: str = account_nickname
self.fp_profile_path: str = fp_profile_path
self.topic: str = topic
# Initialize the Firefox options
self.options: Options = Options()
# Set headless state of browser if enabled
if get_headless():
self.options.add_argument("--headless")
# Set the Firefox profile path
self.options.set_preference("profile", fp_profile_path)
# Initialize the Firefox service
self.service: Service = Service(GeckoDriverManager().install())
# Initialize the browser
self.browser: webdriver.Firefox = webdriver.Firefox(service=self.service, options=self.options)
# Initialize the wait instance
self.wait: WebDriverWait = WebDriverWait(self.browser, 40)
def post(self, text: str = None) -> None:
"""
Starts the Twitter Bot.
Args:
text (str): The text to post
Returns:
None
"""
bot: webdriver.Firefox = self.browser
verbose: bool = get_verbose()
bot.get("https://x.com/compose/post")
post_content: str = self.generate_post()
now: datetime = datetime.now()
# Show a preview of the tweet content
print(colored(" => Preparing to post on Twitter:", "blue"), post_content[:30] + "...")
# Determine the tweet content (either generated or provided)
body = post_content if text is None else text
# Try to find the text box and type the content
text_box = None
selectors = [
(By.CSS_SELECTOR, "div.notranslate.public-DraftEditor-content[role='textbox']"),
(By.XPATH, "//div[@data-testid='tweetTextarea_0']//div[@role='textbox']")
]
for selector in selectors:
try:
text_box = self.wait.until(EC.element_to_be_clickable(selector))
text_box.click()
text_box.send_keys(body)
break
except exceptions.TimeoutException:
continue
# If the text box wasn't found, print error, cache the post, and exit gracefully
if text_box is None:
print(colored("Failed to find the text box element.", "red"))
print(colored("Tweet content (printed to terminal):", "yellow"))
print(body)
self.add_post({
"content": post_content,
"date": now.strftime("%m/%d/%Y, %H:%M:%S")
})
return
# Try to find the "Post" button and click it
tweet_button = None
selectors = [
(By.XPATH, "//span[contains(@class, 'css-1jxf684') and text()='Post']"),
(By.XPATH, "//*[text()='Post']")
]
for selector in selectors:
try:
tweet_button = self.wait.until(EC.element_to_be_clickable(selector))
tweet_button.click()
break
except exceptions.TimeoutException:
continue
# If the tweet button wasn't found, print error, cache the post, and exit gracefully
if tweet_button is None:
print(colored("Failed to find the tweet button element.", "red"))
print(colored("Tweet content (printed to terminal):", "yellow"))
print(body)
self.add_post({
"content": post_content,
"date": now.strftime("%m/%d/%Y, %H:%M:%S")
})
return
if verbose:
print(colored(" => Pressed [ENTER] Button on Twitter..", "blue"))
# Wait for confirmation that the tweet has been posted
self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@data-testid='tweetButton']")))
# Add the post to the cache
self.add_post({
"content": post_content,
"date": now.strftime("%m/%d/%Y, %H:%M:%S")
})
success("Posted to Twitter successfully!")
def get_posts(self) -> List[dict]:
"""
Gets the posts from the cache.
Returns:
posts (List[dict]): The posts
"""
if not os.path.exists(get_twitter_cache_path()):
# Create the cache file if it doesn't exist
with open(get_twitter_cache_path(), 'w') as file:
json.dump({"posts": []}, file, indent=4)
with open(get_twitter_cache_path(), 'r') as file:
parsed = json.load(file)
# Find our account and its posts
accounts = parsed.get("accounts", [])
for account in accounts:
if account["id"] == self.account_uuid:
posts = account.get("posts", [])
return posts
return []
def add_post(self, post: dict) -> None:
"""
Adds a post to the cache.
Args:
post (dict): The post to add
Returns:
None
"""
posts = self.get_posts()
posts.append(post)
with open(get_twitter_cache_path(), 'r') as file:
previous_json = json.load(file)
# Find our account and append the new post
accounts = previous_json.get("accounts", [])
for account in accounts:
if account["id"] == self.account_uuid:
account["posts"].append(post)
# Commit changes to the cache
with open(get_twitter_cache_path(), "w") as f:
json.dump(previous_json, f, indent=4)
def generate_post(self) -> str:
"""
Generates a post for the Twitter account based on the topic.
Returns:
post (str): The post
"""
completion = g4f.ChatCompletion.create(
model=parse_model(get_model()),
messages=[
{
"role": "user",
"content": f"Generate a Twitter post about: {self.topic} in {get_twitter_language()}. "
"The limit is 2 sentences. Choose a specific sub-topic of the provided topic."
}
]
)
if get_verbose():
info("Generating a post...")
if completion is None:
error("Failed to generate a post. Please try again.")
sys.exit(1)
# Remove asterisks and quotes from the generated content
completion = re.sub(r"\*", "", completion).replace("\"", "")
if get_verbose():
info(f"Length of post: {len(completion)}")
# Instead of recursively generating a new post, trim it if it's too long.
max_length = 260
if len(completion) > max_length:
# Optionally, you could trim on a word boundary instead of a strict character limit.
trimmed = completion[:max_length].rsplit(" ", 1)[0] + "..."
if get_verbose():
info(f"Trimmed post to {len(trimmed)} characters.")
return trimmed
return completion