chatbot

Configure chatbot to send Message log.

My idea is configure chatbot that it send messages for example BALANCES, ERRORS...
NEED INSTALL: pip install requests
CONFIGURATION:

Make A Telegram Bot Using BotFather for Telegram Search for BotFather in your Telegram client by opening it. A pre-installed Telegram bot that assists users in making original Telegram bots To create a new bot, enter /newbot. Name and username your bot. Copy the token for your new Telegram bot. Please take note that anyone who obtains your token has complete control over your Telegram bot, so avoid uploading it online.

Send any message to created Bot.

Launch script. The script obtain the chat ID to send Telegram messages using Python, we require the conversation ID that each chat in Telegram holds.

Hello,
I have tested your chatbot code, and everything works fine. I have a Telegram token that looks like this: 123456789:nokfznkofezk56fzefze6fez6. Should I copy and paste the entire code into this token?
Additionally, how can I configure it to only display error messages and not all the successful operation messages?
Everything is good on my end; I am just waiting for the green light from Brumb.
Thank you!

Yes, copy all of "123...:...." to telegramBotToken.
Now, it is not possible to configure only errors, because the code does not provide for this. Have to change all outgoing messages or manually create new lines where only the "sendMessage" function is executed.
Waiting for Brumb to decide what to do

Hi JBR!
It looks very interesting, I will test in the next few days and we will include it in the package!

Have you merged it?
More changes need to be made before merging it

I merged it and then rolled it back! I have a nice version running at the minute.
import os
import shutil
import sys
import time
import re
import json
import getpass
import random
import subprocess
from PIL import Image
from pyzbar.pyzbar import decode
import qrcode_terminal
import fcntl
from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_NB
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import NoSuchElementException, TimeoutException, StaleElementReferenceException, ElementClickInterceptedException, UnexpectedAlertPresentException
from datetime import datetime, timedelta
from selenium.webdriver.chrome.service import Service as ChromeService
Ensure the requests module is installed
try:
import requests
except ImportError:
print("The 'requests' module is not installed. Installing it now...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"])
import requests
class Claimer():
settings_file = "variables.txt"
status_file_path = "status.txt"
start_app_xpath = None
settings = {}
driver = None
target_element = None
random_offset = 0
script = ""
prefix = ""
url = ""
pot_full = "Filled"
pot_filling = "to fill"
seed_phrase = None
wallet_id = ""
def __init__(self):
    self.load_settings()
    self.random_offset = random.randint(self.settings['lowestClaimOffset'], self.settings['highestClaimOffset'])
    print(f"Initialising the {self.prefix} Wallet Auto-claim Python Script - Good Luck!")

    self.imported_seedphrase = None

    # Update the settings based on user input
    if len(sys.argv) > 1:
        user_input = sys.argv[1]  # Get session ID from command-line argument
        self.wallet_id = user_input
        self.output(f"Session ID provided: {user_input}", 2)

        # Safely check for a second argument
        if len(sys.argv) > 2 and sys.argv[2] == "reset":
            self.settings['forceNewSession'] = True

        # Check for the --seed-phrase flag and validate it
        if '--seed-phrase' in sys.argv:
            seed_index = sys.argv.index('--seed-phrase') + 1
            if seed_index < len(sys.argv):
                self.seed_phrase = ' '.join(sys.argv[seed_index:])
                seed_words = self.seed_phrase.split()
                if len(seed_words) == 12:
                    self.output(f"Seed phrase accepted:", 2)
                    self.imported_seedphrase = self.seed_phrase
                else:
                    self.output("Invalid seed phrase. Ignoring.", 2)
            else:
                self.output("No seed phrase provided after --seed-phrase flag. Ignoring.", 2)
    else:
        self.output("\nCurrent settings:", 1)
        for key, value in self.settings.items():
            self.output(f"{key}: {value}", 1)
        user_input = input("\nShould we update our settings? (Default:<enter> / Yes = y): ").strip().lower()
        if user_input == "y":
            self.update_settings()
        user_input = self.get_session_id()
        self.wallet_id = user_input

    self.session_path = "./selenium/{}".format(self.wallet_id)
    os.makedirs(self.session_path, exist_ok=True)
    self.screenshots_path = "./screenshots/{}".format(self.wallet_id)
    os.makedirs(self.screenshots_path, exist_ok=True)
    self.backup_path = "./backups/{}".format(self.wallet_id)
    os.makedirs(self.backup_path, exist_ok=True)
    self.step = "01"

    # Define our base path for debugging screenshots
    self.screenshot_base = os.path.join(self.screenshots_path, "screenshot")

    if self.settings["useProxy"] and self.settings["proxyAddress"] == "http://<IP_ADDRESS>:8080":
        self.run_http_proxy()
    elif self.forceLocalProxy:
        self.run_http_proxy()
        self.output("Use of the built-in proxy is force on for this game.", 2)
    else:
        self.output("Proxy disabled in settings.", 2)

def run(self):
    if not self.settings["forceNewSession"]:
        self.load_settings()
    cookies_path = os.path.join(self.session_path, 'cookies.json')
    if os.path.exists(cookies_path) and not self.settings['forceNewSession']:
        self.output("Resuming the previous session...", 2)
    else:
        telegram_backup_dirs = [d for d in os.listdir(os.path.dirname(self.session_path)) if d.startswith("Telegram")]
        if telegram_backup_dirs:
            print("Previous Telegram login sessions found. Pressing <enter> will select the account numbered '1':")
            for i, dir_name in enumerate(telegram_backup_dirs):
                print(f"{i + 1}. {dir_name}")

            user_input = input("Enter the number of the session you want to restore, or 'n' to create a new session: ").strip().lower()

            if user_input == 'n':
                self.log_into_telegram(self.wallet_id)
                self.quit_driver()
                self.backup_telegram()
            elif user_input.isdigit() and 0 < int(user_input) <= len(telegram_backup_dirs):
                self.restore_from_backup(os.path.join(os.path.dirname(self.session_path), telegram_backup_dirs[int(user_input) - 1]))
            else:
                self.restore_from_backup(os.path.join(os.path.dirname(self.session_path), telegram_backup_dirs[0]))  # Default to the first session

        else:
            self.log_into_telegram(self.wallet_id)
            self.quit_driver()
            self.backup_telegram()

        self.next_steps()
        self.quit_driver()

        try:
            shutil.copytree(self.session_path, self.backup_path, dirs_exist_ok=True)
            self.output("We backed up the session data in case of a later crash!", 3)
        except Exception as e:
            self.output(f"Oops, we weren't able to make a backup of the session data! Error: {e}", 1)

        pm2_session = self.session_path.replace("./selenium/", "")
        self.output(f"You could add the new/updated session to PM use: pm2 start {self.script} --interpreter venv/bin/python3 --name {pm2_session} -- {pm2_session}", 1)
        user_choice = input("Enter 'y' to continue to 'claim' function, 'e' to exit, 'a' or <enter> to automatically add to PM2: ").lower()

        if user_choice == "e":
            self.output("Exiting script. You can resume the process later.", 1)
            sys.exit()
        elif user_choice == "a" or not user_choice:
            self.start_pm2_app(self.script, pm2_session, pm2_session)
            user_choice = input("Should we save your PM2 processes? (Y/n): ").lower()
            if user_choice == "y" or not user_choice:
                self.save_pm2()
            self.output(f"You can now watch the session log into PM2 with: pm2 logs {pm2_session}", 2)
            sys.exit()

    while True:
        self.manage_session()
        wait_time = self.full_claim()

        if os.path.exists(self.status_file_path):
            with open(self.status_file_path, "r+") as file:
                status = json.load(file)
                if self.session_path in status:
                    del status[self.session_path]
                    file.seek(0)
                    json.dump(status, file)
                    file.truncate()
                    self.output(f"Session released: {self.session_path}", 3)

        self.quit_driver()

        now = datetime.now()
        next_claim_time = now + timedelta(minutes=wait_time)
        this_claim_str = now.strftime("%d %B - %H:%M")
        next_claim_time_str = next_claim_time.strftime("%d %B - %H:%M")
        self.output(f"{this_claim_str} | Need to wait until {next_claim_time_str} before the next claim attempt. Approximately {wait_time} minutes.", 1)
        if self.settings["forceClaim"]:
            self.settings["forceClaim"] = False

        while wait_time > 0:
            this_wait = min(wait_time, 15)
            now = datetime.now()
            timestamp = now.strftime("%H:%M")
            self.output(f"[{timestamp}] Waiting for {this_wait} more minutes...", 3)
            time.sleep(this_wait * 60)  # Convert minutes to seconds
            wait_time -= this_wait
            if wait_time > 0:
                self.output(f"Updated wait time: {wait_time} minutes left.", 3)

def load_settings(self):
    default_settings = {
        "forceClaim": False,
        "debugIsOn": False,
        "hideSensitiveInput": True,
        "screenshotQRCode": True,
        "maxSessions": 1,
        "verboseLevel": 2,
        "telegramVerboseLevel": 1,  # New setting for Telegram verbose level
        "lowestClaimOffset": 0,
        "highestClaimOffset": 15,
        "forceNewSession": False,
        "useProxy": False,
        "proxyAddress": "http://<IP_ADDRESS>:8080",
        "requestUserAgent": False,
        "telegramBotToken": "",  # Define token before run script
        "telegramBotChatId": ""
    }

    if os.path.exists(self.settings_file):
        with open(self.settings_file, "r") as f:
            loaded_settings = json.load(f)
        # Filter out unused settings from previous versions
        self.settings = {k: loaded_settings.get(k, v) for k, v in default_settings.items()}
        self.output("Settings loaded successfully.", 3)
    else:
        self.settings = default_settings
        self.save_settings()

def save_settings(self):
    with open(self.settings_file, "w") as f:
        json.dump(self.settings, f)
    self.output("Settings saved successfully.", 3)

