import re
import mysql.connector
import time
import logging
import random
import threading
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from seleniumwire import webdriver
import os
from selenium.common.exceptions import StaleElementReferenceException, NoSuchElementException, TimeoutException
import subprocess
import mysql.connector
import re
import os
import glob
from licensing.models import *
from licensing.methods import Key, Helpers
import sys # Import the sys module to use sys.exit
import traceback
from selenium.webdriver.common.action_chains import ActionChains
import threading
from datetime import datetime, timedelta, time as dt_time
import time
import threading
import os
import mysql.connector
# Path to the config file
CONFIG_FILE_PATH = r'C:\xampp\htdocs\config.php'
# Read the config file and extract the $licenseKey value
with open(CONFIG_FILE_PATH, 'r') as file:
config_content = file.read()
match = re.search(r'\$licenseKey\s*=\s*[\'"]([^\'"]+)[\'"];', config_content)
if match:
license_key = match.group(1)
print(f"Extracted License Key: {license_key}") # Debugging line
else:
print("License key not found in the config file.")
sys.exit(1) # Exit the script if the license key is not found
# The RSA public key and auth token
RSAPubKey = "<RSAKeyValue><Modulus>zFmPPgQGVFfb6FwwJPB4akxsI8OfNbZZyTgwJCIZ+TBBjezfoA0TriJm4Jq1S4tsEiId3u/455Vqq39aQfHIW8RK7i64Xqtf6uDcdzYUQglmTjZ4JCYsS14ksTOEyLezLzFMZH9z65qGz3BdDz5OsKcEHwbW6YKIajDic5CAfTTaxYseGRUYkYpXr6wRhThRnF4al/PHPh9f4q4bVWD1wbwesQ1W490DQ4wyW3eY3RyhsTyejTEH34peVm1feWsCR2fz+uoE6BV6BBiBgR7gFm4at5AVWGvmVrnPnf7ECQEkJeKBsuypGb6UDtt9JW4agNZiADtjHF3eymoSs5QCfQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
auth = "WyIxMDEyOTY3ODciLCJxb3ZmRy8veHFyZVZPcUdjbC9iMTR2ZEtqRWg2aERrbGRmNnpZeTIrIl0="
# Call the activation function with the extracted license key
result = Key.activate(token=auth,
rsa_pub_key=RSAPubKey,
product_id=28530,
key=license_key,
machine_code=Helpers.GetMachineCode(),
floating_time_interval=100)
if result[0] is None or not Helpers.IsOnRightMachine(result[0], is_floating_license=True):
# An error occurred or the key is invalid or it cannot be activated
# (e.g., the limit of activated devices was achieved)
print("The license does not work: Key may be incorrect")
sys.exit(1) # Exit the script if the license does not work
else:
# Everything went fine if we are here!
print("The license is valid!")
# Path to the config.php (update with the correct path if needed)
CONFIG_FILE_PATH = r'C:\xampp\htdocs\config.php'
# Function to extract database connection details from config.php
def get_db_details_from_php():
db_details = {
"host": None,
"name": None,
"user": None,
"password": None
}
try:
with open(CONFIG_FILE_PATH, 'r') as file:
content = file.read()
# Extract database connection details from config.php
match_db_host = re.search(r'\$dbHost\s*=\s*\'([^\']+)\'', content)
if match_db_host:
db_details['host'] = match_db_host.group(1)
match_db_name = re.search(r'\$dbName\s*=\s*\'([^\']+)\'', content)
if match_db_name:
db_details['name'] = match_db_name.group(1)
match_db_user = re.search(r'\$dbUser\s*=\s*\'([^\']+)\'', content)
if match_db_user:
db_details['user'] = match_db_user.group(1)
match_db_pass = re.search(r'\$dbPass\s*=\s*\'([^\']+)\'', content)
if match_db_pass:
db_details['password'] = match_db_pass.group(1)
except Exception as e:
print(f"Error reading config.php: {e}")
return None
# Check if all required values are found
if not all(db_details.values()):
print("Some required database details are missing from the config.php file.")
return None
return db_details
# Step 1: Parse the config.php file to extract MySQL connection credentials
def parse_config_php(config_path):
db_credentials = {'dbHost': '', 'dbName': '', 'dbUser': '', 'dbPass': ''}
try:
with open(config_path, 'r') as file:
content = file.read()
db_credentials['dbHost'] = re.search(r"\$dbHost\s*=\s*'(.*?)';", content).group(1)
db_credentials['dbName'] = re.search(r"\$dbName\s*=\s*'(.*?)';", content).group(1)
db_credentials['dbUser'] = re.search(r"\$dbUser\s*=\s*'(.*?)';", content).group(1)
db_credentials['dbPass'] = re.search(r"\$dbPass\s*=\s*'(.*?)';", content).group(1)
print(f"Parsed config: {db_credentials}")
return db_credentials
except Exception as e:
print(f"Error parsing config file: {e}")
return None
# Step 2: Connect to the MySQL database
def get_db_connection(db_credentials):
try:
connection = mysql.connector.connect(
host=db_credentials['dbHost'],
database=db_credentials['dbName'],
user=db_credentials['dbUser'],
password=db_credentials['dbPass']
)
if connection.is_connected():
print(f"Successfully connected to database {db_credentials['dbName']} on host {db_credentials['dbHost']}")
return connection
else:
print("Failed to connect to the database.")
return None
except mysql.connector.Error as err:
print(f"Error while connecting to the database: {err}")
return None
# Step 3: Get Twitter accounts with missing profile paths
def get_twitter_accounts(connection):
try:
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT username, profile_path, profile_path_loader FROM twitter_accounts WHERE profile_path IS NULL OR profile_path = '' OR profile_path_loader IS NULL OR profile_path_loader = ''")
return cursor.fetchall()
except mysql.connector.Error as err:
print(f"Error executing query: {err}")
return []
# Create a Firefox profile using the command line
def create_firefox_profile(profile_name):
# Specify the full path to the Firefox executable
firefox_path = r'C:\Program Files\Mozilla Firefox\firefox.exe'
subprocess.run([firefox_path, '-CreateProfile', profile_name])
print(f"Profile '{profile_name}' created successfully.")
# Find the profile path after creation
def find_profile_path(profile_name):
profiles_dir = os.path.join(os.getenv('APPDATA'), 'Mozilla', 'Firefox', 'Profiles')
profiles = glob.glob(os.path.join(profiles_dir, f"*.{profile_name}"))
if profiles:
return profiles[0]
else:
return None
#fetch accounts
def get_db_details_from_php():
# Placeholder function to get DB details, replace with actual implementation
return {
"host": "localhost",
"user": "your_db_user",
"password": "your_db_password",
"name": "your_db_name"
}
def fetch_accounts():
db_details = get_db_details_from_php()
if not db_details:
print("Error: Database connection details missing.")
return None
try:
connection = mysql.connector.connect(
host=db_details['host'],
user=db_details['user'],
password=db_details['password'],
database=db_details['name']
)
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT * FROM twitter_accounts")
accounts = cursor.fetchall()
cursor.close()
connection.close()
return accounts
except mysql.connector.Error as e:
print(f"Error fetching accounts: {e}")
return None
# Step 4: Update the database with the new profile path
def update_twitter_account(connection, username, profile_path, profile_loader_path):
try:
cursor = connection.cursor()
update_query = """
UPDATE twitter_accounts
SET profile_path = %s, profile_path_loader = %s
WHERE username = %s
"""
cursor.execute(update_query, (profile_path, profile_loader_path, username))
connection.commit()
except mysql.connector.Error as err:
print(f"Error updating database: {err}")
#Fetch reboot time from DB
def get_reboot_time():
db_details = get_db_details_from_php()
if not db_details:
print("Error: Database connection details missing.")
return None
try:
connection = mysql.connector.connect(
host=db_details['host'],
user=db_details['user'],
password=db_details['password'],
database=db_details['name']
)
cursor = connection.cursor(dictionary=True)
cursor.execute("SELECT reboot_time FROM mgmt LIMIT 1")
result = cursor.fetchone()
cursor.close()
connection.close()
if result and 'reboot_time' in result:
return str(result['reboot_time']) # Ensure reboot_time is returned as a string
else:
print("Error: reboot_time not found in the mgmt table.")
return None
except mysql.connector.Error as e:
print(f"Error fetching reboot_time: {e}")
return None
# Added reset function
def reset_and_restart():
while True:
reboot_time_str = get_reboot_time()
if not reboot_time_str:
print("Error: Could not fetch reboot_time. Exiting reset_and_restart.")
return
try:
reset_hour, reset_minute, reset_second = map(int, reboot_time_str.split(':'))
except ValueError:
print("Error: Invalid reboot_time format. Expected hh:mm:ss.")
return
now = datetime.now()
reset_time = datetime.combine(now.date(), dt_time(reset_hour, reset_minute, reset_second)) # Set custom reset time
if now >= reset_time:
# Wait until the next reset time
reset_time += timedelta(days=1)
time_to_wait = (reset_time - now).total_seconds()
time.sleep(time_to_wait) # Use time.sleep() from the time module
# Zero out the total_messages_sent column
db_details = get_db_details_from_php()
if not db_details:
print("Error: Database connection details missing.")
continue
try:
connection = mysql.connector.connect(
host=db_details['host'],
user=db_details['user'],
password=db_details['password'],
database=db_details['name']
)
cursor = connection.cursor()
cursor.execute("UPDATE twitter_accounts SET total_messages_sent = 0;")
connection.commit()
cursor.close()
connection.close()
print("Successfully reset total_messages_sent.")
except mysql.connector.Error as e:
print(f"Error resetting total_messages_sent: {e}")
continue
# Close all browsers
for thread in threading.enumerate():
if isinstance(thread, threading.Thread) and hasattr(thread, 'bot'):
if thread.bot.driver:
thread.bot.driver.quit()
# Restart the script
os.execv(sys.executable, ['python'] + sys.argv)
# Main execution function
def main():
config_path = r'C:\xampp\htdocs\config.php'
db_credentials = parse_config_php(config_path)
if not db_credentials:
return
connection = get_db_connection(db_credentials)
if not connection:
return
twitter_accounts = get_twitter_accounts(connection)
if not twitter_accounts:
print("No accounts with missing profile paths.")
return
for account in twitter_accounts:
username = account['username']
print(f"Processing account for {username}...")
profile_path, profile_path_loader = account.get('profile_path'), account.get('profile_path_loader')
# Create profile paths only if they are missing
if not profile_path:
profile_name = f"{username}_profile"
create_firefox_profile(profile_name)
profile_path = find_profile_path(profile_name)
if not profile_path_loader:
profile_name_loader = f"{username}_profile_loader"
create_firefox_profile(profile_name_loader)
profile_path_loader = find_profile_path(profile_name_loader)
# Update the database with both paths
if profile_path and profile_path_loader:
update_twitter_account(connection, username, profile_path, profile_path_loader)
print(f"Profile for {username} processed and database updated.")
# Close the connection
connection.close()
if __name__ == "__main__":
main()
# Process to delete user.js from all Firefox profiles so that they are recreated each run
def read_config(file_path):
config = {}
with open(file_path, 'r') as file:
for line in file:
match = re.match(r"\$([a-zA-Z_]+) = '([^']*)';", line)
if match:
config[match.group(1)] = match.group(2)
return config
def delete_user_js(profile_path):
user_js_path = os.path.join(profile_path, 'user.js')
if os.path.isfile(user_js_path):
try:
os.remove(user_js_path)
print(f"Deleted: {user_js_path}")
except Exception as e:
print(f"Error deleting {user_js_path}: {e}")
else:
print(f"File not found: {user_js_path}")
def main():
config_path = 'C:\\xampp\\htdocs\\config.php'
config = read_config(config_path)
dbHost = config['dbHost']
dbName = config['dbName']
dbUser = config['dbUser']
dbPass = config['dbPass']
# Connect to the database and handle any errors
try:
conn = mysql.connector.connect(
host=dbHost,
user=dbUser,
password=dbPass,
database=dbName
)
cursor = conn.cursor(dictionary=True)
except mysql.connector.Error as err:
print(f"Database connection error: {err}")
cursor = None
# Query the accounts and handle potential issues
try:
if cursor:
cursor.execute("SELECT profile_path, profile_path_loader FROM twitter_accounts")
accounts = cursor.fetchall()
for account in accounts:
if account['profile_path']:
delete_user_js(account['profile_path'])
if account['profile_path_loader']:
delete_user_js(account['profile_path_loader'])
cursor.close()
conn.close()
else:
print("Skipping account processing due to database connection failure.")
except Exception as e:
print(f"Error fetching account data: {e}")
print("Script has completed execution.")
if __name__ == "__main__":
main()
class XBot:
def __init__(self, accounts):
self.logger = self.setup_logger()
self.accounts = accounts
self.account = accounts['account_id']
def connect_to_database(self):
# Get database connection details from config.php
db_details = get_db_details_from_php()
if not db_details:
self.log_with_account(self.account, "Error: Database connection details missing.", logging.ERROR)
return None, None
try:
# Establish the connection using the extracted details
connection = mysql.connector.connect(
host=db_details['host'],
user=db_details['user'],
password=db_details['password'],
database=db_details['name']
)
if connection.is_connected():
self.log_with_account(self.account, "Connected to MySQL database")
return connection, connection.cursor(dictionary=True) # Use dictionary cursor here
except mysql.connector.Error as e:
self.log_with_account(self.account, f"Error: {e}", logging.ERROR)
return None, None
# Determine time of zero out and restart
def is_time_between_midnight_and_one_am(self):
current_time = datetime.now().time()
start_time = datetime.strptime("00:00:00", "%H:%M:%S").time()
end_time = datetime.strptime("00:05:00", "%H:%M:%S").time()
return start_time <= current_time < end_time
# Configure logging
@staticmethod
def setup_logger():
logger = logging.getLogger("MyLogger")
if not logger.handlers:
logger.setLevel(logging.DEBUG)
log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(id)s - %(message)s')
console_handler = logging.StreamHandler()
console_handler.setFormatter(log_format)
logger.addHandler(console_handler)
return logger
def log_with_account(self, account_id, message, level=logging.INFO):
extra = {'id': f'Account {account_id}'}
self.logger.log(level, message, extra=extra)
def launch_browser(self, headless, profile_path, active, proxy, proxy_active):
if active == 0:
self.log_with_account(self.account, f'Account {self.account} is Not Active')
return "not active"
self.log_with_account(self.account, 'Launching Browser')
options = webdriver.FirefoxOptions()
if proxy_active == 1:
wire_options = {
'proxy': {
'https': proxy,
}
}
options.add_argument("-profile")
options.add_argument(r'{}'.format(profile_path))
if headless:
options.add_argument('--headless')
try:
self.driver = webdriver.Firefox(seleniumwire_options=wire_options, options=options)
except WebDriverException as e:
self.log_with_account(self.account, f'Failed to launch browser: {e}')
self.driver = None
else:
options.add_argument("-profile")
options.add_argument(r'{}'.format(profile_path))
if headless:
options.add_argument('--headless')
try:
self.driver = webdriver.Firefox(options=options)
except WebDriverException as e:
self.log_with_account(self.account, f'Failed to launch browser: {e}')
self.driver = None
return self.driver
def total_messages_sent(self, cursor):
query = "SELECT total_messages_sent from twitter_accounts WHERE account_id = %s;"
cursor.execute(query, (self.account,))
result = cursor.fetchone()
return result['total_messages_sent'] if result else 0
def update_total_messages_sent(self, connection, cursor):
query = "UPDATE twitter_accounts SET total_messages_sent = total_messages_sent + 1 WHERE account_id = %s"
cursor.execute(query, (self.account,))
connection.commit()
def get_url(self, url):
self.log_with_account(self.account, f'Getting url: {url}')
self.driver.get(url)
def find_dm_id(self, cursor, dm_id, account_id):
query = "SELECT 1 FROM dms WHERE dm_id = %s AND account_id = %s LIMIT 1"
cursor.execute(query, (dm_id, account_id))
return cursor.fetchone() is not None
def scroll_and_scrape_twitter_messages(self, timeout=20, max_retries=5):
conversation_url_xpath = '//a[@data-testid="DM_Conversation_Avatar"]'
previous_count = -1
retries = 0
while retries < max_retries:
try:
self.log_with_account(self.account, 'Scraping message URLs')
scrollable_div = WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.XPATH, conversation_url_xpath))
)
urls = set()
while True:
request_urls_elem = WebDriverWait(self.driver, 20).until(
EC.presence_of_all_elements_located((By.XPATH, conversation_url_xpath)))
for url in request_urls_elem:
urls.add(url.get_attribute('href').replace('/participants', '/info'))
if len(urls) == previous_count:
self.log_with_account(self.account, 'Reached the last chat element')
break
previous_count = len(urls)
scroll = self.driver.find_element(By.XPATH, '//section[@aria-label="Section navigation"]/div/div')
scroll.send_keys(Keys.PAGE_DOWN)
scroll.send_keys(Keys.PAGE_DOWN)
time.sleep(5)
filtered_urls = {url for url in urls if 'messages' in url}
self.log_with_account(self.account, f'{len(urls)} message groups found')
return filtered_urls
except StaleElementReferenceException as e:
self.log_with_account(self.account, f"Stale element reference exception encountered: {e}. Retrying ({retries+1}/{max_retries})")
retries += 1
time.sleep(2) # Wait before retrying
except Exception as e:
self.log_with_account(self.account, f"Error while scrolling or scraping: {e}")
break
return set() # Return an empty set if scraping fails after max retries
def accept_group_dms_and_scrape(self, first_run_done):
self.get_url('https://x.com/messages/requests')
try:
urls = self.scroll_and_scrape_twitter_messages(20)
except Exception as e:
self.log_with_account(self.account, f"Error while scraping message requests: {e}")
urls = set()
connection, cursor = self.connect_to_database()
if not connection or not cursor:
return
try:
# Retrieve current list of DM IDs from the database
current_dm_ids = self.get_dm_ids(cursor)
found_dm_ids = set() # To keep track of the DM IDs found during the scan
# Accept message requests if any are found
for url in urls:
final_url = url.replace('/info', '')
dm_id = final_url.replace('https://x.com/messages/', '')
found_dm_ids.add(dm_id)
if dm_id in current_dm_ids:
self.log_with_account(self.account, 'dm_id already exists in database')
continue
self.get_url(url)
dm_name = "Null"
try:
WebDriverWait(self.driver, 20).until(EC.presence_of_element_located((By.XPATH, "//span[contains(text(),'Delete conversation')]")))
dm_name = WebDriverWait(self.driver, 2).until(EC.presence_of_element_located((By.XPATH, '//section[@aria-label="Section details"]/div/div/div[2]/div/div/div[2]/div/span')))
time.sleep(1)
participants = WebDriverWait(self.driver, 1).until(
EC.presence_of_all_elements_located((By.XPATH, "//div[@data-testid='UserAvatar-Container-unknown']")))
people = len(participants) > 2
except Exception as e:
self.log_with_account(self.account, f"Error while processing message request: {e}")
people = False
group_name = dm_name.text
if not people:
self.log_with_account(self.account, 'Participants less than 3. skipping')
continue
# Retrying Message Accept
retry = 0
while True:
if retry >= 5:
break
retry += 1
try:
self.get_url(final_url)
self.log_with_account(self.account, f'Accepting Message Request: {group_name}')
accept = WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//span[contains(text(),'Accept')]")))
accept.click()
break
except Exception as e:
self.log_with_account(self.account, f"Failed to accept message request. Retrying... Error: {e}")
pass
time.sleep(5)
self.log_with_account(self.account, f'Adding DM {group_name} into Database')
query = """
INSERT INTO dms (dm_id, account_id, dm_name)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
dm_name = VALUES(dm_name)
"""
cursor.execute(query, (dm_id, self.account, group_name))
connection.commit()
# Now redirect to the messages page to scrape DM IDs
self.get_url('https://x.com/messages')
self.log_with_account(self.account, 'Scraping DM IDs from Messages Page')
chat_urls = self.scroll_and_scrape_twitter_messages(timeout=20)
for url in chat_urls:
final_url = url.replace('/info', '')
dm_id = final_url.replace('https://x.com/messages/', '')
found_dm_ids.add(dm_id)
if dm_id in current_dm_ids:
self.log_with_account(self.account, 'dm_id already exists in database')
continue
self.get_url(final_url)
try:
participants = WebDriverWait(self.driver, 20).until(
EC.presence_of_all_elements_located((By.XPATH, "//div[@data-testid='UserAvatar-Container-unknown']")))
people = len(participants) > 2
except Exception as e:
self.log_with_account(self.account, f"Error while processing DM: {e}")
people = False
if not people:
self.log_with_account(self.account, 'Participants less than 3. skipping')
continue
self.log_with_account(self.account, f'Adding DM {dm_id} into Database')
query = """
INSERT INTO dms (dm_id, account_id)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE
account_id = VALUES(account_id)
"""
cursor.execute(query, (dm_id, self.account))
connection.commit()
# Remove groups from the database that were not found during the scan
groups_to_remove = set(current_dm_ids) - found_dm_ids
removed_dm_ids = list(groups_to_remove)
if groups_to_remove:
self.log_with_account(self.account, f'Removing {len(groups_to_remove)} groups from database')
delete_query = "DELETE FROM dms WHERE dm_id = %s AND account_id = %s"
for dm_id in groups_to_remove:
cursor.execute(delete_query, (dm_id, self.account))
connection.commit()
self.log_with_account(self.account, f'Removed dm_ids: {removed_dm_ids}')
finally:
if connection.is_connected():
cursor.close()
connection.close()
def get_dm_ids(self, cursor):
try:
query = """
SELECT dm_id
FROM dms
WHERE account_id = %s
ORDER BY
COALESCE(last_sent, '1970-01-01 00:00:00') ASC
"""
cursor.execute(query, (self.account,))
dm_ids = [row['dm_id'] for row in cursor.fetchall()] # Access using string key 'dm_id'
return dm_ids
except mysql.connector.Error as e:
self.log_with_account(self.account, f"Error fetching DM IDs: {e}", logging.ERROR)
return []
def close_browser(self):
self.log_with_account(self.account, 'Closing Browser')
self.driver.quit()
def scrape_users_for_retweet(self):
usernames = []
self.log_with_account(self.account, 'Getting usernames for retweet')
for i in range(0, 3):
last_sender_xpath = f'(//div[@data-testid="UserAvatar-Container-unknown"]//a)[last()-{i}]'
user_urls = f'(//div[@data-testid="tweetText"])[last()-{i}]//a'
down_arrow_xpath = "/html/body/div[1]/div/div/div[2]/main/div/div/div/section[2]/div/div/div[2]/div/div/div/div/div[2]/button"
down_arrow_elem = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.XPATH, down_arrow_xpath))
)
# Use JavaScript to click the element
self.driver.execute_script("arguments[0].click();", down_arrow_elem)
# Check if the chat has populated with messages from other users
try:
chat_elements = WebDriverWait(self.driver, 3).until(
EC.presence_of_all_elements_located((By.XPATH, '//div[@data-testid="tweetText"]'))
)
if len(chat_elements) <= 1:
# If no other users are found, log the message and skip this process
self.log_with_account(self.account, "Browser did not load chat contents. Skipping this retweet process")
return []
except Exception:
self.log_with_account(self.account, "Browser did not load chat contents. Skipping this retweet process")
return []
try:
urls_elem = WebDriverWait(self.driver, 2).until(
EC.presence_of_all_elements_located((By.XPATH, user_urls)))
for u in urls_elem:
usernames.append(u.get_attribute('href'))
except:
try:
sender_elem = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, last_sender_xpath)))
usernames.append(sender_elem.get_attribute('href'))
except Exception:
self.log_with_account(self.account, "Failed to fetch sender profile URL")
continue
if len(usernames) >= 1:
users = list(dict.fromkeys(usernames))
return users
else:
return []
def start_retweeting(self, users):
retweet_btn_xpath = '(//div[@role="group"]/div[2]/button[@aria-haspopup="menu"])[1]'
unretweet_xpath = '(//div[@role="group"]/div[2]/button[@data-testid="unretweet"])[1]'
retweet_confirm_xpath = '//div[@data-testid="retweetConfirm"]'
unretweet_confirm_xpath = '//div[@data-testid="unretweetConfirm"]'
pinned_xpath = "//div[contains(text(),'Pinned')]"
for user in users:
try:
self.get_url(user)
try:
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located(
(By.XPATH, '//div[@role="group"]/div[2]/button[@aria-haspopup="menu"]')))
pinned = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, pinned_xpath)))
except:
self.log_with_account(self.account, f'Pinned Tweet of user {user} not found!')
continue
self.log_with_account(self.account, 'Retweeting')
try:
unretweet = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, unretweet_xpath)))
unretweet.click()
time.sleep(3)
unretweet_confirm = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, unretweet_confirm_xpath)))
unretweet_confirm.click()
time.sleep(3)
except:
pass
retweet = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, retweet_btn_xpath)))
retweet.click()
time.sleep(random.randint(2, 5))
confirm_retweet = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.XPATH, retweet_confirm_xpath)))
confirm_retweet.click()
time.sleep(3)
except Exception as e:
print(e)
traceback.print_exc()
self.log_with_account(self.account, 'Retweeting failed. Skipping')
def send_messages(self, retweet):
account_id = self.accounts['account_id']
message = self.accounts['message']
first_run_done = self.accounts['first_run_done']
gif_keyword = self.accounts['gif_keyword']
message_limit = self.accounts['message_limit']
delay_drop_sleep = self.accounts['delay:drop:sleep']
split = delay_drop_sleep.split(':')
drop, sleep = split[1], split[2]
delay = split[0].split('-')
refresh = "//span[contains(text(),'Refresh')]"
connection, cursor = self.connect_to_database()
if not connection or not cursor:
return
try:
# Getting DM IDs for current account
self.log_with_account(self.account, 'Getting DM IDs for current account')
dm_ids = self.get_dm_ids(cursor)
self.log_with_account(self.account, f"DM IDs for account {self.account}: {dm_ids}")
count = 0
for dm_id in dm_ids:
# check current time
if self.is_time_between_midnight_and_one_am():
query = 'UPDATE twitter_accounts SET total_messages_sent = 0;'
cursor.execute(query)
connection.commit()
return 'reset'
if count == int(drop):
self.log_with_account(self.account, f'Drop limit Reached. Sleeping for {sleep} Seconds')
time.sleep(int(sleep))
count = 0
url = f'https://x.com/messages/{dm_id}'
self.log_with_account(self.account, 'Checking Messages limit and message sent count')
messages_sent = self.total_messages_sent(cursor=cursor)
if messages_sent >= int(message_limit):
self.log_with_account(self.account, 'Message Limit is Reached, Closing Browser')
return "limit"
try:
self.get_url(url)
WebDriverWait(self.driver, 20).until(EC.element_to_be_clickable((By.XPATH, '//div[@data-testid="dmComposerTextInput"]')))
except:
try:
WebDriverWait(self.driver, 3).until(EC.presence_of_element_located((By.XPATH, refresh)))
self.log_with_account(self.account, 'Refresh Element found, Sleeping for 300 seconds')
time.sleep(300)
continue
except:
pass
self.log_with_account(self.account,f'Group not found. group is probably deleted: {url}')
break
# Ensure chat_details_xpath is always defined
chat_details_xpath = None # Initialize the variable
# Step 1: Click the "chat details" link/button
try:
# Use a more general XPath to check for the "chat details" button
chat_details_xpath = '/html/body/div[1]/div/div/div[2]/main/div/div/div/section[2]/div/div/div[1]/div/div/div/div/div[2]/div/a/div' # Replace with the actual XPath for chat details
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, chat_details_xpath)) # Wait for the element to be present
)
chat_details_elem = self.driver.find_element(By.XPATH, chat_details_xpath)
# Use ActionChains to click the element, ensuring it’s clickable
ActionChains(self.driver).move_to_element(chat_details_elem).click().perform()
time.sleep(4) # Wait for 4 seconds after clicking "chat details"
except Exception as e:
self.log_with_account(self.account, f"Failed to click 'chat details': {e}")
return []
# Step 2: Click "Back"
try:
# You can also try using a more generic XPath for the "back" button
back_button_xpath = '//button[contains(@class, "r-1qi8awa")]' # Adjust as needed
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, back_button_xpath)) # Wait for the back button to be present
)
back_button_elem = self.driver.find_element(By.XPATH, back_button_xpath)
# Use ActionChains to click the back button
ActionChains(self.driver).move_to_element(back_button_elem).click().perform()
time.sleep(3) # Wait for 3 seconds after clicking "chat details"
except Exception as e:
self.log_with_account(self.account, f"Failed to click 'back': {e}")
return []
try:
time.sleep(3)
self.log_with_account(self.account, 'Sending GIF')
gif = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, '//button[@aria-label="Add a GIF"]')))
gif.click()
gif_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search for GIFs"]')))
gif_input.send_keys(gif_keyword)
time.sleep(5)
gif_image = WebDriverWait(self.driver, 20).until(
EC.element_to_be_clickable((By.XPATH, '//div[@data-testid="gifSearchGifImage"]')))
gif_image.click()
self.log_with_account(self.account, 'GIF sent!')
time.sleep(5)
send_message = self.driver.find_element(By.XPATH, '//div[@data-testid="dmComposerTextInput"]')
send_message.click()
time.sleep(2)
self.log_with_account(self.account, f"Sending Text Message")
send_message.send_keys(message)
time.sleep(1)
button = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.XPATH, '//button[@data-testid="dmComposerSendButton"]')))
if retweet == 1:
users = self.scrape_users_for_retweet()
self.driver.execute_script("arguments[0].click();", button)
time.sleep(5)
try:
WebDriverWait(self.driver, 3).until(EC.presence_of_element_located((By.XPATH, refresh)))
self.log_with_account(self.account, 'Refresh Element found, Sleeping for 300 seconds')
time.sleep(300)
pass
except:
pass
self.log_with_account(self.account, "Text Message Sent!")
self.log_with_account(self.account, 'Updating Message Sent Count in database')
self.update_total_messages_sent(connection, cursor)
time.sleep(4) # Sleep for 4 seconds after sending the message
except Exception as e:
print(e)
self.log_with_account(self.account, 'Message Sending Failed!')
# Update last_sent timestamp whether sending was successful or not
self.log_with_account(self.account, f'Updating last_sent for DM {dm_id}')
query = "UPDATE dms SET last_sent = %s WHERE dm_id = %s AND account_id = %s"
cursor.execute(query, (datetime.now(), dm_id, self.account))
connection.commit()
count += 1
random_delay = random.randint(int(delay[0]), int(delay[1]))
self.log_with_account(self.account, f'Delaying for {random_delay} seconds')
time.sleep(random_delay)
if retweet == 1:
try:
self.start_retweeting(users=users)
except:
pass
finally:
if connection.is_connected():
cursor.close()
connection.close()
# Path to the config.php (update with the correct path if needed)
CONFIG_FILE_PATH = r'C:\xampp\htdocs\config.php'
# Function to extract database connection details from config.php
def get_db_details_from_php():
db_details = {
"host": None,
"name": None,
"user": None,
"password": None
}
try:
with open(CONFIG_FILE_PATH, 'r') as file:
content = file.read()
# Extract database connection details from config.php
match_db_host = re.search(r'\$dbHost\s*=\s*\'([^\']+)\'', content)
if match_db_host:
db_details['host'] = match_db_host.group(1)
match_db_name = re.search(r'\$dbName\s*=\s*\'([^\']+)\'', content)
if match_db_name:
db_details['name'] = match_db_name.group(1)
match_db_user = re.search(r'\$dbUser\s*=\s*\'([^\']+)\'', content)
if match_db_user:
db_details['user'] = match_db_user.group(1)
match_db_pass = re.search(r'\$dbPass\s*=\s*\'([^\']+)\'', content)
if match_db_pass:
db_details['password'] = match_db_pass.group(1)
except Exception as e:
print(f"Error reading config.php: {e}")
return None
# Check if all required values are found
if not all(db_details.values()):
print("Some required database details are missing from the config.php file.")
return None
return db_details
if __name__ == '__main__':
# Start the reset thread
reset_thread = threading.Thread(target=reset_and_restart)
reset_thread.daemon = True # This makes sure the thread will exit when the main program exits
reset_thread.start()
accounts = fetch_accounts()
if accounts:
print("Fetched accounts:", accounts)
else:
print("No accounts found.")
def run(accounts):
bot = XBot(accounts=accounts)
threading.current_thread().bot = bot # Set bot attribute for the current thread
if accounts['first_run_done'] == 1:
while True:
try:
launch = bot.launch_browser(headless=False, profile_path=accounts['profile_path'], active=accounts['active'],
proxy=accounts['proxy'], proxy_active=accounts['proxy_active'])
if launch == 'not active':
break
bot.get_url('https://x.com')
bot.accept_group_dms_and_scrape(first_run_done=accounts['first_run_done'])
messages = bot.send_messages(retweet=accounts['rt_active'])
if messages == 'limit':
break
elif messages == 'reset':
if bot.driver:
bot.close_browser()
continue
if bot.driver and bot.driver.service.is_connectable():
bot.close_browser()
except Exception as e:
if bot.driver and bot.driver.service.is_connectable():
bot.driver.quit()
print(e)
else:
while True:
try:
launch = bot.launch_browser(headless=False, profile_path=accounts['profile_path'], active=accounts['active'],
proxy=accounts['proxy'], proxy_active=accounts['proxy_active'])
if launch == 'not active':
break
bot.get_url('https://x.com')
messages = bot.send_messages(retweet=accounts['rt_active'])
if messages == 'limit':
break
if bot.driver and bot.driver.service.is_connectable():
bot.close_browser()
except Exception as e:
if bot.driver and bot.driver.service.is_connectable():
bot.driver.quit()
print(e)
accounts = fetch_accounts()
threads = []
for i in accounts:
thread = threading.Thread(target=run, args=(i,))
thread.start()
threads.append(thread)
for j in threads:
j.join()