diff --git a/.gitignore b/.gitignore index d1906ce..18d623f 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +output.json diff --git a/README.md b/README.md index 0b4ad70..5e6c427 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,16 @@ git clone https://github.com/viktorashi/bingobaker.com-autoplayer && cd bingobak python autobingo.py -h ``` ```string -usage: autobingo [-h] [-d {chrome,edge,firefox,safari,ie,default}] [-u URL] [-cnt COUNT] [-i INPUT_PATH] [-o OUTPUT_PATH] [-c CARDS_PATH] [-t TIMEOUT] - [-gm {normal,blackout,peen,3in6,loser}] [-s SIZE] [-r] [-hdls] [-strt START] [-fs FREE_SPACE] [-fsm FREE_SPACE_IN_MIDDLE] - [{editconfig,generate,checkbingos,mark,clear,markmid}] +usage: autobingo [-h] [-d {chrome,edge,firefox,safari,ie,default}] [-u URL] [-cnt COUNT] + [-i INPUT_PATH] [-o OUTPUT_PATH] [-c CARDS_PATH] + [-gm {normal,blackout,peen,3in6,loser}] [-s SIZE] [-r] [-hdls] [-strt START] + [-fs FREE_SPACE] [-fsm FREE_SPACE_IN_MIDDLE] + [{editconfig,generate,check}] Auto Bingo playing command line tool. Currently only being used for bingobaker.com positional arguments: - {editconfig,generate,checkbingos,mark,clear,markmid} + {editconfig,generate,check} The mode to run the program in. [default: editconfig] options: @@ -44,26 +46,30 @@ options: -cnt COUNT, --count COUNT Number of bingo cards to generate from the generator link -i INPUT_PATH, --input INPUT_PATH - The file containing the keywords to search for on the bingo cards [default: input.txt] + The file containing the keywords to search for on the bingo cards + [default: input.txt] -o OUTPUT_PATH, --output OUTPUT_PATH - File to write the bingo'ed cards to [default: output.txt] + File to write the bingo'ed cards to [default: output.json] -c CARDS_PATH, --cards CARDS_PATH The path you want the cards to be saved in - -t TIMEOUT, --timeout TIMEOUT - Timeout in seconds for the webdriver to wait before clicking on each element to prevent malfunction [default : 0.2 ] -gm {normal,blackout,peen,3in6,loser}, --gamemode {normal,blackout,peen,3in6,loser} The gamemode to play in. [default: normal] -s SIZE, --size SIZE The size of the bingo card [default: 5 ] - -r, --reverse Reverse the bingo card order when reading from [cards.txt] [default False] - -hdls, --headless Run the webdriver in headless mode (no interface for less VRAM consumption i think idk lol) [only possible for chrome, edge, firefox] [default False] + -r, --reverse Reverse the bingo card order when reading from [cards.txt] [default + False] + -hdls, --headless Run the webdriver in headless mode (no interface for less VRAM + consumption i think idk lol) [only possible for chrome, edge, firefox] + [default False] -strt START, --start START The index of the card to start doing anything from -fs FREE_SPACE, --free-space FREE_SPACE Name of the freespace to search for in the card [default: 'no credit'] -fsm FREE_SPACE_IN_MIDDLE, --free-space-middle FREE_SPACE_IN_MIDDLE - Whether to search for the freespace in the middle of the card [default: 1] + Whether to search for the freespace in the middle of the card + [default: 1] ion care how u use my code lol + ``` for the operation and options @@ -72,10 +78,7 @@ for the operation and options ### <font size=5> Positional arguments</font> reffer to the first thing you type after ```python autobingo.py```, meaning the function you want to execute, generating, checking the bingos, marking the spots (which includes checking the bingo's if any spots containing the keywords have been found), and clearing the cards in case jack got a bigo already :'( - generate : generates {--count} bingo cards from the specified {--url}, writes their links to {--cards [default cards.txt]} (middle free space is always checked) -- mark : starts checking each card from {--cards} to see if they contain any keywords from {--input}, if they do, they check for a bingo, which, if found will output the link of those cards tp {--output} -- clear : clears the cards in {--cards} -- checkbingos :checks bingos for each card, will be less used since it automatically checks the bingo eitherway for each card as it searches -- markmid : mark all the middle free space spots for some reason i barely remember why i made this +- check :checks bingos for each card, will be less used since it automatically checks the bingo eitherway for each card as it searches - editconfig : is the default behaviour if nothing specified, it does nothing but update the ***bingoconfig.json*** ### <font size=5> --input <sub> [default: input.txt , shorthand -i]</sub> </font> is the file in which you have the keywords you want to search for on the bingo cards, each keyword on a new line. They DON'T have to be specified exactly as in the cards, lowercase values will be compared and they can just contain those strings @@ -85,8 +88,8 @@ freebooting a freebooted fake laugh bro ``` -### <font size=5> --output <sub>[default: output.txt, shorthand -o]</sub></font> : file that outputs the bingos -#### **`output.txt`** +### <font size=5> --output <sub>[default: output.json, shorthand -o]</sub></font> : file that outputs the bingos +#### **`output.json`** ``` https://bingobaker.com/#64e7dce5f63bda5a https://bingobaker.com/#64e7dce69d2d0c80 @@ -152,7 +155,7 @@ python autobingo.py mark --reverse <h3><span style="color:#F47174"> 3. It will all be saved </span></h3> -### Every parameter value you provide to the command will be saved in a file called ***bingoconfig.json*** right next to the program. <h3><span style="color:red"> DO NOT CHANGE THE CONFIG FILE DIRECTLY</span></h3> +### Every parameter value you provide to the command will be saved in a file called ***bingoconfig.json*** right next to the program. Plus it will automatically check to see if the size provided is accurate to the card's size, if the free space is in the middle or not and update that in the config file <h3><span style="color:red"> DO NOT CHANGE THE CONFIG FILE DIRECTLY</span></h3> idk how well i've tested this but i believe it starts messing up if you do. But delete it all-togather if you have and it doesn't work anymore, it will just generate another one. @@ -174,9 +177,9 @@ should do the job, then you would create an **input.txt** right next to this and ##### **`zsh`** ```bash -python autobingo.py mark +python autobingo.py check ``` -ez, peek into the console once in a while and see if it outputed anything about a congratilations, then go to output.txt to check it. +ez, peek into the console once in a while and see if it outputed anything about a congratilations, then go to output.json to check it. #### This opens up Microsoft Edge browser and and generates 75 bingo cards with the generator link you have provided to it, the bingo's of which will be saved to the ***wins.txt*** file, also increases the timeout. ##### **`zsh`** diff --git a/autobingo.py b/autobingo.py index 29af07a..8fd4f88 100644 --- a/autobingo.py +++ b/autobingo.py @@ -8,7 +8,7 @@ e pe moduri: - editconfig [defaultu daca nu e specificat] - generate - - checkbingos + - check - help - mark - clear @@ -22,7 +22,7 @@ parser.add_argument( "mode", help="The mode to run the program in. [default: editconfig]", - choices=["editconfig", "generate", "checkbingos", "mark", "clear", "markmid"], + choices=["editconfig", "generate", "check"], nargs="?", ) @@ -69,19 +69,13 @@ dest="cards_path", type=str, ) -parser.add_argument( - "-t", - "--timeout", - help="Timeout in seconds for the webdriver to wait before clicking on each element to prevent malfunction [default : 0.2 ]", - type=float, -) parser.add_argument( "-gm", "--gamemode", help="The gamemode to play in. [default: normal] ", type=str, choices=["normal", "blackout", "peen", "3in6", "loser"], - dest="type", + dest="gamemode", ) parser.add_argument( "-s", @@ -159,21 +153,11 @@ options["count"] = 10 case _: pass - # if arg in bingoconfig.json: set it in options - # else: check if: - # arg == "mode": set it to "editconfig" - # arg == "driver": set it to "chrome" - # arg == "count" : set it to 10 - # else pass else: # because this is a default that we don't want to set in bingoconfig.json as it depends on the user's call of the program if not arg == "mode": options[arg] = args[arg] -if "gamemode" in options: - if options["gamemode"] == "3in6": - options["size"] = 6 - update_config(options) if args["mode"] == "editconfig": @@ -188,36 +172,6 @@ print(f"{option} : {options[option]}") -match options["driver"]: - case "chrome": - if options["headless"]: - opts = webdriver.ChromeOptions() - opts.add_argument("--headless=new") - options["driver"] = webdriver.Chrome(options=opts) - else: - options["driver"] = webdriver.Chrome() - case "edge": - if options["headless"]: - opts = webdriver.EdgeOptions() - opts.add_argument("--headless=new") - options["driver"] = webdriver.Edge(options=opts) - else: - options["driver"] = webdriver.Edge() - case "firefox": - if options["headless"]: - opts = webdriver.FirefoxOptions() - opts.add_argument("--headless=new") - options["driver"] = webdriver.Firefox(options=opts) - else: - options["driver"] = webdriver.Firefox() - case "safari": - options["driver"] = webdriver.Safari() - case "ie": - options["driver"] = webdriver.Ie() - case _: - options["driver"] = webdriver.Chrome() -from main import autobingo - not_for_class = ["count", "headless"] # preparing it for the autobingo class input_options = {} @@ -227,17 +181,45 @@ if not (option in not_for_class): input_options[option] = options[option] -bingo = autobingo(**input_options) +from main import autobingo + match args["mode"]: case "generate": + match options["driver"]: + case "chrome": + if options["headless"]: + opts = webdriver.ChromeOptions() + opts.add_argument("--headless=new") + input_options["driver"] = webdriver.Chrome(options=opts) + else: + input_options["driver"] = webdriver.Chrome() + case "edge": + if options["headless"]: + opts = webdriver.EdgeOptions() + opts.add_argument("--headless=new") + input_options["driver"] = webdriver.Edge(options=opts) + else: + input_options["driver"] = webdriver.Edge() + case "firefox": + if options["headless"]: + opts = webdriver.FirefoxOptions() + opts.add_argument("--headless=new") + input_options["driver"] = webdriver.Firefox(options=opts) + else: + input_options["driver"] = webdriver.Firefox() + case "safari": + input_options["driver"] = webdriver.Safari() + case "ie": + input_options["driver"] = webdriver.Ie() + case _: + input_options["driver"] = webdriver.Chrome() + + # kinda like object destructuring in js + bingo = autobingo(**input_options) bingo.createCards(options["count"]) print("Cards generated! Check the cards.txt file for the links") - case "checkbingos": + + case "check": + bingo = autobingo(**input_options) bingo.check_bingo_of_all_cards() - case "mark": - bingo.mark_spots() - case "clear": - bingo.clear_all_cards() - case "markmid": - bingo.mark_all_middle_spots() diff --git a/bruh.mp3 b/bruh.wav similarity index 100% rename from bruh.mp3 rename to bruh.wav diff --git a/main.py b/main.py index 03e819e..2081948 100644 --- a/main.py +++ b/main.py @@ -2,29 +2,26 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -from time import sleep from utils import ( waitElement, - writeTheCards, - clear_card, - find_words_click_and_return_num_of_found, - check_bingo_and_write_to_output, - click_middle, + note_card, + check_bingos_and_write_to_output, + get_card_details, + update_config_one_attr, ) -# do enums +# do enums idk class autobingo: def __init__( self, - driver: webdriver, + driver: webdriver = "", url: str = "", cards_path="cards.txt", input_path="input.txt", - output_path="output.txt", - timeout: int = 0.2, - type: str = "normal", + output_path="output.json", + gamemode: str = "normal", size: int = 5, reverse: bool = False, start: int = 0, @@ -41,35 +38,30 @@ def __init__( """ # turn input phrases file path into list of strings - phrases: [str] - with open(input_path) as f: - phrases = f.read().splitlines() - - cards: [str] - with open(cards_path, "r+") as f: - cards = f.read().splitlines() - - input_phrases: list[str] + input_phrases: [str] with open(input_path) as f: input_phrases = f.read().splitlines() + self.input_phrases = input_phrases self.free_space_in_middle = free_space_in_middle self.free_space = free_space self.reverse = reverse - self.type = type - self.input_phrases = input_phrases - if self.reverse: - cards.reverse() - if start > 0: - cards = cards[start:] - self.cards: [str] = cards - self.input_path: [str] = phrases + self.gamemode = gamemode self.driver = driver self.url = url + self.input_path = input_path self.output_path = output_path self.cards_path = cards_path - self.timeout = timeout - self.size = size + + if self.reverse: + cards.reverse() + if start > 0: + cards = cards[start:] + + if gamemode == "3in6": + self.size = 6 + else: + self.size = size def createCards(self, num: int) -> None: """ @@ -89,93 +81,21 @@ def createCards(self, num: int) -> None: (By.CLASS_NAME, "bingo-card-svg g g") ) ) - sleep(self.timeout) - click_middle(elems, self.size, self) - writeTheCards(self, str(self.driver.current_url)) - except TimeoutError: - print(TimeoutError) - - def mark(self, input_phrases): - for indx, cardURL in enumerate(self.cards): - self.driver.get(cardURL) - found = find_words_click_and_return_num_of_found(self, input_phrases) - if found and check_bingo_and_write_to_output(self): - print( - "CONGRATS YO YOU GOT A BINGOO!!!, check the output file for the link" - ) - print(f"Checked card {indx}") - - """ - Wrappers: - """ + from math import sqrt - def mark_spots_list(self, input_phrases: [str]) -> None: - """' - input_phrases : list of strings that will be searched for in the bingo card - """ - try: - # obtains the cards links - # for each card, find word from marking and check if, and if found check to see if that card has a bingo, if yes report it to the logs along wit that card's link and print, continue on with the next card and go on - self.mark(input_phrases) - except TimeoutError: - print(TimeoutError) - - def mark_spots_str(self, input_phrases: str) -> None: - """' - input_phrases : file containing the list of strings that will be searched for in the bingo card - """ - try: - # obtains the cards links - - # for each card, find word from marking and check if, and if found check to see if that card has a bingo, if yes report it to the logs along wit that card's link and print, continue on with the next card and go on - # get the input phrases as list from the file - - with open(input_phrases) as f: - input_phrases = f.read().splitlines() - print(input_phrases) - self.mark(input_phrases) - except TimeoutError: - print(TimeoutError) + # automatically set the size of the card + self.size = int(sqrt(len(elems))) + update_config_one_attr("size", self.size) - def mark_spots(self) -> None: - """' - default for no file or list of words, uses the default input.txt file specified in the constructor - """ - try: - # for each card, find word from marking and check if, and if found check to see if that card has a bingo, if yes report it to the logs along wit that card's link and print, continue on with the next card and go on - # get the input phrases as list from the file - self.mark(self.input_phrases) - except TimeoutError: - print(TimeoutError) - - def clear_all_cards(self) -> None: - """ - clears all the cards from the cards.txt file - """ - for cardURL in self.cards: - self.driver.get(cardURL) - clear_card(self) + # this will be done in the mark function, and also the mark middle function doesn't really have a purpose anymore + # click_middle(elems, self.size, self) + card_details = get_card_details(self, elems) + note_card(self, card_details) + except TimeoutError: + print(TimeoutError) def check_bingo_of_all_cards(self) -> None: """ checks bingo for all cards in the cards.txt file """ - for cardURL in self.cards: - self.driver.get(cardURL) - check_bingo_and_write_to_output(self) - - def mark_all_middle_spots(self) -> None: - for cardURL in self.cards: - try: - # go to the website - self.driver.get(cardURL) - # check the free space in the middle (13th element) - elems = WebDriverWait(self.driver, 50).until( - EC.visibility_of_all_elements_located( - (By.CLASS_NAME, "bingo-card-svg g g") - ) - ) - click_middle(elems, self.size, self) - writeTheCards(self, str(self.driver.current_url)) - except TimeoutError: - print(TimeoutError) + check_bingos_and_write_to_output(self) diff --git a/utils.py b/utils.py index 033f7b4..15d0c21 100644 --- a/utils.py +++ b/utils.py @@ -2,9 +2,6 @@ from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By -import json -from typing import Callable -from time import sleep def waitElement(self, xpath: str) -> WebElement: @@ -13,103 +10,54 @@ def waitElement(self, xpath: str) -> WebElement: ) -def waitElementSelector(self, selector: str) -> WebElement: - return WebDriverWait(self.driver, 10).until( - EC.presence_of_element_located((By.CSS_SELECTOR, selector)) - ) - - -def writeTheCards(self, link: str) -> None: - """ - writes the link to the cards.txt file - """ - self.cards.append(link) - with open(self.cards_path, "a") as f: - f.write(link + "\n") - - -def clear_card(self) -> None: - """ - clears the current card - must be used at the viewport already on that specific card, so function must be used right after navigating to current page - """ - # this checks for free space - cnt = 0 - waitElement(self, "/html/body/div[3]/a").click() - - waitElement(self, "/html/body/div[3]/div/div[6]/a").click() - - # switch to alert and accept - self.driver.switch_to.alert.accept() - # switch back to default content - - self.driver.switch_to.default_content() +def get_text_squares(self, elems) -> [[str]]: + squares: [[str]] = [] + row: [str] = [] + cnt: int = 0 + for elem in elems: + cnt += 1 + texts = elem.find_elements(By.TAG_NAME, "tspan") + phrase = " ".join([text.text.replace("\n", " ") for text in texts]) + row.append(phrase) + if cnt == self.size: + squares.append(row) + cnt = 0 + row = [] + return squares - # wait for the page to load - # click on the free space - waitElementSelector( - self, "#svg > svg > g:nth-child(20) > g > text > tspan:nth-child(1)" - ).click() +def get_card_details(self, elems) -> dict: """ - mai sunt cateva clickuri + returns a dictionary of the url of the card and a 2d array of the phrases squares of the card """ - print(f"cleared {str( self.driver.current_url)}") - + return {"url": self.driver.current_url, "squares": get_text_squares(self, elems)} -def find_words_click_and_return_num_of_found(self, input_phrases: list[str]) -> int: - """ - this must be run on the viewport that is already on that specific card, so function must be used right after navigating to current page - input_phrases : list[str] - """ - cnt = 0 - elems = WebDriverWait(self.driver, 10).until( - EC.visibility_of_all_elements_located((By.CLASS_NAME, "bingo-card-svg g g")) - ) - for elem in elems: - # check if elem already clicked - img = elem.find_element(By.TAG_NAME, "image") - if img.get_dom_attribute("visibility") == "hidden": - texts = elem.find_elements(By.TAG_NAME, "tspan") - # transform list of strings by replacing \n with space and joining them - texts = " ".join([text.text.replace("\n", " ") for text in texts]) - for phrase in input_phrases: - if phrase.lower() in texts.lower(): - sleep(self.timeout) - elem.click() - cnt += 1 - return cnt - - -def get_squares(self) -> [[bool]]: - # must be used at the viewport already on that specific card, so function must be used right after finding new word on current page +def get_squares_completion(self, card: dict) -> [[bool]]: """ returns the squares of the card that are checked in a 2d bool array - """ - squares: [[bool]] = [] - elems = WebDriverWait(self.driver, 10).until( - EC.presence_of_all_elements_located((By.CSS_SELECTOR, "image")) - ) - cnt = 0 - row: [bool] = [] - for elem in elems: - cnt += 1 - # XAPTHU SFANTTT - # xpath = f"/html/body/div[2]/*[local-name() = 'svg']/*[local-name()='g'][{i*5 + j + 1}]/*[local-name()='g']/*[local-name()='image']" - visibilty = elem.get_dom_attribute("visibility") - - # daca e gol gen '' sau daca apare visibilty visib;e atunci e shown si doar daca apare hidden e hidden bruh - - if visibilty == "visible" or visibilty == "": - row.append(1) - else: - row.append(0) - if cnt == self.size: - squares.append(row) - cnt = 0 - row = [] - return squares + adds the completion 2d matrix value to the card for it to be printed to output + """ + squares_completion: [[bool]] = [[0 for _ in range(5)] for _ in range(5)] + # have to search for the free space, it won't neccesarilly be in the middle + if self.size % 2 == 0 or (not self.free_space_in_middle): + self.input_phrases.append(self.free_space) + else: + # or, in if it is odd or in the middle, just check that middle + from math import ceil + + mid = ceil(self.size / 2) + # i forgor its indexed from 0 💀 + squares_completion[mid][mid] = 1 + for i in range(self.size): + for j in range(self.size): + for input_phrase in self.input_phrases: + if input_phrase.lower() in card["squares"][i][j].lower(): + squares_completion[i][j] = 1 + break + + card["completion"] = squares_completion + return squares_completion def check_bingo_row_collumn_diagonal(size, squares) -> bool: @@ -186,12 +134,13 @@ def check_loser(size, squares) -> bool: return False -def check_bingo_and_write_to_output(self) -> bool: +def check_bingos_and_write_to_output(self) -> None: # this assumes the viewport is already on that specific card, so function must be used right after finding new word on current page + from typing import Callable - check_bigo: Callable[[any], bool] + check_bingo: Callable[[any], bool] - match self.type: + match self.gamemode: case "normal": check_bingo = check_bingo_row_collumn_diagonal case "blackout": @@ -203,20 +152,60 @@ def check_bingo_and_write_to_output(self) -> bool: case "loser": check_bingo = check_loser - if check_bingo(self.size, get_squares(self)): - curr_url = str(self.driver.current_url) - write_to_output(self, curr_url) - # currently only works for macos but imma try to change it si maybe i also contribute to playsound library on github with python 10+ support - # import os + cards: [dict] = read_cards_file(self) + + # if the first one doesn't have it in the middle, change the settings to not look for it in the middle + from math import ceil + + mid = ceil(self.size / 2) + if not (self.free_space.lower() in cards[0]["squares"][mid][mid].lower()): + print("WARNING: free space not found in middle of card, updating config") + update_config_one_attr("free_space_in_middle", 0) + else: + print("free space found in middle of card, updating config") + update_config_one_attr("free_space_in_middle", 1) + + winning_cards: [dict] = [] + + for card in cards: + # the following will add new attribute to card dict + if check_bingo(self.size, get_squares_completion(self, card)): + # for better conciseness and readability + del card["squares"] + winning_cards.append(card) + print("CONGRATS YOOO YOU GOT A BINGOO, check the output file for details") + # currently only plays sound and works for macos but imma try to change it si maybe i also contribute to playsound library on github with python 10+ support + # playsound() + + if len(winning_cards) > 0: + print(winning_cards) + final_wins = winning_cards + previous_wins = read_from_output(self) + if len(previous_wins) > 0: + previous_urls = [card["url"] for card in previous_wins] + new_wins = [ + card for card in winning_cards if card["url"] not in previous_urls + ] + new_wins.extend(previous_wins) + final_wins = new_wins + write_to_output(self, final_wins) - # os.system("afplay bruh.mp3") - return True - return False + +import json + + +def note_card(self, card: dict) -> None: + """ + writes the link to the cards.txt file + """ + with open(self.cards_path, "a+") as f: + f.write(json.dumps(card)) + f.write("\n") -def write_to_output(self, cardURL: str) -> None: - with open(self.output_path, "a+") as f: - f.write(cardURL + "\n") +def write_to_output(self, cards: [dict]) -> None: + with open(self.output_path, "w+") as f: + f.write(json.dumps(cards)) def update_config(options: dict): @@ -224,28 +213,35 @@ def update_config(options: dict): f.write(json.dumps(options)) +def read_cards_file(self) -> [dict]: + with open(self.cards_path, "r") as f: + return [json.loads(line) for line in f.readlines()] + + def read_from_config() -> dict: with open("bingoconfig.json", "r") as f: return json.loads(f.read()) -def click_middle(elems, size, self) -> None: - # size must be odd for freespace to be in the center, else check where it is - if size % 2 == 0 or not self.free_space_in_middle: - find_words_click_and_return_num_of_found(self, [self.free_space]) - return +def update_config_one_attr(attr: str, value: any) -> None: + options = read_from_config() + options[attr] = value + update_config(options) - from math import floor, ceil - # check if elem already clicked - mid = (floor(size / 2) * size) + ceil(size / 2) - # i forgor its indexed from 0 💀 - elem = WebDriverWait(self.driver, 10).until( - EC.element_to_be_clickable(elems[mid - 1]) - ) - # elem = elems[mid - 1] - img = elem.find_element(By.TAG_NAME, "image") - # if it hasn't been clicked yet - if img.get_dom_attribute("visibility") == "hidden": - elem.click() - # check_bingo_and_write_to_output(self) +def read_from_output(self) -> [dict]: + try: + with open(self.output_path, "r") as f: + return json.loads(f.read()) + except FileNotFoundError: + return [] + + +# def playsound(): +# import sounddevice +# import soundfile + +# filename = "bruh.wav" +# data, fs = soundfile.read(filename, dtype="float32") +# sounddevice.play(data, fs) +# status = sounddevice.wait()