def update_settings(self):

    def update_setting(setting_key, message, default_value):
        current_value = self.settings.get(setting_key, default_value)
        response = input(f"\n{message} (Y/N, press Enter to keep current [{current_value}]): ").strip().lower()
        if response == "y":
            self.settings[setting_key] = True
        elif response == "n":
            self.settings[setting_key] = False

    update_setting("forceClaim", "Shall we force a claim on first run? Does not wait for the timer to be filled", self.settings["forceClaim"])
    update_setting("debugIsOn", "Should we enable debugging? This will save screenshots in your local drive", self.settings["debugIsOn"])
    update_setting("hideSensitiveInput", "Should we hide sensitive input? Your phone number and seed phrase will not be visible on the screen", self.settings["hideSensitiveInput"])
    update_setting("screenshotQRCode", "Shall we allow log in by QR code? The alternative is by phone number and one-time password", self.settings["screenshotQRCode"])

    try:
        new_max_sessions = int(input(f"\nEnter the number of max concurrent claim sessions. Additional claims will queue until a session slot is free.\n(current: {self.settings['maxSessions']}): "))
        self.settings["maxSessions"] = new_max_sessions
    except ValueError:
        self.output("Number of sessions remains unchanged.", 1)

    try:
        new_verbose_level = int(input("\nEnter the number for how much information you want displaying in the console.\n 3 = all messages, 2 = claim steps, 1 = minimal steps\n(current: {}): ".format(self.settings['verboseLevel'])))
        if 1 <= new_verbose_level <= 3:
            self.settings["verboseLevel"] = new_verbose_level
            self.output("Verbose level updated successfully.", 2)
        else:
            self.output("Verbose level remains unchanged.", 2)
    except ValueError:
        self.output("Verbose level remains unchanged.", 2)

    try:
        new_telegram_verbose_level = int(input("\nEnter the Telegram verbose level (3 = all messages, 2 = claim steps, 1 = minimal steps)\n(current: {}): ".format(self.settings['telegramVerboseLevel'])))
        if 1 <= new_telegram_verbose_level <= 3:
            self.settings["telegramVerboseLevel"] = new_telegram_verbose_level
            self.output("Telegram verbose level updated successfully.", 2)
        else:
            self.output("Telegram verbose level remains unchanged.", 2)
    except ValueError:
        self.output("Telegram verbose level remains unchanged.", 2)

    try:
        new_lowest_offset = int(input("\nEnter the lowest possible offset for the claim timer (valid values are -30 to +30 minutes)\n(current: {}): ".format(self.settings['lowestClaimOffset'])))
        if -30 <= new_lowest_offset <= 30:
            self.settings["lowestClaimOffset"] = new_lowest_offset
            self.output("Lowest claim offset updated successfully.", 2)
        else:
            self.output("Invalid range for lowest claim offset. Please enter a value between -30 and +30.", 2)
    except ValueError:
        self.output("Lowest claim offset remains unchanged.", 2)

    try:
        new_highest_offset = int(input("\nEnter the highest possible offset for the claim timer (valid values are 0 to 60 minutes)\n(current: {}): ".format(self.settings['highestClaimOffset'])))
        if 0 <= new_highest_offset <= 60:
            self.settings["highestClaimOffset"] = new_highest_offset
            self.output("Highest claim offset updated successfully.", 2)
        else:
            self.output("Invalid range for highest claim offset. Please enter a value between 0 and 60.", 2)
    except ValueError:
        self.output("Highest claim offset remains unchanged.", 2)

    if self.settings["lowestClaimOffset"] > self.settings["highestClaimOffset"]:
        self.settings["lowestClaimOffset"] = self.settings["highestClaimOffset"]
        self.output("Adjusted lowest claim offset to match the highest as it was greater.", 2)

    update_setting("useProxy", "Use Proxy?", self.settings["useProxy"])

    if self.settings["useProxy"]:
        proxy_address = input(f"\nEnter the Proxy IP address and port (current: {self.settings['proxyAddress']}): ").strip()
        if proxy_address:
            self.settings["proxyAddress"] = proxy_address

    update_setting("requestUserAgent", "Shall we collect a User Agent during setup?: ", self.settings["requestUserAgent"])

    # Collect the Telegram Bot Token
    new_telegram_bot_token = input(f"\nEnter the Telegram Bot Token (current: {self.settings['telegramBotToken']}): ").strip()
    if new_telegram_bot_token:
        self.settings["telegramBotToken"] = new_telegram_bot_token

    self.save_settings()

    update_setting("forceNewSession", "Overwrite existing session and Force New Login? Use this if your saved session has crashed\nOne-Time only (setting not saved): ", self.settings["forceNewSession"])

    self.output("\nRevised settings:", 1)
    for key, value in self.settings.items():
        self.output(f"{key}: {value}", 1)
    self.output("", 1)

def output(self, string, level=2):
    if self.settings['verboseLevel'] >= level:
        print(string)
    if self.settings['telegramBotToken'] and not self.settings['telegramBotChatId']:
        try:
            self.settings['telegramBotChatId'] = self.get_telegram_bot_chat_id()
            self.save_settings()  # Save the settings after getting the chat ID
        except ValueError as e:
            print(f"Error fetching Telegram chat ID: {e}")
    if self.settings['telegramBotChatId'] and self.wallet_id and self.settings['telegramVerboseLevel'] >= level:
        self.send_message(string)

def get_telegram_bot_chat_id(self):
    url = f"https://api.telegram.org/bot{self.settings['telegramBotToken']}/getUpdates"
    response = requests.get(url).json()
    print(response)  # Add this line to print the entire response
    if 'result' in response and len(response['result']) > 0:
        return response['result'][0]['message']['chat']['id']
    else:
        raise ValueError("No messages found in response")

def send_message(self, string):
    try:
        if self.settings['telegramBotChatId'] == "":
            self.settings['telegramBotChatId'] = self.get_telegram_bot_chat_id()

        message = f"{self.wallet_id}: {string}"
        url = f"https://api.telegram.org/bot{self.settings['telegramBotToken']}/sendMessage?chat_id={self.settings['telegramBotChatId']}&text={message}"
        response = requests.get(url).json()
        # print(response)  # This sends the message and prints the response
        if not response.get("ok"):
            raise ValueError(f"Failed to send message: {response}")
    except ValueError as e:
        print(f"Error: {e}")

def increase_step(self):
    step_int = int(self.step) + 1
    self.step = f"{step_int:02}"

def update_settings(self):

    def update_setting(setting_key, message, default_value):
        current_value = self.settings.get(setting_key, default_value)
        response = input(f"\n{message} (Y/N, press Enter to keep current [{current_value}]): ").strip().lower()
        if response == "y":
            self.settings[setting_key] = True
        elif response == "n":
            self.settings[setting_key] = False

    update_setting("forceClaim", "Shall we force a claim on first run? Does not wait for the timer to be filled", self.settings["forceClaim"])
    update_setting("debugIsOn", "Should we enable debugging? This will save screenshots in your local drive", self.settings["debugIsOn"])
    update_setting("hideSensitiveInput", "Should we hide sensitive input? Your phone number and seed phrase will not be visible on the screen", self.settings["hideSensitiveInput"])
    update_setting("screenshotQRCode", "Shall we allow log in by QR code? The alternative is by phone number and one-time password", self.settings["screenshotQRCode"])

    try:
        new_max_sessions = int(input(f"\nEnter the number of max concurrent claim sessions. Additional claims will queue until a session slot is free.\n(current: {self.settings['maxSessions']}): "))
        self.settings["maxSessions"] = new_max_sessions
    except ValueError:
        self.output("Number of sessions remains unchanged.", 1)

    try:
        new_verbose_level = int(input("\nEnter the number for how much information you want displaying in the console.\n 3 = all messages, 2 = claim steps, 1 = minimal steps\n(current: {}): ".format(self.settings['verboseLevel'])))
        if 1 <= new_verbose_level <= 3:
            self.settings["verboseLevel"] = new_verbose_level
            self.output("Verbose level updated successfully.", 2)
        else:
            self.output("Verbose level remains unchanged.", 2)
    except ValueError:
        self.output("Verbose level remains unchanged.", 2)

    try:
        new_telegram_verbose_level = int(input("\nEnter the Telegram verbose level (3 = all messages, 2 = claim steps, 1 = minimal steps)\n(current: {}): ".format(self.settings['telegramVerboseLevel'])))
        if 1 <= new_telegram_verbose_level <= 3:
            self.settings["telegramVerboseLevel"] = new_telegram_verbose_level
            self.output("Telegram verbose level updated successfully.", 2)
        else:
            self.output("Telegram verbose level remains unchanged.", 2)
    except ValueError:
        self.output("Telegram verbose level remains unchanged.", 2)

    try:
        new_lowest_offset = int(input("\nEnter the lowest possible offset for the claim timer (valid values are -30 to +30 minutes)\n(current: {}): ".format(self.settings['lowestClaimOffset'])))
        if -30 <= new_lowest_offset <= 30:
            self.settings["lowestClaimOffset"] = new_lowest_offset
            self.output("Lowest claim offset updated successfully.", 2)
        else:
            self.output("Invalid range for lowest claim offset. Please enter a value between -30 and +30.", 2)
    except ValueError:
        self.output("Lowest claim offset remains unchanged.", 2)

    try:
        new_highest_offset = int(input("\nEnter the highest possible offset for the claim timer (valid values are 0 to 60 minutes)\n(current: {}): ".format(self.settings['highestClaimOffset'])))
        if 0 <= new_highest_offset <= 60:
            self.settings["highestClaimOffset"] = new_highest_offset
            self.output("Highest claim offset updated successfully.", 2)
        else:
            self.output("Invalid range for highest claim offset. Please enter a value between 0 and 60.", 2)
    except ValueError:
        self.output("Highest claim offset remains unchanged.", 2)

    if self.settings["lowestClaimOffset"] > self.settings["highestClaimOffset"]:
        self.settings["lowestClaimOffset"] = self.settings["highestClaimOffset"]
        self.output("Adjusted lowest claim offset to match the highest as it was greater.", 2)

    update_setting("useProxy", "Use Proxy?", self.settings["useProxy"])

    if self.settings["useProxy"]:
        proxy_address = input(f"\nEnter the Proxy IP address and port (current: {self.settings['proxyAddress']}): ").strip()
        if proxy_address:
            self.settings["proxyAddress"] = proxy_address

    update_setting("requestUserAgent", "Shall we collect a User Agent during setup?: ", self.settings["requestUserAgent"])

    # Collect the Telegram Bot Token
    new_telegram_bot_token = input(f"\nEnter the Telegram Bot Token (current: {self.settings['telegramBotToken']}): ").strip()
    if new_telegram_bot_token:
        self.settings["telegramBotToken"] = new_telegram_bot_token

    self.save_settings()

    update_setting("forceNewSession", "Overwrite existing session and Force New Login? Use this if your saved session has crashed\nOne-Time only (setting not saved): ", self.settings["forceNewSession"])

    self.output("\nRevised settings:", 1)
    for key, value in self.settings.items():
        self.output(f"{key}: {value}", 1)
    self.output("", 1)

def get_session_id(self):
    """Prompts the user for a session ID or determines the next sequential ID based on a 'Wallet' prefix.

    Returns:
        str: The entered session ID or the automatically generated sequential ID.
    """
    self.output(f"Your session will be prefixed with: {self.prefix}", 1)
    user_input = input("Enter your unique Session Name here, or hit <enter> for the next sequential wallet: ").strip()

    # Set the directory where session folders are stored
    screenshots_dir = "./screenshots/"

    # Ensure the directory exists to avoid FileNotFoundError
    if not os.path.exists(screenshots_dir):
        os.makedirs(screenshots_dir)

    # List contents of the directory
    try:
        dir_contents = os.listdir(screenshots_dir)
    except Exception as e:
        self.output(f"Error accessing the directory: {e}", 1)
        return None  # or handle the error differently

    # Filter directories with the 'Wallet' prefix and extract the numeric parts
    wallet_dirs = [int(dir_name.replace(self.prefix + 'Wallet', ''))
                for dir_name in dir_contents
                if dir_name.startswith(self.prefix + 'Wallet') and dir_name[len(self.prefix) + 6:].isdigit()]

    # Calculate the next wallet ID
    next_wallet_id = max(wallet_dirs) + 1 if wallet_dirs else 1

    # Use the next sequential wallet ID if no user input was provided
    if not user_input:
        user_input = f"Wallet{next_wallet_id}"  # Ensuring the full ID is prefixed correctly

    return self.prefix+user_input

