text:
Questions and Answers about Imola friday

For 3 times every 3 minutes, when I am recording fp1 data, cmd tells me 'received no data for more than 60 seconds, exiting'. I don't know, my connection seems stable, but maybe this is the problem and I am wrong... Anyway the file it saved is not txt but it's a file that windows doesn't recognize. Is it normal? And HOW to downgrade to 2.1.4 if I get the same problem in FP2????

I cannot reproduce this problem currently. The recording is running just fine for me. Is there any more information in the log?
The file type is always a text file. If windows doesn't recognize it, you haven't added .txt to the file name you provided in the command.
python -m fastf1.livetiming save test.txt

If you still think that downgrading may help (I can't see how it would, there were no notable changes to the client):
pip uninstall fastf1
pip install fastf1==2.1.4

Now it's working, but I didn't add .txt... Will it read the file or should I restart record?

You don't need to add it. That's just so windows would recognize it automatically. Python doesn't care. And you can still open it with a text editor.
Renaming it to .txt after the recording has finished is also possible if you want that for convenience.

Anyway for tomorrow I need to modify the 60 seconds to more. What is the code to do it?

It seems working fine now, I think the problem was my connection, sorry... If for today it works, than you can close the issue.

Now trying to see the speed graphic it gives me this error...

You can look up how to increase the timeout in the documentation. The command line tool also shows you usage information.
In the image above you are loading some sort of Brazilian GP?! Is that intentional?
Apart from that, there is currently no live timing data available for download. The live timing replay in the official app is not (yet) working either. So it's something with their servers. Meaning you currently can't get data for this session if you don't have it recorded.

No it's not intentional... What am I doing wrong in the code?? I recorded live timing from fp1 of Imola, it should work...

I started recording after about 20 minutes that the session started for that problem.. Is that the error??

No, I see the problem. The official name is "Emilia Romagna Grand Prix". Your "Emilian Grand Prix" is closer to "Brazilian Grand Prix" when the code is doing a fuzzy string match.
You might want to use the event number instead of the name, i.e.
ff1.get_session(2021, 2, 'FP2')

Did it, this time the folder created in cache is correct (Emilia Romagna Grand Prix), but the error is the same... It saves only these 3 files and than gives me error. I think for some reasons the recording could be corrupted. Do you have the recording of FP1 of this morning?

I've also encountered an error while trying to load the telemetry for this session. I was trying to use the load_telemetry function with data that i recorded. I also tried the load_laps function and the same error mentioned above showed up.
Below is the error for load_telemetry.

@ricky732 No, it's not your recording that's the problem here. There's some missing data (mainly tire information) which apparently wasn't available for this session. (I read some comments about how this data was also missing in the official app)
The code is not capable to deal with this. Let's just hope it works in FP2.
@JM-Monteiro Telemetry data cannot be loaded if the lap data hasn't been loaded yet. That's probably not entirely obvious from the documentation. Sorry for that.
Because of the the problem with the missing data above, you will not be able to load this data.

Thanks for the quick reply! 😀

Same error here. Will try FP2 later

Question: If for example instead of taking fastest lap of a driver I would like to take a lap that was deleted due to track-limits, what should I do in the code?

Anyway for me FP2 works fine. What a pity I can't know what Leclerc lap it considers as fastest. That one which was deleted or that official one?

Hey @ricky732, my data for some reason got corrupted. Could you post the file you have recorded for FP2?
Thanks in advance!

im_fp2.txt
here it is. @JM-Monteiro

So guys, I just made this unic script for all the data. @theOehrly , it works very well and I am very happy with that, but the different graphics are showed everytime I close one. Is there a way to show all of them in the same time??

Ok, so some more general info about FP1 again. The recordings from FP1 are missing everything but lap times as far as I can tell now. As there were major technical issues at the track in FP1 (TV feed not working properly, radios not working, driver tracker not working) I'll attribute the problems to this. I won't try to work around this to make whatever remains of the data usable with this package.
Deleted laps are most likely included in the data. So if the fastest lap is a deleted lap it will be returned as the result of .get_fastest(). I'm not even sure if the raw api data provides information on deleted laps.
You'll have to manually compare the lap time to the official session results to be sure. I'll see what can be done about this in the future.
Methods like .pick_driver() return an instance of the Laps object. This is a subclass of pandas DataFrame. This means you can just use the usual pandas methods to sort the data by lap time and get the second fastest lap then (or do some other sorting/slicing/filtering/...).
@ricky732 regarding your code above, you only need to call plt.show once at the end. But that a generic matplotlib question tbh.

Fantastic, thank you!! I know I could make stupid questions sometimes, I am good at pc but I don't have much experience in programmating. I'll read the guide for lap times because I have not clear how should I see all the lap times of a driver and than tell the program what to chose for telemetry data. If it's an easy code and you could explain me, it would be very appreciated and good ❤ Thanks for everything @theOehrly

Ok, some very quick examples here. I'd say, just play around with it in an interactive session for a bit.
And take a look a some basic examples on how to work with pandas.
print(laps)
                      Time DriverNumber  ... TrackStatus  IsAccurate
0   0 days 00:33:33.656000            3  ...           1       False
1   0 days 00:34:51.559000            3  ...           1        True
2   0 days 00:36:26.232000            3  ...           1        True
3   0 days 00:37:43.530000            3  ...           1        True
4   0 days 00:39:28.246000            3  ...           1        True
..                     ...          ...  ...         ...         ...
389 0 days 01:13:19.737000           99  ...           1        True
390 0 days 01:14:39.536000           99  ...           1        True
391 0 days 01:15:58.973000           99  ...           1        True
392 0 days 01:17:57.772000           99  ...           1        True
393 0 days 01:20:04.437000           99  ...           1       False
[394 rows x 25 columns]

lec = laps.pick_driver('LEC')
print(lec)
                      Time DriverNumber  ... TrackStatus  IsAccurate
174 0 days 00:30:44.600000           16  ...           1       False
175 0 days 00:32:01.921000           16  ...           1        True
176 0 days 00:33:43.753000           16  ...           1        True
177 0 days 00:35:00.548000           16  ...           1        True
178 0 days 00:36:42.589000           16  ...           1        True
179 0 days 00:38:15.489000           16  ...           1        True
180 0 days 00:39:31.821000           16  ...           1        True
181 0 days 00:53:00.277000           16  ...          25       False
182 0 days 00:54:35.776000           16  ...           1       False
183 0 days 00:55:51.514000           16  ...           1        True
184 0 days 00:57:57.001000           16  ...           1        True
185 0 days 00:59:22.528000           16  ...           1        True
186 0 days 01:00:38.372000           16  ...           1        True
187 0 days 01:02:28.154000           16  ...           1       False
188 0 days 01:04:51.418000           16  ...           2       False
189 0 days 01:11:06.092000           16  ...           2       False
190 0 days 01:12:54.647000           16  ...           1       False
191 0 days 01:14:14.567000           16  ...           1        True
192 0 days 01:15:34.124000           16  ...           1        True
193 0 days 01:17:25.866000           16  ...           1        True
194 0 days 01:19:40.125000           16  ...           1       False
[21 rows x 25 columns]

print(lec['LapTime'])
174                      NaT
175   0 days 00:01:17.321000
176   0 days 00:01:41.832000
177   0 days 00:01:16.795000
178   0 days 00:01:42.041000
179   0 days 00:01:32.900000
180   0 days 00:01:16.332000
181                      NaT
182                      NaT
183   0 days 00:01:15.738000
184   0 days 00:02:05.487000
185   0 days 00:01:25.527000
186   0 days 00:01:15.844000
187   0 days 00:01:49.782000
188   0 days 00:02:23.264000
189                      NaT
190   0 days 00:01:48.555000
191   0 days 00:01:19.920000
192   0 days 00:01:19.557000
193   0 days 00:01:51.742000
194   0 days 00:02:14.259000
Name: LapTime, dtype: timedelta64[ns]

lec_sorted = lec.sort_values(by='LapTime')
print(lec_sorted['LapTime'])

183   0 days 00:01:15.738000
186   0 days 00:01:15.844000
180   0 days 00:01:16.332000
177   0 days 00:01:16.795000
175   0 days 00:01:17.321000
192   0 days 00:01:19.557000
191   0 days 00:01:19.920000
185   0 days 00:01:25.527000
179   0 days 00:01:32.900000
176   0 days 00:01:41.832000
178   0 days 00:01:42.041000
190   0 days 00:01:48.555000
187   0 days 00:01:49.782000
193   0 days 00:01:51.742000
184   0 days 00:02:05.487000
194   0 days 00:02:14.259000
188   0 days 00:02:23.264000
174                      NaT
181                      NaT
182                      NaT
189                      NaT
Name: LapTime, dtype: timedelta64[ns]

second_fastest = lec_sorted.iloc[1]
print(second_fastest)

Time                  0 days 01:00:38.372000
DriverNumber                              16
LapTime               0 days 00:01:15.844000
LapNumber                                 13
Stint                                      2
PitOutTime                               NaT
PitInTime                                NaT
Sector1Time           0 days 00:00:24.359000
Sector2Time           0 days 00:00:26.269000
Sector3Time           0 days 00:00:25.216000
Sector1SessionTime    0 days 00:59:46.887000
Sector2SessionTime    0 days 01:00:13.156000
Sector3SessionTime    0 days 01:00:38.372000
SpeedI1                                233.0
SpeedI2                                258.0
SpeedFL                                282.0
SpeedST                                287.0
Compound                                SOFT
TyreLife                                 6.0
FreshTyre                               True
LapStartTime          0 days 00:59:22.528000
Team                                 Ferrari
Driver                                   LEC
TrackStatus                                1
IsAccurate                              True
Name: 186, dtype: object

Clear! After this, could you tell me how to use a specific lap for telemetry comparison? I mean, instead of pick_fastest(), what should I write? pick_second for example?

I think I've done it! Amazing!!! Just playing a bit with the code, it's great! I can select every lap now! I have throttle, speed, speed+delta, brake, gearbox and rpm. What is missing guys? What are other possible things maybe I have missed?

I was sure there was a "pick_lap()" but I cannot seem to find it anymore. But please check https://theoehrly.github.io/Fast-F1/core.html for all info on what is available.
On a sidenote remeber that some plots are usually read in a specific way so the line of
plt.gca().invert_yaxis()
could come in handy.

I don't think there ever was something like 'pick_lap()'. But as the data is a DataFrame you could just do something like
lap3 = laps[laps['LapNumber'] == 3] for a specific lap number
and of course the already mentioned
lap = laps.iloc[2] if sort your laps by some metric and then select by index.

The FP1 Highlights in their youtube channel explicitly mentioned the FP1 data issue

@theOehrly , I did exactly in the second way!!! But now I noted this in the Ferrari throttle graphic... In the straights it's not all the time flat out, while the mercedes one is right. How to solve this problem???

By the way guys, I worked on a smoothing for Speed curve. What do you think about the result? Left smoothed, right not smoothed. @theOehrly told that every smooth misses details on the curve, but I don't see so many missing details...

That jitter of the Ferrari throttle very much looks like an issue with the raw data. I guess if you need to remove it you'll have to do it "manually" in some way. Maybe just apply some threshold and set all values that are greater than 98 to 100.
It think something like this might work.
car_data = lap.get_car_data()
throttle = car_data['Throttle'].copy()
throttle.loc[throttle > 98] = 100

There's nothing universal that can be done against something like that. If there's something that looks like an error with the raw data, and you want to remove it, you'll likely have to deal with it differently each time.
The smoothing doesn't look to bad. But it's still losing a bit of detail. I guess whether that's acceptable or not depends on what you want to analyze.

Thank you!!! You should think doing a smoothing only for interested parts without touching entering, minimum speed, and exiting of the corners. I was trying but for me it's not so easy...

authors: FMPete JM-Monteiro harningle ricky732 theOehrly


text:
Allow arbitrary kwargs to subprocess.Popen

In my particular case I need to set the cwd param of the subprocess.

nevermind there's a session.chdir

source: gharchive/pull-request

repo: theacodes/nox

license_type: permissive

license_source: github-api


text:
Add casey muratori's blog to the feed

Adding the casey to the mix
Removing blog after hitting bad rss feed in reload.py
tested locally and checks out with casey's feed

Let me know if this meets the reqs as a solid enough bloggulus rss feed!

Looks good! Can't believe I missed that one...

license: MIT


text:
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://127.0.0.1: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://127.0.0.1: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.

authors: Wellynounet jbr1989 thebrumby

repo: thebrumby/HotWalletClaimer


text:
installation on windows; textual and cartography need hand holding

Installing on windows 10 needed intervention.
This worked for me:
cd emesh/

pip install -r requirements.txt

pip install tree_sitter_languages  pygments  mdurl  linkify-it-py  importlib-metadata markdown-it-py rich mdit-py-plugins

pip install  yarl  click  attrs  async-timeout  aiosignal aiohttp

pip install textual

pip install textual-dev

pip install deterministic-rsa-keygen  cffi  certifi  requests  cryptography

pip install meshtastic

Then the GUI can be started
python gui.py

Hello,
I cant replicate this error (and sorry for the delay). Whic Python version are you using?

source: gharchive/issue

authors: thecookingsenpai tmarkson

repo: thecookingsenpai/emesh


text:
Keep preventDefault during Carousel transition.

Clicking prev / next during a slide transition appends the carousel tag to the URL as there's no event handlers to call e.preventDefault().
Possibly jarring to user experience as the carousel jumps to page top.

Hey, thanks for the contribution. I had something like this in mind and I ended up with the current code and the reason for that is also the click handlers for page navigation (you know the dots).
I would prefer a better solution and more simple: while animating, remove all click handlers for all carousel children.

Please download and test latest commit. Thanks

authors: CamWiseOwl thednp

license: mit

license_source: bigquery


text:
Create sensibly sized staging or local database for nicer workflow

The current database is massive and growing fast, and I think development would go smoother with a nicely representative snapshot of what the data really looks like for future feature dev.
Tools like this one make it possible to create a DB for dev and testing purposes, that can be sanitized for easier local development:
https://github.com/18F/rdbms-subsetter

Part of this, it would be good to have the option of having a huge table if
you need to play around with the db to get an idea of performance issues.
On Thu 21. Nov 2019 at 17:53, Chris Adams notifications@github.com wrote:

The current database is massive and growing fast, and I think development
would go smoother with a nicely representative snapshot of what the data
really looks like for future feature dev.
Tools like this one make it possible to create a DB for dev and testing
purposes, that can be sanitized for easier local development:
https://github.com/18F/rdbms-subsetter
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/thegreenwebfoundation/greenwebfoundation-admin/issues/26?email_source=notifications&email_token=AAQGYETW6LXYHZ62FXJB23DQU24JTA5CNFSM4JQFIVV2YY3PNVWWK3TUL52HS4DFUVEXG43VMWVGG33NNVSW45C7NFSM4H3FDJTA,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAQGYEQ4XYWDD7HZAFWZN4DQU24JTANCNFSM4JQFIVVQ
.

--
Sent from Gmail Mobile

authors: jonathan-s mrchrisadams


text:
Wewbsite

[x] #5
[x] #6
[x] #7
[x] #8
[x] #9
[x] #10
[x] #11
[x] #12

Finally

repo: thekifake/fotology


text:
Please add Arduino NANO ESP32

Link to official doc:
https://docs.arduino.cc/hardware/nano-esp32

The board is now available.


text:
Fix unread marker being removed from DOM

Fixes #762.

Do we need a second review of this one, or is it small enough to go with 1?

@xPaw is on a roll!! 👏

authors: YaManicKill astorije xPaw

repo: thelounge/lounge


text:
🛑 [Gitlab] Gitlab is down

In 2b76994, [Gitlab] Gitlab (https://git.themanaworld.org/explore) was down:

HTTP code: 0
Response time: 0 ms

Resolved: [Gitlab] Gitlab is back up in bb1d585.

repo: themanaworld/upptime


text:
fix(fallback-media-hash): as part of bulk redirects

for thematters/developer-resource#373

closed by https://github.com/thematters/matters-web/pull/4315

repo: thematters/matters-web


text:
Exposing NodeCompleteResult to clients

Added NodeComplete processing to DialogueRunner

Looks great!

This pull request is a reworked version of #30, after discussion on it.

Awesome, thanks!

authors: desplesda thebeardphantom

repo: thesecretlab/YarnSpinner


text:
Running multiple files does not work

While running individual test works perfectly, I'm unable to run multiple test files. My test are written in .ts and setting up a index.js file to configure multiple test doesn't work.
Content of index.js
`
require('ts-node').register();
const { configure } = require('japa');
configure({
files: ['/*.ts'],
experimentalEsmSupport: false,
});
`
Note
experimentalEsmSupport was set to both true and false and return the same result. On the command line. ZERO TESTS EXECUTED was printed.
Package version
4.0.0
Error Message & Stack Trace
ZERO TESTS EXECUTED Printed

Its because your files glob is incorrect. I assume you want to run all .ts file and the glob for that should be.
files: ['**/*.ts']

authors: Danicoco thetutlage

repo: thetutlage/japa


text:
Import tuf.log in test_init.py and fix logger name.  Issue #303.

In PR #307, tuf.log is no longer imported by __init__.py.  Add an import statement for tuf.log to fix missing handler warning in test_init.py.  The logger name specified is also incorrect.

Looks good

Merging!

authors: davedoesdev vladimir-v-diaz

repo: theupdateframework/tuf


text:
Error during template rendering

Error message:
'admin_tags' is not a registered tag library. Must be one of: admin_list admin_modify admin_urls cache i18n l10n log rest_framework static tz
In template /django_chunk_file_upload/templates/django_chunk_file_upload/forms/widgets/drag_drop_input.html, error at line 1
Remedy:
Remove admin tags from that line and everything works

@securespider
Thanks for support, it has been fixed

authors: securespider thewebscraping

repo: thewebscraping/django-chunk-file-upload


text:
fix(test): increase operation assertion timeouts

Proposed changes

Allow enough time to retry fetching the jwt token if it fails the first time.
Types of changes

[x] Bugfix (non-breaking change which fixes an issue)
[ ] New feature (non-breaking change which adds functionality)
[ ] Improvement (general improvements like code refactoring that doesn't explicitly fix a bug or add any new functionality)
[ ] Documentation Update (if none of the other choices apply)
[ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

Paste Link to the issue

Checklist

[x] I have read the CONTRIBUTING doc
[x] I have signed the CLA (in all commits with git commit -s)
[ ] I ran cargo fmt as mentioned in CODING_GUIDELINES
[ ] I used cargo clippy as mentioned in CODING_GUIDELINES
[ ] I have added tests that prove my fix is effective or that my feature works
[ ] I have added necessary documentation (if appropriate)

Further comments

Found during a PR check failure on:

https://github.com/thin-edge/thin-edge.io/actions/runs/4873733875/attempts/1

Allow enough time to retry fetching the jwt token if it fails the first time.

Why should 120 seconds be required to get a token? Something sounds wrong in the system not the tests.

Allow enough time to retry fetching the jwt token if it fails the first time.

Why should 120 seconds be required to get a token? Something sounds wrong in the system not the tests.

@didier-wenzek This is a by-product of the service dependency topic. If the mapper is not ready yet to receive the jwt token request the component asking for the jwt will wait 60 seconds before trying again. The timeout of 120 seconds is just 2 x the wait period in case if the second request also fails (though I can't image that happening too often). So I believe if we implement the health check logic in each of the services then the jwt timeout will not occur very often. Locally the tests are generally ok, however sometimes some of the services are slower to start up in different environments.

authors: didier-wenzek reubenmiller


text:
Rename columns that changed in a recent update

After a recent update to things, the database was moved and some of the columns in the TMTask table were renamed. See discussion on the ticket: https://github.com/thingsapi/things.py/issues/100
There is another problem that the database has also moved. The issue suggests exporting a custom THINGSDB environment variable, but because this PR does not address that issue, I don't feel comfortable marking that issue as resolved by this.

Also, hello! This is my first time contributing. Please let me know if there's anything you would like me to do to make this PR match the project's standards in some way. I didn't see a readme for contributing.

@AlexanderWillner I'm not sure who the right person is to bring this up with, but the library currently will not function without these changes. Is there anything I can do to help move this along? (If you're not the correct person to ping, is there someone else I can ask?) 🙏🏻

@CaAlden Is there anyway for me to use these changes locally on things-cli?

@ishan1m
It might not be the simplest thing, but you could get something working if you checkout things-cli locally and pull in my fork anywhere things-cli is using things from pip. I have a project using things in the same way right now but I'm hoping for this to merge so I can undo the hack-y-ness.
I basically did git submodule add with my fork in my project and changed my references to things to use the local library. (When I did it this way I added an __init__.py to the submodule and changed my imports to import things from things.

Thanks - I'll make the needed changes in all repositories

Thanks for the detailed response @CaAlden!
Thanks @AlexanderWillner, looking forward to it!

@AlexanderWillner It looks like the tests are failing because the main.sqlite file committed in the tests directory has the old column names. Is there a script or something for regenerating that file? How was it originally made?

@AlexanderWillner It looks like the tests are failing because the main.sqlite file committed in the tests directory has the old column names. Is there a script or something for regenerating that file? How was it originally made?

It's the actual test Things DB (Things is using sqlite). What is needed is to open the main.sqlite file with the current version of Things and save it again. The commands make db-from-things and make db-to-things were supposed to do this semi-automatically, however, the location of the database now contains a random value as well.
All can be figured out, just needs a few minutes/hours...

@CaAlden Thank you for the heads-up and the fast pull request. Also, thank you for your patience these weeks until we got back to you.
An important note (also, /cc @AlexanderWillner): we might need to check what Things version the user has; it seems not all are automatically upgraded. I'm on Big Sur (which is still actively supported) and Things is at Version 3.15.16 and thingsapi works as expected. Some of us are not on Monterey or Ventura, since Apple doesn't offer OS upgrades for some MacBooks. See also: https://endoflife.date/macos

Follow-up and new idea. Instead of implementing backwards-compatible support, do this:

check the version number of the Things Database (things.Database().get_version()).
If it is below a certain number, then prompt the user to install an older version of thingsapi; e.g. pip install things.py==0.0.14.

That keeps the code clean.

I looks like the problem is solved. I hope that non-developers like myself can soon use Things CLI again by simply upgrading the pip package! 🤞 I'm on macOS 13.3.1 (a) (22E772610a)and Things 3.17.7 (31707500).

check the version number of the Things Database (things.Database().get_version()).
If it is below a certain number, then prompt the user to install an older version of thingsapi; e.g. pip install things.py==0.0.14.

Do we know in which DB version the changes happened? We could start with using version 21 as the breaking point...

check the version number of the Things Database (things.Database().get_version()).
If it is below a certain number, then prompt the user to install an older version of thingsapi; e.g. pip install things.py==0.0.14.

Do we know in which DB version the changes happened? We could start with using version 21 as the breaking point...

Sounds good to me. We can always fix it later if needed.

@AlexanderWillner Ping ;)

Partly solved, thanks for the contributions! Next step(s) to be discussed in #110

authors: AlexanderWillner CaAlden ishan1m mikez roelvangils


text:
Error while running docker-compose up

This is the error i get when i run while running docker-compose up
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-P5Plug/twilio/
ERROR: Service 'frontend' failed to build: The command '/bin/sh -c pip install --no-cache-dir twilio==4.4.0' returned a non-zero code: 1

Hi,
We have just pushed new docker images to sort out this issue (in fact, they were being pushed as you sent that comment).
If you pull the latest images and try again, the issue should be sorted.

Hey thanks man, it worked

There were some RED lines (errors?) appear while running docker-compose up

debconf: delaying package configuration, since apt-utils is not installed
You are using pip version 8.1.1, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

They may not be fatal errors but I think should be addressed in future update.

authors: Manu-Surendra phongiswindy thinkst

repo: thinkst/canarytokens-docker


text:
feat(auth): add passkey authentication support for web and native clients

Problem solved
Short description of the bug fixed or feature added

PR-Codex overview
The focus of this PR is to introduce webLocalStorage and nativeLocalStorage for storing data in different environments.
Detailed summary

Introduces webLocalStorage and nativeLocalStorage for storage in web and native environments
Updates LocalStorage constructor to accept a storage parameter
Adds PasskeyNativeClient for native passkey authentication
Updates authentication methods to use webLocalStorage and PasskeyWebClient

The following files were skipped due to too many changes: packages/thirdweb/src/wallets/in-app/core/authentication/passkeys.ts, packages/thirdweb/src/wallets/in-app/web/lib/auth/passkeys.ts, pnpm-lock.yaml

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

#4022  👈
main

This stack of pull requests is managed by Graphite. Learn more about stacking.

Join @joaquim-verges and the rest of your teammates on  Graphite

repo: thirdweb-dev/js


text:
[TOOL-2807] Nebula: Fix incorrect prepared transaction object, Fix sign_transaction action not shown properly on page reload

Problem solved
Short description of the bug fixed or feature added

PR-Codex overview
This PR focuses on enhancing the handling of chat messages and transaction data within the nebula-app. It introduces new types for transaction data and modifies the processing of messages to accommodate a new role and structure.
Detailed summary

Updated role in deleted_at history to include "action".
Changed SendTransactionOption to NebulaTxData for transaction data handling.
Improved message processing logic in ChatPageContent.tsx to handle "action" roles.
Modified Chats.tsx to accommodate new transaction data structure.
Adjusted transaction preparation in SendTransactionButton.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

[!WARNING]
This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

#5809  👈 (View in Graphite)
#5807
#5805
#5804
main

How to use the Graphite Merge Queue
Add either label to this PR to merge it via the merge queue:

merge-queue - adds this PR to the back of the merge queue
hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.


text:
Fix the tests.

This fixes the tests for now using a test http server.  I need to come back later and add some additional tests cases but I just wanted to get a skeleton in place.

Also adding a fix for the RBAC.


text:
增加类似 OpenAI API 的接口方式

是否可以增加类似 OpenAI API 的接口方式，可以参考：https://github.com/ninehills/chatglm-openai-api

早上刚看到，感谢建议！

已提供api接口，但具体设计需要再行斟酌，可以一起参与！

你好，请问接口api功能已经有了吗？如何使用？

参照此文档

authors: ccartermices online2311 thomas-yanxin

repo: thomas-yanxin/LangChain-ChatGLM-Webui


text:
Update akita repo url and add 'A Web Monetization Story'

What type of PR is this? (check all applicable)

[x] Adding new resource(s)

Description

Update the url for Akita in the Tools section of the readme
Add new tutorial resource 'A Web Monetization Story' in the readme

FYI @vezwork

So glad you like it 😄
Really cool that you're putting together all this Web Monetization info!

repo: thomasbnt/awesome-web-monetization


text:
PHP & MySQL Tutorial

The page is unavailable.
Can you help to fix it?
Thanks.

It's moved into the repository, the documentation.md file:
https://github.com/thomasjbradley/saving-signatures-sample/blob/master/documentation.md

repo: thomasjbradley/signature-pad