def prompt_user_agent(self):
    print (f"Step {self.step} - Please enter the User-Agent string you wish to use or press enter for default.")
    user_agent = input(f"Step {self.step} - User-Agent: ").strip()
    return user_agent

def set_cookies(self):
    if not (self.forceRequestUserAgent or self.settings["requestUserAgent"]):
        cookies_path = f"{self.session_path}/cookies.json"
        cookies = self.driver.get_cookies()
        with open(cookies_path, 'w') as file:
            json.dump(cookies, file)
    else:
        user_agent = self.prompt_user_agent()
        cookies_path = f"{self.session_path}/cookies.json"
        cookies = self.driver.get_cookies()
        cookies.append({"name": "user_agent", "value": user_agent})  # Save user agent to cookies
        with open(cookies_path, 'w') as file:
            json.dump(cookies, file)

def setup_driver(self):
    chrome_options = Options()
    chrome_options.add_argument(f"user-data-dir={self.session_path}")
    chrome_options.add_argument("--headless")  # Ensure headless is enabled
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-dev-shm-usage")

    # Attempt to load user agent from cookies
    try:
        cookies_path = f"{self.session_path}/cookies.json"
        with open(cookies_path, 'r') as file:
            cookies = json.load(file)
            user_agent_cookie = next((cookie for cookie in cookies if cookie["name"] == "user_agent"), None)
            if user_agent_cookie and user_agent_cookie["value"]:
                user_agent = user_agent_cookie["value"]
                self.output(f"Using saved user agent: {user_agent}", 2)
            else:
                user_agent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/124.0.2478.50 Version/17.0 Mobile/15E148 Safari/604.1"
                self.output("No user agent found, using default.", 2)
    except FileNotFoundError:
        user_agent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/124.0.2478.50 Version/17.0 Mobile/15E148 Safari/604.1"
        self.output("Cookies file not found, using default user agent.", 2)

    chrome_options.add_argument(f"user-agent={user_agent}")

    chrome_options.add_argument("--disable-blink-features=AutomationControlled")
    chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
    chrome_options.add_experimental_option('useAutomationExtension', False)

    if self.settings["useProxy"]:
        proxy_server = self.settings["proxyAddress"]
        chrome_options.add_argument(f"--proxy-server={proxy_server}")

    chrome_options.add_argument("--ignore-certificate-errors")
    chrome_options.add_argument("--allow-running-insecure-content")
    chrome_options.add_argument("--test-type")

    chromedriver_path = shutil.which("chromedriver")
    if chromedriver_path is None:
        self.output("ChromeDriver not found in PATH. Please ensure it is installed.", 1)
        exit(1)

    try:
        service = Service(chromedriver_path)
        self.driver = webdriver.Chrome(service=service, options=chrome_options)
        return self.driver
    except Exception as e:
        self.output(f"Initial ChromeDriver setup may have failed: {e}", 1)
        self.output("Please ensure you have the correct ChromeDriver version for your system.", 1)
        exit(1)

def run_http_proxy(self):
    proxy_lock_file = "./start_proxy.txt"
    max_wait_time = 15 * 60  # 15 minutes
    wait_interval = 5  # 5 seconds
    start_time = time.time()

    while os.path.exists(proxy_lock_file) and (time.time() - start_time) < max_wait_time:
        self.output("Proxy is already running. Waiting for it to free up...", 2)
        time.sleep(wait_interval)

    if os.path.exists(proxy_lock_file):
        self.output("Max wait time elapsed. Proceeding to run the proxy.", 2)

    with open(proxy_lock_file, "w") as lock_file:
        lock_file.write(f"Proxy started at: {time.ctime()}\n")

    try:
        subprocess.run(['./launch.sh', 'enable-proxy'], check=True)
        self.output("http-proxy started successfully.", 2)
    except subprocess.CalledProcessError as e:
        self.output(f"Failed to start http-proxy: {e}", 1)
    finally:
        os.remove(proxy_lock_file)

def get_driver(self):
    if self.driver is None:  # Check if driver needs to be initialized
        self.manage_session()  # Ensure we can start a session
        self.driver = self.setup_driver()
        self.output("\nCHROME DRIVER INITIALISED: Try not to exit the script before it detaches.",2)
    return self.driver

def quit_driver(self):
    if self.driver:
        self.driver.quit()
        self.output("\nCHROME DRIVER DETACHED: It is now safe to exit the script.",2)
        self.driver = None
        self.release_session()  # Mark the session as closed

def manage_session(self):
    current_session = self.session_path
    current_timestamp = int(time.time())
    session_started = False
    new_message = True
    output_priority = 2

    while True:
        try:
            with open(self.status_file_path, "r+") as file:
                flock(file, LOCK_EX)
                status = json.load(file)

                # Clean up expired sessions
                for session_id, timestamp in list(status.items()):
                    if current_timestamp - timestamp > 300:  # 5 minutes
                        del status[session_id]
                        self.output(f"Removed expired session: {session_id}", 3)

                # Check for available slots, exclude current session from count
                active_sessions = {k: v for k, v in status.items() if k != current_session}
                if len(active_sessions) < self.settings['maxSessions']:
                    status[current_session] = current_timestamp
                    file.seek(0)
                    json.dump(status, file)
                    file.truncate()
                    self.output(f"Session started: {current_session} in {self.status_file_path}", 3)
                    flock(file, LOCK_UN)
                    session_started = True
                    break
                flock(file, LOCK_UN)

            if not session_started:
                self.output(f"Waiting for slot. Current sessions: {len(active_sessions)}/{self.settings['maxSessions']}", output_priority)
                if new_message:
                    new_message = False
                    output_priority = 3
                time.sleep(random.randint(5, 15))
            else:
                break

        except FileNotFoundError:
            # Create file if it doesn't exist
            with open(self.status_file_path, "w") as file:
                flock(file, LOCK_EX)
                json.dump({}, file)
                flock(file, LOCK_UN)
        except json.decoder.JSONDecodeError:
            # Handle empty or corrupt JSON
            with open(self.status_file_path, "w") as file:
                flock(file, LOCK_EX)
                self.output("Corrupted status file. Resetting...", 3)
                json.dump({}, file)
                flock(file, LOCK_UN)

def release_session(self):
    current_session = self.session_path
    current_timestamp = int(time.time())

    with open(self.status_file_path, "r+") as file:
        flock(file, LOCK_EX)
        status = json.load(file)
        if current_session in status:
            del status[current_session]
            file.seek(0)
            json.dump(status, file)
            file.truncate()
        flock(file, LOCK_UN)
        self.output(f"Session released: {current_session}", 3)

def log_into_telegram(self, user_input=None):

    self.step = "01"

    # Check and recreate directories
    self.session_path = f"./selenium/{user_input}"
    if os.path.exists(self.session_path):
        shutil.rmtree(self.session_path)
    os.makedirs(self.session_path, exist_ok=True)

    self.screenshots_path = f"./screenshots/{user_input}"
    if os.path.exists(self.screenshots_path):
        shutil.rmtree(self.screenshots_path)
    os.makedirs(self.screenshots_path, exist_ok=True)

    self.backup_path = f"./backups/{user_input}"
    if os.path.exists(self.backup_path):
        shutil.rmtree(self.backup_path)
    os.makedirs(self.backup_path, exist_ok=True)

    def visible_QR_code():
        max_attempts = 5
        attempt_count = 0
        last_url = "not a url"  # Placeholder for the last detected QR code URL

        xpath = "//canvas[@class='qr-canvas']"
        self.driver.get(self.url)
        wait = WebDriverWait(self.driver, 30)
        self.output(f"Step {self.step} - Waiting for the first QR code - may take up to 30 seconds.", 1)
        self.increase_step()
        QR_code = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))

        if not QR_code:
            return False

        wait = WebDriverWait(self.driver, 2)

        while attempt_count < max_attempts:
            try:
                QR_code = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
                QR_code.screenshot(f"{self.screenshots_path}/Step {self.step} - Initial QR code.png")
                image = Image.open(f"{self.screenshots_path}/Step {self.step} - Initial QR code.png")
                decoded_objects = decode(image)
                if decoded_objects:
                    this_url = decoded_objects[0].data.decode('utf-8')
                    if this_url != last_url:
                        last_url = this_url  # Update the last seen URL
                        attempt_count += 1
                        self.output("*** Important: Having @HereWalletBot open in your Telegram App might stop this script from logging in! ***\n", 2)
                        self.output(f"Step {self.step} - Our screenshot path is {self.screenshots_path}\n", 1)
                        self.output(f"Step {self.step} - Generating screenshot {attempt_count} of {max_attempts}\n", 2)
                        qrcode_terminal.draw(this_url)
                    if attempt_count >= max_attempts:
                        self.output(f"Step {self.step} - Max attempts reached with no new QR code.", 1)
                        return False
                    time.sleep(0.5)  # Wait before the next check
                else:
                    time.sleep(0.5)  # No QR code decoded, wait before retrying
            except TimeoutException:
                self.output(f"Step {self.step} - QR Code is no longer visible.", 2)
                return True  # Indicates the QR code has been scanned or disappeared

        self.output(f"Step {self.step} - Failed to generate a valid QR code after multiple attempts.", 1)
        return False  # If loop completes without a successful scan

    self.driver = self.get_driver()

    # QR Code Method
    if self.settings['screenshotQRCode']:
        try:
            while True:
                if visible_QR_code():  # QR code not found
                    self.test_for_2fa()
                    return  # Exit the function entirely

                # If we reach here, it means the QR code is still present:
                choice = input(f"\nStep {self.step} - QR Code still present. Retry (r) with a new QR code or switch to the OTP method (enter): ")
                print("")
                if choice.lower() == 'r':
                    visible_QR_code()
                else:
                    break

        except TimeoutException:
            self.output(f"Step {self.step} - Canvas not found: Restart the script and retry the QR Code or switch to the OTP method.", 1)

    # OTP Login Method
    self.increase_step()
    self.output(f"Step {self.step} - Initiating the One-Time Password (OTP) method...\n",1)
    self.driver.get(self.url)
    xpath = "//button[contains(@class, 'btn-primary') and contains(., 'Log in by phone Number')]"
    self.move_and_click(xpath, 30, True, "switch to log in by phone number", self.step, "visible")
    self.increase_step()

    # Country Code Selection
    xpath = "//div[@class='input-field-input']"
    self.target_element = self.move_and_click(xpath, 30, True, "update user's country", self.step, "visible")
    if not self.target_element:
        self.output(f"Step {self.step} - Failed to find country input field.", 1)
        return

    user_input = input(f"Step {self.step} - Please enter your Country Name as it appears in the Telegram list: ").strip()
    self.target_element.send_keys(user_input)
    self.target_element.send_keys(Keys.RETURN)
    self.increase_step()

    # Phone Number Input
    xpath = "//div[@class='input-field-input' and @inputmode='decimal']"
    self.target_element = self.move_and_click(xpath, 30, True, "request user's phone number", self.step, "visible")
    if not self.target_element:
        self.output(f"Step {self.step} - Failed to find phone number input field.", 1)
        return

    def validate_phone_number(phone):
        # Regex for validating an international phone number without leading 0 and typically 7 to 15 digits long
        pattern = re.compile(r"^[1-9][0-9]{6,14}$")
        return pattern.match(phone)

    while True:
        if self.settings['hideSensitiveInput']:
            user_phone = getpass.getpass(f"Step {self.step} - Please enter your phone number without leading 0 (hidden input): ")
        else:
            user_phone = input(f"Step {self.step} - Please enter your phone number without leading 0 (visible input): ")

        if validate_phone_number(user_phone):
            self.output(f"Step {self.step} - Valid phone number entered.",3)
            break
        else:
            self.output(f"Step {self.step} - Invalid phone number, must be 7 to 15 digits long and without leading 0.",1)
    self.target_element.send_keys(user_phone)
    self.increase_step()

    # Wait for the "Next" button to be clickable and click it
    xpath = "//button//span[contains(text(), 'Next')]"
    self.move_and_click(xpath, 15, True, "click next to proceed to OTP entry", self.step, "visible")
    self.increase_step()

    try:
        # Attempt to locate and interact with the OTP field
        wait = WebDriverWait(self.driver, 20)
        if self.settings['debugIsOn']:
            time.sleep(3)
            self.driver.save_screenshot(f"{self.screenshots_path}/Step {self.step} - Ready_for_OTP.png")
        password = wait.until(EC.visibility_of_element_located((By.XPATH, "//input[@type='tel']")))
        otp = input(f"Step {self.step} - What is the Telegram OTP from your app? ")
        password.click()
        password.send_keys(otp)
        self.output(f"Step {self.step} - Let's try to log in using your Telegram OTP.\n",3)
        self.increase_step()

    except TimeoutException:
        # Check for Storage Offline
        xpath = "//button[contains(text(), 'STORAGE_OFFLINE')]"
        self.move_and_click(xpath, 8, True, "check for 'STORAGE_OFFLINE'", self.step, "visible")
        if self.target_element:
            self.output(f"Step {self.step} - ***Progress is blocked by a 'STORAGE_OFFLINE' button",1)
            self.output(f"Step {self.step} - If you are re-usi,ng an old Wallet session; try to delete or create a new session.",1)
            found_error = True
        # Check for flood wait
        xpath = "//button[contains(text(), 'FLOOD_WAIT')]"
        self.move_and_click(xpath, 8, True, "check for 'FLOOD_WAIT'", self.step, "visible")
        if self.target_element:
            self.output(f"Step {self.step} - ***Progress is blocked by a 'FLOOD_WAIT' button", 1)
            self.output(f"Step {self.step} - You need to wait for the specified number of seconds before retrying.", 1)
            self.output(f"Step {self.step} - {self.target_element.text}")
            found_error = True
        if not found_error:
            self.output(f"Step {self.step} - Selenium was unable to interact with the OTP screen for an unknown reason.")

    except Exception as e:  # Catch any other unexpected errors
        self.output(f"Step {self.step} - Login failed. Error: {e}", 1)
        if self.settings['debugIsOn']:
            self.driver.save_screenshot(f"{self.screenshots_path}/Step {self.step} - error_Something_Occured.png")

    self.increase_step()
    self.test_for_2fa()

    if self.settings['debugIsOn']:
        time.sleep(3)
        self.driver.save_screenshot(f"{self.screenshots_path}/Step {self.step} - After_Entering_OTP.png")

def test_for_2fa(self):
    try:
        self.increase_step()
        WebDriverWait(self.driver, 30).until(lambda d: d.execute_script('return document.readyState') == 'complete')
        xpath = "//input[@type='password' and contains(@class, 'input-field-input')]"
        fa_input = self.move_and_click(xpath, 10, False, "check for 2FA requirement (will timeout if you don't have 2FA)", self.step, "present")

        if fa_input:
            if self.settings['hideSensitiveInput']:
                tg_password = getpass.getpass(f"Step {self.step} - Enter your Telegram 2FA password: ")
            else:
                tg_password = input(f"Step {self.step} - Enter your Telegram 2FA password: ")
            fa_input.send_keys(tg_password + Keys.RETURN)
            self.output(f"Step {self.step} - 2FA password sent.\n", 3)
            self.output(f"Step {self.step} - Checking if the 2FA password is correct.\n", 2)

            xpath = "//*[contains(text(), 'Incorrect password')]"
            try:
                incorrect_password = WebDriverWait(self.driver, 3).until(EC.visibility_of_element_located((By.XPATH, xpath)))
                self.output(f"Step {self.step} - 2FA password is marked as incorrect by Telegram - check your debug screenshot if active.", 1)
                if self.settings['debugIsOn']:
                    screenshot_path = f"{self.screenshots_path}/Step {self.step} - Incorrect 2FA Password.png"
                    self.driver.save_screenshot(screenshot_path)
                self.quit_driver()
                sys.exit()  # Exit if incorrect password is detected
            except TimeoutException:
                pass

            self.output(f"Step {self.step} - No password error found.", 3)
            xpath = "//input[@type='password' and contains(@class, 'input-field-input')]"
            fa_input = self.move_and_click(xpath, 5, False, "final check to make sure we are correctly logged in", self.step, "present")
            if fa_input:
                self.output(f"Step {self.step} - 2FA password entry is still showing, check your debug screenshots for further information.\n", 1)
                sys.exit()
            self.output(f"Step {self.step} - 2FA password check appears to have passed OK.\n", 3)
        else:
            self.output(f"Step {self.step} - 2FA input field not found.\n", 1)

    except TimeoutException:
        # 2FA field not found
        self.output(f"Step {self.step} - Two-factor Authorization not required.\n", 3)

    except Exception as e:  # Catch any other unexpected errors
        self.output(f"Step {self.step} - Login failed. 2FA Error - you'll probably need to restart the script: {e}", 1)
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/Step {self.step} - error_Something_Bad_Occurred.png"
            self.driver.save_screenshot(screenshot_path)

def next_steps(self):
    # Must OVERRIDE this function in the child class
    self.output("Function 'next-steps' - Not defined (Need override in child class) \n", 1)

def launch_iframe(self):
    self.driver = self.get_driver()

    try:
        self.driver.get(self.url)
        WebDriverWait(self.driver, 30).until(lambda d: d.execute_script('return document.readyState') == 'complete')
        self.output(f"Step {self.step} - Attempting to verify if we are logged in (hopefully QR code is not present).", 3)
        xpath = "//canvas[@class='qr-canvas']"
        wait = WebDriverWait(self.driver, 5)
        wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/Step {self.step} - Test QR code after session is resumed.png"
            self.driver.save_screenshot(screenshot_path)
        self.output(f"Step {self.step} - Chrome driver reports the QR code is visible: It appears we are no longer logged in.", 2)
        self.output(f"Step {self.step} - Most likely you will get a warning that the central input box is not found.", 2)
        self.output(f"Step {self.step} - System will try to restore session, or restart the script from CLI to force a fresh log in.\n", 2)

    except TimeoutException:
        self.output(f"Step {self.step} - nothing found to action. The QR code test passed.\n", 3)
    self.increase_step()

    self.driver.get(self.url)
    WebDriverWait(self.driver, 30).until(lambda d: d.execute_script('return document.readyState') == 'complete')

    # There is a very unlikely scenario that the chat might have been cleared.
    # In this case, the "START" button needs pressing to expose the chat window!
    xpath = "//button[contains(., 'START')]"
    button = self.move_and_click(xpath, 3, False, "check for the start button (should not be present)", self.step, "clickable")
    if button:
        button.click()
    self.increase_step()

    # New link logic to avoid finding an expired link
    if self.find_working_link(self.step):
        self.increase_step()
    else:
        self.send_start(self.step)
        self.increase_step()
        self.find_working_link(self.step)
        self.increase_step()

    # Now let's move to and JS click the "Launch" Button
    xpath = "//button[contains(@class, 'popup-button') and contains(., 'Launch')]"
    button = self.move_and_click(xpath, 8, False, "click the 'Launch' button (probably not present)", self.step, "clickable")
    if button:
        button.click()
    self.increase_step()

    # HereWalletBot Pop-up Handling
    self.select_iframe(self.step)
    self.increase_step()

def full_claim(self):
    # Must OVERRIDE this function in the child class
    self.output("Function 'full_claim' - Not defined (Need override in child class) \n", 1)

def get_balance(self,claimed=False):
    # Must OVERRIDE this function in the child class
    self.output("Function 'get_balance' - Not defined (Need override in child class) \n", 1)

def clear_screen(self):
    # Attempt to clear the screen after entering the seed phrase or mobile phone number.
    # For Windows
    if os.name == 'nt':
        os.system('cls')
    # For macOS and Linux
    else:
        os.system('clear')

def select_iframe(self, old_step):
    self.output(f"Step {self.step} - Attempting to switch to the app's iFrame...",2)

    try:
        wait = WebDriverWait(self.driver, 20)
        popup_body = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "popup-body")))
        iframe = popup_body.find_element(By.TAG_NAME, "iframe")
        self.driver.switch_to.frame(iframe)
        self.output(f"Step {self.step} - Was successfully able to switch to the app's iFrame.\n",3)

        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/{self.step}-iframe-switched.png"
            self.driver.save_screenshot(screenshot_path)

    except TimeoutException:
        self.output(f"Step {self.step} - Failed to find or switch to the iframe within the timeout period.\n",3)
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/{self.step}-iframe-timeout.png"
            self.driver.save_screenshot(screenshot_path)
    except Exception as e:
        self.output(f"Step {self.step} - An error occurred while attempting to switch to the iframe: {e}\n",3)
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/{self.step}-iframe-error.png"
            self.driver.save_screenshot(screenshot_path)

def send_start(self, old_step):
    xpath = "//div[contains(@class, 'input-message-container')]/div[contains(@class, 'input-message-input')][1]"

    def attempt_send_start():
        chat_input = self.move_and_click(xpath, 5, False, "find the chat window/message input box", self.step, "present")
        if chat_input:
            self.increase_step()
            self.output(f"Step {self.step} - Attempting to send the '/start' command...",2)
            chat_input.send_keys("/start")
            chat_input.send_keys(Keys.RETURN)
            self.output(f"Step {self.step} - Successfully sent the '/start' command.\n",3)
            if self.settings['debugIsOn']:
                screenshot_path = f"{self.screenshots_path}/{self.step}-sent-start.png"
                self.driver.save_screenshot(screenshot_path)
            return True
        else:
            self.output(f"Step {self.step} - Failed to find the message input box.\n",1)
            return False

    if not attempt_send_start():
        # Attempt failed, try restoring from backup and retry
        self.output(f"Step {self.step} - Attempting to restore from backup and retry.\n",2)
        if self.restore_from_backup(self.backup_path):
            if not attempt_send_start():  # Retry after restoring backup
                self.output(f"Step {self.step} - Retried after restoring backup, but still failed to send the '/start' command.\n",1)
        else:
            self.output(f"Step {self.step} - Backup restoration failed or backup directory does not exist.\n",1)

def restore_from_backup(self, path):
    if os.path.exists(path):
        try:
            self.quit_driver()
            shutil.rmtree(self.session_path)
            shutil.copytree(path, self.session_path, dirs_exist_ok=True)
            self.driver = self.get_driver()
            self.driver.get(self.url)
            WebDriverWait(self.driver, 10).until(lambda d: d.execute_script('return document.readyState') == 'complete')
            self.output(f"Step {self.step} - Backup restored successfully.",2)
            return True
        except Exception as e:
            self.output(f"Step {self.step} - Error restoring backup: {e}\n",1)
            return False
    else:
        self.output(f"Step {self.step} - Backup directory does not exist.\n",1)
        return False

def move_and_click(self, xpath, wait_time, click, action_description, old_step, expectedCondition):
    def timer():
        return random.randint(1, 3) / 10

    self.output(f"Step {self.step} - Attempting to {action_description}... [{xpath}]", 2)

    wait = WebDriverWait(self.driver, wait_time)
    target_element = None
    for attempt in range(5):  # Retry loop for handling StaleElementReferenceException
        try:
            if expectedCondition == "visible":
                target_element = wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))
            elif expectedCondition == "present":
                target_element = wait.until(EC.presence_of_element_located((By.XPATH, xpath)))
            elif expectedCondition == "invisible":
                wait.until(EC.invisibility_of_element_located((By.XPATH, xpath)))
                return None
            elif expectedCondition == "clickable":
                target_element = wait.until(EC.element_to_be_clickable((By.XPATH, xpath)))

            if target_element is None:
                self.output(f"Step {self.step} - The element was not found for {action_description}.", 2)
                return None

            actions = ActionChains(self.driver)
            actions.move_to_element(target_element).pause(timer()).perform()

            self.driver.execute_script("""
                var elem = arguments[0];
                var rect = elem.getBoundingClientRect();
                var isVisible = (
                    rect.top >= 0 &&
                    rect.left >= 0 &&
                    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
                );
                if (!isVisible) {
                    elem.scrollIntoView({block: 'center'});
                }
            """, target_element)

            is_in_viewport = self.driver.execute_script("""
                var elem = arguments[0], box = elem.getBoundingClientRect();
                return (box.top >= 0 && box.left >= 0 &&
                        box.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                        box.right <= (window.innerWidth || document.documentElement.clientWidth));
            """, target_element)

            if not is_in_viewport:
                self.output(f"Step {self.step} - Element still out of bounds after moving with ActionChains and JavaScript scrolling.", 2)
                continue

            if click or expectedCondition in ["visible", "clickable"]:
                self.clear_overlays(target_element, self.step)

            if click:
                if self.click_element(xpath, action_description=action_description):
                    self.output(f"Step {self.step} - Successfully clicked {action_description}.", 3)
                    return target_element
            else:
                return target_element

        except StaleElementReferenceException:
            self.output(f"Step {self.step} - StaleElementReferenceException caught, retrying attempt {attempt + 1} for {action_description}.", 2)
        except TimeoutException:
            self.output(f"Step {self.step} - Timeout while trying to {action_description}.", 3)
            self.debug_information(action_description)
            break
        except Exception as e:
            self.output(f"Step {self.step} - An error occurred while trying to {action_description}: {e}", 1)
            self.debug_information(action_description)
            break

    return target_element

def click_element(self, xpath, timeout=30, action_description=""):
    end_time = time.time() + timeout
    while time.time() < end_time:
        try:
            element = self.driver.find_element(By.XPATH, xpath)
            actions = ActionChains(self.driver)
            actions.move_to_element(element).perform()

            self.driver.execute_script("""
                var elem = arguments[0];
                var rect = elem.getBoundingClientRect();
                var isVisible = (
                    rect.top >= 0 &&
                    rect.left >= 0 &&
                    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
                );
                if (!isVisible) {
                    elem.scrollIntoView({block: 'center'});
                }
            """, element)

            overlays_cleared = self.clear_overlays(element, self.step)
            if overlays_cleared > 0:
                self.output(f"Step {self.step} - Cleared {overlays_cleared} overlay(s), retrying click...", 3)

            actions.click(element).perform()
            return True
        except ElementClickInterceptedException as e:
            intercepting_element = self.driver.execute_script(
                "var elem = arguments[0];"
                "var rect = elem.getBoundingClientRect();"
                "var x = rect.left + (rect.width / 2);"
                "var y = rect.top + (rect.height / 2);"
                "return document.elementFromPoint(x, y);", element)
            if intercepting_element:
                self.driver.execute_script("arguments[0].style.display = 'none';", intercepting_element)
                self.output(f"Step {self.step} - Intercepting element hidden, retrying click...", 3)
        except UnexpectedAlertPresentException:
            alert = self.driver.switch_to.alert
            alert.accept()
            self.output(f"Step {self.step} - Unexpected alert handled.", 3)
        except (StaleElementReferenceException, NoSuchElementException):
            pass
        except TimeoutException:
            self.output(f"Step {self.step} - Click timed out.", 2)
            break
        except Exception as e:
            self.output(f"Step {self.step} - An error occurred: {e}", 3)
            break
    return False

def clear_overlays(self, target_element, step):
    try:
        element_location = target_element.location_once_scrolled_into_view
        overlays = self.driver.find_elements(By.XPATH, "//*[contains(@style,'position: absolute') or contains(@style,'position: fixed')]")
        overlays_cleared = 0
        for overlay in overlays:
            overlay_rect = overlay.rect
            if (overlay_rect['x'] <= element_location['x'] <= overlay_rect['x'] + overlay_rect['width'] and
                overlay_rect['y'] <= element_location['y'] <= overlay_rect['y'] + overlay_rect['height']):
                self.driver.execute_script("arguments[0].style.display = 'none';", overlay)
                overlays_cleared += 1
        self.output(f"Step {step} - Removed {overlays_cleared} overlay(s) covering the target.", 3)
        return overlays_cleared
    except Exception as e:
        self.output(f"Step {step} - An error occurred while trying to clear overlays: {e}", 1)
        return 0

def monitor_element(self, xpath, timeout=8, action_description=""):
    end_time = time.time() + timeout
    first_time = True
    while time.time() < end_time:
        try:
            elements = self.driver.find_elements(By.XPATH, xpath)
            if first_time:
                self.output(f"Step {self.step} - Found {len(elements)} elements with XPath: {xpath} for {action_description}", 3)
                first_time = False

            texts = [element.text.replace('\n', ' ').replace('\r', ' ').strip() for element in elements if element.text.strip()]
            if texts:
                return ' '.join(texts)
        except (StaleElementReferenceException, TimeoutException, NoSuchElementException):
            pass
        except Exception as e:
            self.output(f"An error occurred: {e}", 3)
            if self.settings['debugIsOn']:
                self.debug_information(action_description, "monitor_element_error")
            return "Unknown"
    return "Unknown"

def debug_information(self, action_description, error_type="error"):
    # Check if "not" is present in the action_description enclosed in brackets
    if re.search(r'\(.*?not.*?\)', action_description, re.IGNORECASE):
        # Skip debugging if the condition is met
        return

    if self.settings['debugIsOn']:
        # Take a screenshot on error
        screenshot_path = f"{self.screenshots_path}/{self.step}_{action_description}_{error_type}.png"
        self.driver.save_screenshot(screenshot_path)
        self.output(f"Screenshot saved to {screenshot_path}", 3)

        # Save the HTML page source on error
        page_source = self.driver.page_source
        with open(f"{self.screenshots_path}/{self.step}_{action_description}_page_source.html", "w", encoding="utf-8") as f:
            f.write(page_source)

        # Save browser console logs on error
        logs = self.driver.get_log("browser")
        with open(f"{self.screenshots_path}/{self.step}_{action_description}_browser_console_logs.txt", "w", encoding="utf-8") as f:
            for log in logs:
                f.write(f"{log['level']}: {log['message']}\n")

def find_working_link(self, old_step):

    start_app_xpath = self.start_app_xpath
    self.output(f"Step {self.step} - Attempting to open a link for the app: {start_app_xpath}...", 2)

    try:
        # Wait for the presence of all elements located by the specified XPath with extended timeout
        start_app_buttons = WebDriverWait(self.driver, 10).until(
            EC.presence_of_all_elements_located((By.XPATH, start_app_xpath))
        )
        clicked = False

        if not start_app_buttons:
            self.output(f"Step {self.step} - No buttons found with XPath: {start_app_xpath}\n", 1)
            if self.settings['debugIsOn']:
                screenshot_path = f"{self.screenshots_path}/{self.step}-no-buttons-found.png"
                self.driver.save_screenshot(screenshot_path)
            return False

        # Iterate through the buttons in reverse order
        for button in reversed(start_app_buttons):
            actions = ActionChains(self.driver)
            actions.move_to_element(button).pause(0.2)
            try:
                # Save screenshot if debug is on
                if self.settings['debugIsOn']:
                    self.driver.save_screenshot(f"{self.screenshots_path}/{self.step} - Find working link.png")
                # Perform actions and click the button using JavaScript
                actions.perform()
                self.driver.execute_script("arguments[0].click();", button)
                clicked = True
                break
            except (StaleElementReferenceException, ElementClickInterceptedException):
                self.output(f"Step {self.step} - Button click intercepted or stale, trying next button.", 2)
                continue

        # Check if a button was successfully clicked
        if not clicked:
            self.output(f"Step {self.step} - None of the 'Open Wallet' buttons were clickable.\n", 1)
            if self.settings['debugIsOn']:
                screenshot_path = f"{self.screenshots_path}/{self.step}-no-clickable-button.png"
                self.driver.save_screenshot(screenshot_path)
            return False
        else:
            self.output(f"Step {self.step} - Successfully able to open a link for the app..\n", 3)
            if self.settings['debugIsOn']:
                screenshot_path = f"{self.screenshots_path}/{self.step}-app-opened.png"
                self.driver.save_screenshot(screenshot_path)
            return True

    except TimeoutException:
        self.output(f"Step {self.step} - Failed to find the 'Open Wallet' button within the expected timeframe.\n", 1)
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/{self.step}-timeout-finding-button.png"
            self.driver.save_screenshot(screenshot_path)
        return False
    except Exception as e:
        self.output(f"Step {self.step} - An error occurred while trying to open the app: {e}\n", 1)
        if self.settings['debugIsOn']:
            screenshot_path = f"{self.screenshots_path}/{self.step}-unexpected-error-opening-app.png"
            self.driver.save_screenshot(screenshot_path)
        return False

def validate_seed_phrase(self):
    # Let's take the user inputed seed phrase and carry out basic validation
    while True:
        # Prompt the user for their seed phrase
        if self.settings['hideSensitiveInput']:
            self.seed_phrase = getpass.getpass(f"Step {self.step} - Please enter your 12-word seed phrase (your input is hidden): ")
        else:
            self.seed_phrase = input(f"Step {self.step} - Please enter your 12-word seed phrase (your input is visible): ")
        try:
            if not self.seed_phrase:
                raise ValueError(f"Step {self.step} - Seed phrase cannot be empty.")

            words = self.seed_phrase.split()
            if len(words) != 12:
                raise ValueError(f"Step {self.step} - Seed phrase must contain exactly 12 words.")

            pattern = r"^[a-z ]+$"
            if not all(re.match(pattern, word) for word in words):
                raise ValueError(f"Step {self.step} - Seed phrase can only contain lowercase letters and spaces.")
            return self.seed_phrase  # Return if valid

        except ValueError as e:
            self.output(f"Error: {e}",1)

# Start a new PM2 process
def start_pm2_app(self, script_path, app_name, session_name):
    interpreter_path = "venv/bin/python3"
    command = f"NODE_NO_WARNINGS=1 pm2 start {script_path} --name {app_name} --interpreter {interpreter_path} --watch {script_path} -- {session_name}"
    subprocess.run(command, shell=True, check=True)

# Save the new PM2 process
def save_pm2(self):
    command = f"NODE_NO_WARNINGS=1 pm2 save"
    result = subprocess.run(command, shell=True, text=True, capture_output=True)
    print(result.stdout)

def backup_telegram(self):

    # Ask the user if they want to backup their Telegram directory
    backup_prompt = input("Would you like to backup your Telegram directory? (Y/n): ").strip().lower()
    if backup_prompt == 'n':
        self.output(f"Step {self.step} - Backup skipped by user choice.", 3)
        return

    # Ask the user for a custom filename
    custom_filename = input("Enter a custom filename for the backup (leave blank for default): ").strip()

    # Define the backup destination path
    if custom_filename:
        backup_directory = os.path.join(os.path.dirname(self.session_path), f"Telegram:{custom_filename}")
    else:
        backup_directory = os.path.join(os.path.dirname(self.session_path), "Telegram")

    try:
        # Ensure the backup directory exists and copy the contents
        if not os.path.exists(backup_directory):
            os.makedirs(backup_directory)
        shutil.copytree(self.session_path, backup_directory, dirs_exist_ok=True)
        self.output(f"Step {self.step} - We backed up the session data in case of a later crash!", 3)
    except Exception as e:
        self.output(f"Step {self.step} - Oops, we weren't able to make a backup of the session data! Error: {e}", 1)

def get_seed_phrase_from_file(self, screenshots_path):
    seed_file_path = os.path.join(screenshots_path, 'seed.txt')
    if os.path.exists(seed_file_path):
        with open(seed_file_path, 'r') as file:
            return file.read().strip()
    return None

How is the implementation of this function going?

Hi,
It's working well in my local environment, but @Wellynounet had some problems with the code I sent him. Unfortunately, it's my days for being at my "real" work, but I'll get back to it in the coming days.


Add metadata to a track from a different track

-s now accepts another track which can be used as a metadata source.
This metadata source track needs to be separated using "::" from the
track to be downloaded.
For example:
$ spotdl -s "nightcore janji heroes::janji heroes"

This will download the nightcore version of the track but the original
track would be used for metadata. Similarly, one may also pass Spotify
URIs or YouTube URLs in either of the two tracks.
Closes #500.

Looks like this feature has dropped in Spotdlv3. Is that correct?

Looks like this feature has dropped in Spotdlv3. Is that correct?

@last72 Yes.

@last72 Yes.


clickhouse-backup: 2.5.27 -> 2.5.29

Automatic update generated by nixpkgs-update tools. This update was made based on information from https://github.com/Altinity/clickhouse-backup/releases.
meta.description for clickhouse-backup is: Tool for easy ClickHouse backup and restore using object storage for backup files
meta.homepage for clickhouse-backup is: https://github.com/Altinity/clickhouse-backup
Updates performed

Golang update

To inspect upstream changes

Release on GitHub

Compare changes on GitHub

Impact
Checks done

built on NixOS
The tests defined in passthru.tests, if any, passed
found 2.5.29 with grep in /nix/store/nlp7yhbfllxvqb0rjq12gx1wj6sl0m7y-clickhouse-backup-2.5.29
found 2.5.29 in filename of file in /nix/store/nlp7yhbfllxvqb0rjq12gx1wj6sl0m7y-clickhouse-backup-2.5.29

Rebuild report (if merged into master) (click to expand)

2 total rebuild path(s)

2 package rebuild(s)

First fifty rebuilds by attrpath

clickhouse-backup

Instructions to test this update (click to expand)

Either download from Cachix:
nix-store -r /nix/store/nlp7yhbfllxvqb0rjq12gx1wj6sl0m7y-clickhouse-backup-2.5.29 \
  --option binary-caches 'https://cache.nixos.org/ https://nix-community.cachix.org/' \
  --option trusted-public-keys '
  nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
  cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
  '

(The Cachix cache is only trusted for this store-path realization.)
For the Cachix download to work, your user must be in the trusted-users list or you can use sudo since root is effectively trusted.
Or, build yourself:
nix-build -A clickhouse-backup https://github.com/r-ryantm/nixpkgs/archive/54d75a194fe684e2a762a61cb9f64abf40681707.tar.gz

Or:
nix build github:r-ryantm/nixpkgs/54d75a194fe684e2a762a61cb9f64abf40681707#clickhouse-backup

After you've downloaded or built it, look at the files and if there are any, run the binaries:
ls -la /nix/store/nlp7yhbfllxvqb0rjq12gx1wj6sl0m7y-clickhouse-backup-2.5.29
ls -la /nix/store/nlp7yhbfllxvqb0rjq12gx1wj6sl0m7y-clickhouse-backup-2.5.29/bin

Pre-merge build results
We have automatically built all packages that will get rebuilt due to
this change.
This gives evidence on whether the upgrade will break dependent packages.
Note sometimes packages show up as failed to build independent of the
change, simply because they are already broken on the target branch.
Result of nixpkgs-review run on x86_64-linux 1

1 package built:

clickhouse-backup

Maintainer pings
cc @devusb for testing.

Add a :+1: reaction to pull requests you find important.

Result of nixpkgs-review pr 335644 run on x86_64-linux 1

1 package built:

clickhouse-backup


Cannot install on Python version 3.11.2; only versions >=3.7,<3.11 are supported.

at
https://github.com/xinntao/facexlib/blob/29d792eb2a48e4e87b2b57001a85d2297c439a6f/facexlib/tracking/data_association.py
just comment out numba import and @jit and compile, it works :)
import numpy as np
from numba import jit
from scipy.optimize import linear_sum_assignment as linear_assignment
@jit

can confirm this fixes the error

at 29d792e/facexlib/tracking/data_association.py
just comment out numba import and @jit and compile, it works :)
import numpy as np from numba import jit from scipy.optimize import linear_sum_assignment as linear_assignment
@jit


Bisect series error

Trying to find what is broken in current main.
Slack thread: https://cloud-native.slack.com/archives/C01AUBA4PFE/p1730547278346759
Version ad4857de5210bb1756b46c9ae6e653c72cc7654d shown BAD.
Version 7d6884e3e shown GOOD.
Version now under test: b8867f8ea

/prombench v2.55.0

/prombench restart v2.55.0

/prombench restart v2.55.0

/prombench restart v2.55.0

/prombench restart v2.55.0

/prombench cancel

Proposed a revert at #15316


Consider to resolve ${workspaceFolder} in a multi root workspace that has only one folder

VSCode Version: 1.20.1
OS Version: W10

Steps to Reproduce:

Create a new workspace and save it in the root of you repository

{
    "folders": [
        {
            "path": "."
        }
    ],
    "settings": {
    }
}

Add the following launch settings in your .code-workspace file

"settings": {
    "launch": {
        "verison": "0.2.0",
        "configurations": [
            {
                "type": "node",
                "request": "launch",
                "name": "Run program",
                "args": ["app.js"],
                "cwd": "${workspaceRoot}"
            }
        ]
    }
}

Open the your workspace you just created.
Launch the debugger by running "Run program (workspace)".
Notice the error; Attribute cwd is not absolute ('${workspaceRoot}'). consider adding '${workspaceFolder}/ as a prefix to make it absolute.

Expected behaviour
To make it more backwards compatible with older behaviour, old variables like ${workspaceRoot} and ${workspaceFolder} should be relative to either your .vscode folder, your code-workspace file etc. So instead of complaining, it should resolve the path of ${workspaceRoot} to the same path as your *.code-workspace file.
If I postfix my variable with the folder name from workspace file; { "folders": [ { "name": "myProject", "path": "./project" } ] } like  "cwd": "${workspaceRoot:myProject}" it should resolve the path to the path of<EMAIL_ADDRESS>that again is relative to the code-workspace file. Old variables should work from global settings, user settings (.vscode) and workspace (*.code-workspace) and be relative to each of them.

details in an earlier discussion in #43591

Since this "older behaviour" existed only for 1 or 2 milestones and was never documented, I'm reluctant to add this "magic".
In addition, since it becomes immediately wrong if another folder is added, we can only support the "magic" if are able to show errors whenever a variable cannot be resolved.

Hmm, if you add another folder, it stills resolve to the same path of the code-workspace file. It will not resolve to another path by magic?
If you move your .code-workspace file, yes, then it will suddenly resolve to another path than it previously did.
Are we talking about a different magic? Nothing magic as I see it. It will behave exactly the same no matter how many folders you have in your workspace. Let it be exactly as it is today, but make it possible to use only ${workspaceFolder} without postfix instead of throwing error. The most natural path to choose then, is the path where the code-workspace file currently is saved

We cannot resolve the "workspaceRoot" variable to the location of the workspace file because that would introduce a new concept is VS Code. The (deprecated) "workspaceRoot" variable represents the root path of a workspace in "classic" VS Code. In a new "multi-root" workspace they do not really have a meaning as there are multiple roots. Just tying them to the the "workspace file" is an arbitrary choice since "workspace files" can live anywhere and its location has no specific semantics. Many users keep their workspace files on the desktop so that they can easily open a workspace just by double-clickable them. Resolving "workspaceRoot" to the "Desktop" doesn't make a lot of sense.
A better solution would be to resolve "workspaceRoot" to the location of the first root folder of a workspace. This would have addressed your use case as well.

Now if there is only one folder in a workspace we will resolve against that folder. You can check it out in tomorrow's vscode insiders
https://github.com/Microsoft/vscode/commit/872de2a131ac60b6b2e2fe477514d67afec55db9

@isidorn
thanks, will check it out
@weinand
"In a new "multi-root" workspace "workspaceRoot" does not really have a meaning as there are multiple roots."
workspaceRoot may be deprecated, but shouldn't workspaceFolder resolve to the path of the .code-workspace file? After all, all the paths under "folders: []" can be both relative to the .code-workspace file and absolute.
... the path is actually relative by just defining "path": "." when you first click "Save workspace as".

@lostfields I'm repeating myself but I'll try again:
Resolving "workspaceRoot" (or "workspaceFolder") to the "workspace file" location is an arbitrary choice since "workspace files" can live anywhere and its location has no specific semantics. Many users keep their workspace files on the desktop so that they can easily open a workspace just by double-clickable them. Resolving "workspaceRoot" to the "Desktop" doesn't make a lot of sense.


Update cloudwatch, sdk-core, ses, sns, sqs, ssm to 2.20.115

About this PR
📦 Updates

software.amazon.awssdk:cloudwatch
software.amazon.awssdk:sdk-core
software.amazon.awssdk:ses
software.amazon.awssdk:sns
software.amazon.awssdk:sqs
software.amazon.awssdk:ssm

from 2.20.110 to 2.20.115
Usage
✅ Please merge!
I'll automatically update this PR to resolve conflicts as long as you don't change it yourself.
If you'd like to skip this version, you can just close this PR. If you have any feedback, just mention me in the comments below.
Configure Scala Steward for your repository with a .scala-steward.conf file.
Have a fantastic day writing Scala!

⚙ Adjust future updates
Add this to your .scala-steward.conf file to ignore future updates of this dependency:
updates.ignore = [ { groupId = "software.amazon.awssdk" } ]

Or, add this to slow down future updates of this dependency:
dependencyOverrides = [{
  pullRequests = { frequency = "30 days" },
  dependency = { groupId = "software.amazon.awssdk" }
}]

labels: library-update, early-semver-patch, semver-spec-patch, commit-count:1

Superseded by #2820.


Improve blitting APIs

deprecate blit(), renamed to blitDEPRECATED. It's only used in one
place in copyFrame() now. We can't void it because we don't have
access to the texture from the RenderTarget.

add a new blit() api that works with textures instead of render
targets.

add a new resolve() api that works with textures instead of render
targets. doesn't support scaling.

always use a shader when scaling in the framegraph
(there was only one place where we used blit)

use the new blit() for:

for mipmap generation on vk (fixme)
copying the depth buffer to avoid ssao feedback loop

use the new resolve() for:

manually resolving MSAA color/depth

LGTM so far

VulkanDriver::generateMipMap lgtm


Hard fork intg test more coverage berkeley

Explain your changes:
Adding transactions and zkapps with valid_until and also adds zkapps that upgrade the verification-key-permission version.
Explain how you tested your changes:
*
Checklist:

[ ] Dependency versions are unchanged

Notify Velocity team if dependencies must change in CI

[ ] Modified the current draft of release notes with details on what is completed or incomplete within this project
[ ] Document code purpose, how to use it

Mention expected invariants, implicit constraints

[ ] Tests were added for the new behavior

Document test purpose, significance of failures
Test names should reflect their purpose

[ ] All tests pass (CI will check this if you didn't)
[ ] Serialized types are in stable-versioned modules
[ ] Does this close issues? List them

Closes #0000

!ci-nightly-me

!ci-build-me

!ci-build-me

!ci-nightly-me


draining karpenter-provisioned node resulted in another karpenter-provisioned node while capacity is available in a managed eks nodegroup

Version

Karpenter: v0.10.0

Kubernetes: Server Version: version.Info{Major:"1", Minor:"22+", GitVersion:"v1.22.9-eks-a64ea69", GitCommit:"540410f9a2e24b7a2a870ebfacb3212744b5f878", GitTreeState:"clean", BuildDate:"2022-05-12T19:15:31Z", GoVersion:"go1.16.15", Compiler:"gc", Platform:"linux/amd64"}
Expected Behavior
I have spot eks managed node group "apps" with label "apps=true"
AWS terminates spot node
AWS node termination handler makes immediate node drain and all pods targeted with nodeSelector apps=true get pending status
AWS Karpenter Kicks in
5m later spot eks managed node group "apps"  gets new node in READY state
I want to stop manually the karpenter provisioner node and do this:
k drain --delete-emptydir-data --ignore-daemonsets -l karpenter.sh/provisioner-name=apps
I expect the node to be drained all pods relocated to eks managed node group "apps"
Actual Behavior
Another karpenter node started with provisioner, however I have available capacity in a new node added by eks managed node group.
Steps to Reproduce the Problem
provisioner:
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: apps
spec:
  labels:
    apps: "true"
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["spot"]
    - key: kubernetes.io/arch
      operator: In
      values: ["amd64"]
    - key: node.kubernetes.io/instance-type
      operator: In
      values: ["m5.large", "t3.large"]
    - key: topology.kubernetes.io/zone
      operator: In
      values: ["us-east-1a", "us-east-1b", "us-east-1c"]
  limits:
    resources:
      cpu: '5'
  provider:
    subnetSelector:
      karpenter.sh/discovery: prod-eks
    securityGroupSelector:
      karpenter.sh/discovery: prod-eks
    tags:
      karpenter.sh/discovery: prod-eks
      terraform: "false"
      karpenter: "true"
      managed_by: "karpenter"
      environment: "prod"
      karpenter.sh/cluster/prod-eks: owned
      kubernetes.io/cluster/prod-eks: owned
  ttlSecondsAfterEmpty: 300

There are pods in Pending state, and you are saying since the already existing nodes have enough capacity Karpenter should not launch a new node. Is that correct?

correct. The spot managed node group with the same labels exists, yet the karpenter keeps spawning new nodes when I drain the karpenter node. It should be moved to existing capacity instead, but it's not.

Karpenter will only provision nodes if pods are marked as unschedulable, so somehow those pods are not able to be scheduled to the node group. Try tossing a taint on your provisioner or delete it entirely, and see if you can get those pods scheduled onto the node group you're expecting should work.

ok will try and report tomorrow.

Hey @dmitry-mightydevops ,
Anything to report on this issue?  Or can we close it out?
Thanks!

Pls give me a time till EOW - I will then carefully test it and report it.


Add skipTitle Configuration. Fixes #121

Add skipTitle configuration option to skip mention-bot if the PR title contains specific text.
Example:
{
  "skipTitle": "WIP"
}

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at<EMAIL_ADDRESS>Thanks!

Fixes https://github.com/facebook/mention-bot/issues/121

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

Thanks, the code looks really good 👍


Heroes of Might and Magic V corruptions and lockups on Haswell

Software information
Heroes of Might and Magic V shows graphics corruptions when using dxvk that do not happen with wined3d. I captured an apitrace with dxvk v1.10.1 using apitrace-11.1-win32. Replaying the attached trace with apitrace-11.1-win64 and dxvk is a reproducible way to totally crash my graphical session (Gnome Shell using Wayland).
I also captured a trace with wined3d but I noticed that a trace captured with wined3d can not be replayed with dxvk. Curiously, the trace captured with dxvk can not be replayed with wined3d either. This is why I didn't include the wined3d trace here.
I could not create a renderdoc capture from the trace because this would crash wine:
$ ENABLE_VULKAN_RENDERDOC_CAPTURE=1 WINEDLLOVERRIDES="d3d9,d3d11,dxgi=n,b" wine apitrace.exe replay H5_Game_trimmed_packed.trace
Z:\REDACTED\.steam\steam\steamapps\common\Heroes of Might and Magic 5\bin\H5
_Game.exe
[0x7B60D190] ANOMALY: Instruction "lea" is modifying the stack
[0x7B60D1A8] ANOMALY: Instruction "lea" is modifying the stack
info:  Game: H5_Game.exe
info:  DXVK: v1.10.1
info:  Built-in extension providers:
info:    Win32 WSI
info:    OpenVR
info:    OpenXR
info:  OpenVR: could not open registry key, status 2
info:  OpenVR: Failed to locate module
0: debug: apitrace: warning: caught exception 0xc0000005
apitrace: warning: caught exception 0xc0000005
0: error: caught an unhandled exception
wine: Unhandled page fault on read access to<PHONE_NUMBER>000017 at address 00007F51BE4D173F (thread 0100), starting debugger...
...

I know that Haswell is a bit old but a crash of the graphical session is a bit much. That may be a Mesa bug, but for reporting a bug to Mesa I need a renderdoc capture ...
System information

GPU: Intel(R) HD Graphics 4600 (HSW GT2)
Driver: Mesa 22.0.5
Wine version: wine-6.0.3 (Ubuntu 6.0.3~repack-1)
DXVK version: v1.10.1

Apitrace file(s)
H5_Game_trimmed_packed.trace.zip
Log files
H5_Game_d3d9.log

Can you post a screenshot of the graphical corruptions. Didn't notice anything obvious in the trace on my R9 380.
Also try latest master and see if it makes a difference https://github.com/doitsujin/dxvk/actions/runs/3137839527

I have an image captured by a smartphone. A screenshot wasn't possible because the graphical session crashed.

This is almost certainly a driver bug.
Vulkan support on anything before Maxwell is incomplete.
Nothing we can do here.

Is there a way for me to get a renderdoc?

It it crashes, not really.
Just attach the apitrace.


Compression again

Hi all
Any news on getting compression into restic? Last update I saw was two an a half years ago (almost) and that thread was closed. Is it so hard to add lib(something)z to the code?
roy

Compression is tracked in #21, and call I can say is that it's not off the radar or anything like that.


chore: Add gekkowrld and fix markdown syntax

What changes does this PR do?
Add Gekko Wrld to the list of Community and fix some markdown syntax errors
Questions

So I should delete this one and create two new PR's?

So I should delete this one and create two new PR's?

That'd be good, thanks. :)


Version 5.87.0 breaking some 3rd part dependencies

Description
Version 5.87.0 (verified!) of paket introduced a regression and remove 3rd part dependencies. They are removed of the paket.lockfile and of the csproj files preventing to build.
Repro steps

Create a console project (c#) and convert to paket

With Paket >=5.87.0, add the nuget packagesCommon.Logging to the project

Add the line ILog logger = LogManager.GetLogger("test"); and using Common.Logging;in the only cs file.

Verify that you can't build. The interface ILog is not recognised. And the dependency Common.Logging.Core is missing.

Delete the paket.lock and do a paket installwith an older version of paket. You can build and the dependency is here in the lock file and the csproj.

@matthid any ideas?

@pmiossec can you please upload a dependencies file that shows this?

Can you change to the v2 url of the nuget feed? This is probably a duplicate of https://github.com/fsprojects/Paket/issues/2652.
But please provide the required info to be absolutely sure.

V2

I'm off until Monday that's why I would like to report the bug before (very sorry for that) . I will try to do that as soon as possible but at least on Monday...
Thanks a lot for the quick diagnosis...

I hope on monday you go to work and everything is already fixed ;-)

ok I pushed a temporary workaround which automatically uses v2 feed. This gives us a bit time until we do the correct fix. you may need to use -f to invalidate the cache.

I have tested the new version and the workaround works.
No need I provide some file or repository any more?

No. It's fine. We have a repro and we understood the root cause. It's
"just" a matter of providing a proper fix now..
Thanks for reporting and confirmation
Am 24.08.2017 5:31 nachm. schrieb "Philippe Miossec" <
<EMAIL_ADDRESS>
I have tested the new version and the workaround works.
No need I provide some file or repository any more?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/fsprojects/Paket/issues/2656#issuecomment-324671089,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADgNCPHpXFrTsIrM5IZxifXcyIcTa57ks5sbZc9gaJpZM4PBNjH
.

Ok. Thanks a lot.


How to visualize the 3D predictions on the remote server?

I want to visualize the detection results as .obj files on the remote server, but I found that using only '-show-dir' still calls the following code, which will call open3d and cause an error.
https://github.com/open-mmlab/mmdetection3d/blob/4ff136163ee4e6436e7f5c44f73c4a4932aa7657/mmdet3d/visualization/local_visualizer.py#L160-L227
Is there any solution for this?

Download the files locally and run
import open3d as o3d
import numpy as np

# Load the point cloud
def read_points_from_obj(file_path):
    points = []
    with open(file_path, 'r') as file:
        for line in file:
            if line.startswith('v '):
                _, x, y, z = line.strip().split()
                points.append([float(x), float(y), float(z)])
    return np.array(points)

# Load the points from the file
# points = read_points_from_obj("points.obj")

points = read_points_from_obj("/home/maciej/code/pred/points1.obj")
point_cloud = o3d.geometry.PointCloud()
point_cloud.points = o3d.utility.Vector3dVector(points)
# Load the predicted objects
predictions = o3d.io.read_triangle_mesh("/home/maciej/code/pred/pred1.obj")
vis = o3d.visualization.Visualizer()
vis.create_window()

# Add the geometries
vis.add_geometry(point_cloud)
vis.add_geometry(predictions)

# Run the visualizer
vis.run()


Error: Malformed UTF-8 data

https://sentry.io/tendermint/cosmos-voyager/issues/790774744
Error: Malformed UTF-8 data
  at Object.stringify (/C:/Users/powerup/Desktop/%EC%83%88%20%ED%8F%B4%EB%8D%94%20(2)/resources/app/dist/renderer.js:6586:24)
  at WordArray.init.toString (/C:/Users/powerup/Desktop/%EC%83%88%20%ED%8F%B4%EB%8D%94%20(2)/resources/app/dist/renderer.js:6323:38)
  at Store.loadPersistedState (/C:/Users/powerup/Desktop/%EC%83%88%20%ED%8F%B4%EB%8D%94%20(2)/resources/app/dist/renderer.js:67123:27)
  at Array.wrappedActionHandler (/C:/Users/powerup/Desktop/%EC%83%88%20%ED%8F%B4%EB%8D%94%20(2)/resources/app/dist/renderer.js:5875:23)
  at Store.dispatch (/C:/Users/powerup/Desktop/%EC%83%88%20%ED%8F%B4%EB%8D%94%20(2)/resources/app/dist/renderer.js:5597:15)
...
(8 additional frame(s) were not displayed)

Apparently decrypting the state failed. I will catch decryption errors and remove the cache if this happens as a safety net.


Extension created with your project cannot get loaded in chrome

When I try to load the extension I get this error:
Cannot load extension with file or directory name framework. Filenames starting with "" are reserved for use by the system.
Could not load manifest

Are you able to share a minimum reproducible sample project?

the _framework  ==> the underscore in the folder name cause an error. Underscore cannot be used in chrome extension folder name.
Just create a sample project and try to load it in chrome as an extension and you will see the error.

The folder should be renamed during the build, you will then be able to load the extension from the browser. If you are trying to load from the wwwroot folder it will not work though. It should be loaded from the browserextension folder.


fix(moac): volume remains in destroyed state

For some reason which is out of scope of this PR, mayastor fails to
send a reply to grpc request issued by moac. That makes the whole
life-cycle of the volume stuck until the moac is restarted. It's
a general problem pertaining to all grpc operations. We need to
set a timeout on all grpc requests to prevent this from happening.
The default timeout for all grpc calls is 15s now. The destroy
replica call with timeout of 1 hour being exception because it is
known to be slow when the replica is large.
This might trigger new bugs in mayastor as some operations might
be resent now. It is a question of how mayastor will handle a new
request while the old one is still in the progress. We will fix
those as we discover them. This could have happened even before.
It's just that likelyhood has increased now.
Resolves: CAS-896

Would we want to add now big timeouts for Create and Destroy calls to mayastor? Internally, mayastor will connect to etcd and if that is taking a long time (slow disk or maybe etcd is temporarily not available) then might be safer to have bigger timeouts to start with?

That is a question. Currently we wait 15s for all calls. That is far bigger than round-trip time on LAN that could be units of ms. I think we want to see errors in moac log if etcd is down or unreachable. I think we will need to find out what is the right value. But how about 30s? Or do you think it should be even higher?

Well at the moment mayastor uses a 30s timeout for etcd, after which it will timeout and attempt to reconnect again.
Maybe 1 minute?

1 minute seems like a good start 👍

I have updated the PR and also the commit message to mention explicitly non-default timeout for create and destroy nexus calls.

bors merge


ISPN-7322 Improve triangle algorithm: ordering by segment

https://issues.jboss.org/browse/ISPN-7322
Send the backup updates unordered (OOB) and the ordering is done at Infinispan level.

run performance tests please

@Holmistr cool! :)
can I trust the results post by the "bot"?

@pruivo I'm just writing an email to infinispan mailing list, give me 30 minutes, everything should be explained there :)

@danberindei updated! if it is ok for you, trigger another perf run to see if I didn't mess up anything :P

n performance tests please

run performance tests please

run performance tests please

@pruivo @danberindei I was doing some changes yesterday and I accidentally broke uploading the link back on the PR, however, the tests run successfully, report generation was also successful.
Link to the results is here.

@Holmistr I was wondering what happened :)
@danberindei the results looks good.

Integrated, thanks Pedro!


An Actual Apk App

Can you build an easy Android App that can download provided m3u8 hls links and save them as mp4 or even better as mkv x265 to save disk space and

This project doesn't aim to provide an android app but you can use vsd inside termux on android. Then re-encode downloaded stream to x256 mkv using ffmpeg.


feat: Move Transaction from transaction_execution.rs to transaction.rs

Move Transaction from transaction_execution.rs to transaction.rs

[x] issue #1737

This change is

@hakymulla this repo has been deprecated and moved into https://github.com/starkware-libs/sequencer
Can you redo your PR there please?


[ZEPPELIN-5316] Incorrect markdown at '/quickstart/docker.html'

What is this PR for?
Fix incorrect markdown at /quickstart/docker.md
What type of PR is it?
[Documentation]
Todos

[ ] - Task

What is the Jira issue?

https://issues.apache.org/jira/browse/ZEPPELIN-5316

How should this be tested?

Checked update document locally

Screenshots (if appropriate)
Before

After

Questions:

Does the licenses files need update? No
Is there breaking changes for older versions? No
Does this needs documentation? No

LGTM, thanks @cuspymd


Configuration error when building Conduit/HDF5 with uberenv using CMake 3.19/3.21

Error:
CMake Error at /usr/tce/packages/cmake/cmake-3.21.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find HDF5 (missing: HDF5_LIBRARIES) (found version "1.8.22")
Call Stack (most recent call first):
  /usr/tce/packages/cmake/cmake-3.21.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:594 (_FPHSA_FAILURE_MESSAGE)
  /usr/tce/packages/cmake/cmake-3.21.1/share/cmake/Modules/FindHDF5.cmake:1001 (find_package_handle_standard_args)
  /usr/WS1/han12/axom/uberenv_libs/clang-10.0.0/conduit-0.7.2axom/lib/cmake/conduit/conduit_setup_deps.cmake:59 (find_package)
  /usr/WS1/han12/axom/uberenv_libs/clang-10.0.0/conduit-0.7.2axom/lib/cmake/conduit/ConduitConfig.cmake:71 (include)
  cmake/thirdparty/FindConduit.cmake:29 (find_package)
  cmake/thirdparty/SetupAxomThirdParty.cmake:93 (include)
  cmake/CMakeBasics.cmake:20 (include)
  CMakeLists.txt:104 (include)

To reproduce:

change CMake version in packages.yaml to 3.21.1

./scripts/uberenv/uberenv.py<EMAIL_ADDRESS>

configure with uberenv-generated host-config

@bmhan12 does it work with cmake 3.18.x or 3.20.x?

Is this with newish spack?
If so you have to make sure you are using conduit@develop b/c newer spack switched to build hdf5 with cmake, which required extra logic in conduit.

@rhornung67  3.18 is okay, 3.20 has the same error.
Can also reproduce the error by changing the CMake executable path, without having to rebuild the TPL's
https://github.com/LLNL/axom/blob/3999cf27a5437b032f9d3e23ca4476c04d139a2e/host-configs/rzgenie-toss_3_x86_64_ib-clang%4010.0.0.cmake#L4

Thanks for checking. We should keep an eye on this since I believe MARBL is updating to CMake 3.21. @kennyweiss any thoughts on how to handle this to prevent application user issues?

@bmhan12  can you post the spack spec for this build?

Ok -- I see this is with conduit-0.7.2, if newer spack you need conduit@develop for sure.
No way around it b/c spack changed how hdf5 was built. Since we are using older versions of hdf5 and their cmake build systems are frozen in time.
We test conduit vs 3.20 and 3.21 as part of CI, if this is a newer spack issue I think that the solution.


[Bug] Issue after going to back to video from PiP mode.

The Issue
Steps to reproduce

Play video
Press home button to enter PiP mode
Try to open Odysee from PiP mode to get back to the video

Expected behavior
After pressing the enlarge button in PiP mode application should open the video page.
Actual behavior
After pressing the enlarge button in PiP mode application opens Home page.
System Configuration

Device: Oneplus 5T (A5010)
Android version: LineageOS 18.1 (Android 11)

Anything Else
After coming back to the application on Home screen player isn't even minimized.
To get back to the video you need to switch to other tab (Following/Wallet/Library) and then player will be shown minimized.
Screen recording

Internal Use
Acceptance Criteria

Definition of Done

[ ]  Tested against acceptance criteria
[ ] Tested against the assumptions of the user story
[ ] The project builds without errors
[ ] Unit tests are written and passing
[ ] Tests on devices/browsers listed in the issue have passed
[ ] QA performed & issues resolved
[ ] Refactoring completed
[ ] Any configuration or build changes documented
[ ] Documentation updated
[ ] Peer Code Review performed

Thanks for the issues. This one is covered in https://github.com/OdyseeTeam/odysee-android/issues/91