Example #1
0
async def click_character_select_button(check_open_state: Union[bool,
                                                                None] = None):
    await async_sleep(0.5)

    button_x, button_y = (
        CFG.character_select_button_position["x"],
        CFG.character_select_button_position["y"],
    )

    ACFG.moveMouseAbsolute(x=button_x, y=button_y)
    ACFG.left_click()

    if check_open_state is not None:
        log(f"Checking that character select is {'open' if check_open_state else 'closed'}"
            )
        await async_sleep(2)
        success = False
        for _ in range(CFG.character_select_max_close_attempts):
            if await check_character_menu(check_open_state):
                success = True
                break
            else:
                ACFG.moveMouseAbsolute(x=button_x, y=button_y)
                ACFG.left_click()

        if not success:
            log("Unable to toggle character menu!\nNotifying dev...")
            notify_admin(
                "Failed to find toggle character menu in `click_character_select_button` loop"
            )
            sleep(2)
        log("")
    else:
        await async_sleep(0.5)
Example #2
0
async def check_if_should_change_servers(
    original_current_server_id: str = "N/A", ) -> Tuple[bool, str]:
    current_server_id = ("" if original_current_server_id == "N/A" else
                         original_current_server_id)
    current_server_playing = 0
    highest_player_server_playing = 0

    log("Querying Roblox API for server list")
    url = f"https://games.roblox.com/v1/games/{CFG.game_id}/servers/Public"
    try:
        response = get(url, timeout=10)
    except Exception:
        return False, "[WARN] Could not poll Roblox servers. Is Roblox down?"
    if response.status_code == 200:
        log("Finding best server and comparing to current...")

        response_result = response.json()
        servers = response_result["data"]
        if current_server_id == "N/A":
            current_server_id = ""
        for server in servers:
            server_id = server.get("id", "undefined")
            if server.get("playerTokens") is None:
                server_playing = -1
            else:
                server_playing = len(server["playerTokens"])
            if server_id == "undefined" or server_playing == -1:
                notify_admin(
                    f"Handled Error in `check_if_should_change_servers`\nServers:\n`{servers}`\nProblem:\n`{server}`"
                )
                continue

            if current_server_id == server_id:
                current_server_id = server_id
                current_server_playing = server_playing
            elif ("playerTokens" in server
                  and server_playing > highest_player_server_playing):
                highest_player_server_playing = server_playing
        log("")
        if current_server_id == "" or current_server_id == "undefined":
            if highest_player_server_playing == 0:
                return False, "[WARN] Could not poll Roblox servers. Is Roblox down?"
            return_message = (
                f"[WARN] Could not find FumoCam. Are we in a server?\n"
                f"Original Server ID: {original_current_server_id}\n"
                f"Detected Server ID: {current_server_id}")
            return True, return_message
        elif (current_server_playing < CFG.player_switch_cap
              and (current_server_playing + CFG.player_difference_to_switch) <
              highest_player_server_playing):
            difference = highest_player_server_playing - current_server_playing
            return (
                True,
                f"[WARN] There is a server with {difference} more players online.",
            )
        else:
            return False, ""
    return False, "[WARN] Could not poll Roblox servers. Is Roblox down?"
Example #3
0
 async def dev(self, ctx: commands.Context):
     args = await self.get_args(ctx)
     if not args:
         await ctx.send(
             "[Specify a message, this command is for emergencies! (Please do not misuse it)]"
         )
         return
     msg = " ".join(args)
     notify_admin(f"{ctx.message.author.display_name}: {msg}")
     await ctx.send(
         "[Notified dev! As a reminder, this command is only for emergencies. If you were unaware of this and used"
         " the command by mistake, please write a message explaining that or you may be timed-out/banned.]"
     )
Example #4
0
 async def manual_dev_command(self, message: TwitchMessage):
     args = message.content.split(" ", 1)
     if len(args) < 2:
         await message.channel.send(
             "[Specify a message, this command is for emergencies! (Please do not misuse it)]"
         )
         return
     msg = args[-1]
     notify_admin(f"{message.author}: {msg}")
     await message.channel.send(
         "[Notified dev! As a reminder, you have been blacklisted by a trusted member, so your"
         " controls will not work. If you feel this is in error, use this commmand.]"
     )
Example #5
0
async def open_roblox_with_selenium_browser(js_code: str) -> bool:
    log("Opening Roblox via Browser...")
    try:
        with open(CFG.browser_cookies_path, "r", encoding="utf-8") as f:
            cookies = json.load(f)
    except FileNotFoundError:
        print("COOKIES PATH NOT FOUND, INITIALIZE WITH TEST FIRST")
        log("")
        return False

    options = webdriver.ChromeOptions()
    options.add_argument(f"--user-data-dir={CFG.browser_profile_path}")
    driver = webdriver.Chrome(options=options,
                              executable_path=str(CFG.browser_driver_path))
    driver.get(CFG.game_instances_url)

    for cookie in cookies:
        try:
            driver.add_cookie(cookie)
        except Exception:
            print(f"ERROR ADDING COOKIE: \n{cookie}\n")

    driver.refresh()
    driver.execute_script(js_code)

    sleep_time = 0.25
    success = False
    log("Verifying Roblox has opened...")
    for _ in range(int(CFG.max_seconds_browser_launch / sleep_time)):
        crashed = await do_crash_check(do_notify=False)
        active = is_process_running(CFG.game_executable_name)
        if not crashed and active:
            success = True
            break
        await async_sleep(sleep_time)
    try:
        driver.quit()
        kill_process(CFG.browser_driver_executable_name)
        kill_process(CFG.browser_executable_name)
    except Exception:
        print(format_exc())

    if not success:
        log("Failed to launch game. Notifying Dev...")
        notify_admin("Failed to launch game")
        await async_sleep(5)
        log("")
        return False
    log("")
    return True
Example #6
0
async def auto_nav(location: str,
                   do_checks: bool = True,
                   slow_spawn_detect: bool = True):
    log_process("AutoNav")
    if do_checks:
        await check_active(force_fullscreen=False)
        await async_sleep(0.5)
        await send_chat(
            f"[AutoNavigating to {CFG.nav_locations[location]['name']}!]")
        if not CFG.collisions_disabled:
            log("Disabling collisions")
            await toggle_collisions()
            await async_sleep(0.5)
        await async_sleep(1)
        log("Respawning")
        await respawn_character(notify_chat=False)
        await async_sleep(7)
    log("Zooming out to full scale")
    ACFG.zoom(zoom_direction_key="o", amount=105)

    spawn = spawn_detection_main(CFG.resources_path, slow=slow_spawn_detect)
    if spawn == "ERROR":
        log("Failed to detect spawn!\n Notifying Dev...")
        notify_admin("Failed to find spawn in `auto_nav`")
        sleep(5)
        return

    if spawn == "comedy_machine":
        comedy_to_main()
    elif spawn == "tree_house":
        treehouse_to_main()
    await async_sleep(1)
    if location == "shrimp":
        main_to_shrimp_tree()
    elif location == "ratcade":
        main_to_ratcade()
    elif location == "train":
        main_to_train()
    elif location == "classic":
        main_to_classic()
    elif location == "treehouse":
        main_to_treehouse()
    log("Zooming in to normal scale")
    default_zoom_in_amount = CFG.zoom_max - CFG.zoom_default
    zoom_in_amount = CFG.nav_post_zoom_in.get(location, default_zoom_in_amount)
    ACFG.zoom(zoom_direction_key="i", amount=zoom_in_amount)
    log(f"Complete! This is experimental, so please re-run \n'!nav {location}' if it didn't work."
        )
    await async_sleep(3)
Example #7
0
async def mute_toggle(set_mute: Union[bool, None] = None):
    log_process("In-game Mute")
    desired_mute_state = not CFG.audio_muted
    if set_mute is not None:  # If specified, force to state
        desired_mute_state = set_mute
    desired_volume = 0 if desired_mute_state else 100
    log_msg = "Muting" if desired_mute_state else "Un-muting"
    log(log_msg)
    sc_exe_path = str(CFG.resources_path / CFG.sound_control_executable_name)
    os.system(  # nosec
        f'{sc_exe_path} /SetVolume "{CFG.game_executable_name}" {desired_volume}'
    )

    # Kill the process no matter what, race condition for this is two songs playing (bad)
    kill_process(executable=CFG.vlc_executable_name, force=True)

    if desired_mute_state:  # Start playing music
        copyfile(
            CFG.resources_path / OBS.muted_icon_name,
            OBS.output_folder / OBS.muted_icon_name,
        )
        vlc_exe_path = str(CFG.vlc_path / CFG.vlc_executable_name)
        music_folder = str(CFG.resources_path / "soundtracks" / "overworld")
        Popen(
            f'"{vlc_exe_path}" --playlist-autostart --loop --playlist-tree {music_folder}'
        )
        output_log("muted_status", "In-game audio muted!\nRun !mute to unmute")
        sleep(5)  # Give it time to load VLC
    else:  # Stop playing music
        try:
            if os.path.exists(OBS.output_folder / OBS.muted_icon_name):
                os.remove(OBS.output_folder / OBS.muted_icon_name)
        except OSError:
            log("Error, could not remove icon!\nNotifying admin...")
            async_sleep(2)
            notify_admin("Mute icon could not be removed")
            log(log_msg)
        output_log("muted_status", "")
    CFG.audio_muted = desired_mute_state

    await check_active()
    log_process("")
    log("")
Example #8
0
    async def blacklist(self, ctx: commands.Context):
        if ctx.message.author.name.lower() not in CFG.vip_twitch_names:
            await ctx.send("[You do not have permission to run this command!]")
        args = await self.get_args(ctx)
        if not args:
            await ctx.send("[Please specify a user!]")
            return
        try:
            name = args[0].lower()
            if name[0] == "@":
                name = name[1:]
        except Exception:
            await ctx.send("[Please specify a user!]")
            return

        added = False
        if name not in CFG.twitch_blacklist:
            CFG.twitch_blacklist.append(name)
            with open(str(CFG.twitch_blacklist_path), "w") as f:
                json.dump(CFG.twitch_blacklist, f)
            added = True

        await ctx.send(
            f"['{name}' has {'already' if not added else ''} been blacklisted from interacting with FumoCam.]"
        )
        await async_sleep(1)
        await ctx.send("[It is recommended you also report them to Twitch, if needed.]")
        await async_sleep(1)
        await ctx.send(
            f"[@{name} if you feel this is in error, please type '!dev unjust ban' in chat."
            " The dev has already been notified]"
        )

        mod_url = f"<https://www.twitch.tv/popout/becomefumocam/viewercard/{ctx.message.author.name.lower()}>"
        target_url = (
            f"<https://www.twitch.tv/popout/becomefumocam/viewercard/{name.lower()}>"
        )

        notify_admin(
            f"{ctx.message.author.name} has blacklisted {name}\n{mod_url}\n{target_url}"
        )
Example #9
0
async def check_if_game_loaded() -> bool:
    game_loaded = False
    log("Loading into game")

    for attempt in range(CFG.max_attempts_game_loaded):
        if await check_ui_loaded():
            game_loaded = True
            break
        log(f"Loading into game (Check #{attempt}/{CFG.max_attempts_game_loaded})"
            )
        await async_sleep(1)

    if not game_loaded:
        log("Failed to load into game.")
        notify_admin("Failed to load into game")
        await async_sleep(5)
        await CFG.add_action_queue(ActionQueueItem("handle_crash"))
        log("")
        return False
    log("")
    return True
Example #10
0
    async def event_message(self, message: TwitchMessage):
        if message.echo:
            return
        msg_str = f"[Twitch] {message.author.display_name}: {message.content}"

        print(msg_str.encode("ascii", "ignore").decode("ascii", "ignore"))

        log_task = create_task(self.do_discord_log(message))

        if message.author.name not in CFG.twitch_blacklist:
            commands_task = create_task(self.handle_commands(message))
        else:
            if message.content.startswith("!dev"):
                await self.manual_dev_command(message)

        if await self.is_new_user(message.author.name):
            for msg in self.help_msgs:
                await message.channel.send(msg)

            await message.channel.send(
                f"Welcome to the stream {message.author.mention}! "
                "This is a 24/7 bot you can control with chat commands! See above."
            )

        try:
            await log_task
        except Exception:
            print(traceback.format_exc())
            notify_admin(f"```{traceback.format_exc()}```")

        if message.author.name not in CFG.twitch_blacklist:
            try:
                await commands_task
            except commands.errors.CommandNotFound:
                pass
            except Exception:
                print(traceback.format_exc())
                notify_admin(f"```{traceback.format_exc()}```")
Example #11
0
async def toggle_collisions() -> bool:
    log_process(
        f"{'Enabling' if CFG.collisions_disabled else 'Disabling'} Grief Collisions"
    )
    log("Opening Settings")
    await check_active(force_fullscreen=False)
    await async_sleep(1)

    need_zoom_adjust = False
    if CFG.zoom_level < CFG.zoom_ui_min_cv:
        ACFG.zoom("o", CFG.zoom_out_ui_cv)
        need_zoom_adjust = True

    if not await click_settings_button(check_open_state=True):
        notify_admin("Failed to open settings")
        log("")
        log_process("")
        if need_zoom_adjust:
            ACFG.zoom("i", CFG.zoom_out_ui_cv)
        return False

    log(f"Finding {CFG.settings_menu_grief_label} option")
    await async_sleep(1)
    if not await ocr_for_settings():
        notify_admin("Failed to click settings option")
        log("")
        log_process("")
        if need_zoom_adjust:
            ACFG.zoom("i", CFG.zoom_out_ui_cv)
        return False

    CFG.collisions_disabled = not (CFG.collisions_disabled)
    collisions_msg = ("" if CFG.collisions_disabled else
                      "[WARN] Griefing/Collisions enabled!")
    output_log("collisions", collisions_msg)

    log("Closing Settings")
    await async_sleep(0.25)
    if not await click_settings_button(check_open_state=False):
        notify_admin("Failed to close settings")
        log("")
        log_process("")
        if need_zoom_adjust:
            ACFG.zoom("i", CFG.zoom_out_ui_cv)
        return False
    log("")
    log_process("")
    ACFG.resetMouse()
    if need_zoom_adjust:
        ACFG.zoom("i", CFG.zoom_out_ui_cv)
    return True
Example #12
0
async def ocr_for_settings(option: str = "",
                           click_option: bool = True) -> bool:
    desired_option = (CFG.settings_menu_grief_text
                      if not option else option).lower()
    desired_label = (CFG.settings_menu_grief_label
                     if not option else option).lower()

    ocr_data = {}
    found_option = False
    for attempts in range(
            CFG.settings_menu_ocr_max_attempts):  # Attempt multiple OCRs
        if not click_option:
            log(f"Finding '{desired_label.capitalize()}' (Attempt #{attempts}/{CFG.settings_menu_ocr_max_attempts})"
                )
        screenshot = np.array(await
                              take_screenshot_binary(CFG.window_settings))

        menu_green = {
            "upper_bgra": np.array([212, 255, 158, 255]),
            "lower_bgra": np.array([163, 196, 133, 255]),
        }
        green_mask = cv.inRange(screenshot, menu_green["lower_bgra"],
                                menu_green["upper_bgra"])
        screenshot[green_mask > 0] = (255, 255, 255, 255)

        menu_red = {
            "upper_bgra": np.array([79, 79, 255, 255]),
            "lower_bgra": np.array([63, 63, 214, 255]),
        }
        red_mask = cv.inRange(screenshot, menu_red["lower_bgra"],
                              menu_red["upper_bgra"])
        screenshot[red_mask > 0] = (255, 255, 255, 255)

        gray = cv.cvtColor(screenshot, cv.COLOR_BGR2GRAY)  # PyTesseract
        _, thresh = cv.threshold(gray, 240, 255, cv.THRESH_BINARY)
        thresh_not = cv.bitwise_not(thresh)
        kernel = np.ones((2, 1), np.uint8)
        img = cv.erode(thresh_not, kernel, iterations=1)
        img = cv.dilate(img, kernel, iterations=1)

        ocr_data = pytesseract.image_to_data(
            img, config="--oem 1", output_type=pytesseract.Output.DICT)
        ocr_data["text"] = [word.lower() for word in ocr_data["text"]]
        print(ocr_data["text"])
        for entry in ocr_data["text"]:
            if desired_option in entry:
                desired_option = entry
                if click_option:
                    log("Found option, clicking")
                    found_option = True
                    break
                else:
                    return True
        if found_option:
            break
        await async_sleep(0.25)

    if not found_option:
        if click_option:
            log(f"Failed to find '{desired_label.capitalize()}'.\n Notifying Dev..."
                )
            notify_admin(f"Failed to find `{desired_option}`")
            await async_sleep(5)
        return False
    else:
        # We found the option
        print(ocr_data)
        ocr_index = ocr_data["text"].index(desired_option)

        option_top = ocr_data["top"][ocr_index]
        option_y_center = ocr_data["height"][ocr_index] / 2
        option_y_pos = option_top + option_y_center
        # The top-offset of our capture window + the found pos of the option
        desired_option_y = int(CFG.window_settings["top"] + option_y_pos)

        option_left = ocr_data["left"][ocr_index]
        option_x_center = ocr_data["width"][ocr_index] / 2
        option_x_pos = option_left + option_x_center
        # The left-offset of our capture window + the found pos of the option
        desired_option_x = int(CFG.window_settings["left"] + option_x_pos)

    # Click the option
    ACFG.moveMouseAbsolute(x=int(desired_option_x), y=int(desired_option_y))
    await async_sleep(0.5)
    ACFG.left_click()
    await async_sleep(0.5)
    return True
Example #13
0
async def ocr_for_character(character: str = "",
                            click_option: bool = True) -> bool:
    desired_character = (CFG.character_select_desired.lower()
                         if not character else character.lower())

    await async_sleep(0.5)

    ocr_data = {}
    last_ocr_text = None
    times_no_movement = 0
    found_character = False
    scroll_amount = 0
    for attempts in range(CFG.character_select_max_scroll_attempts):
        if click_option:
            log(f"Scanning list for '{desired_character.capitalize()}'"
                f" ({attempts}/{CFG.character_select_max_scroll_attempts})")
        for _ in range(
                CFG.character_select_scan_attempts):  # Attempt multiple OCRs
            screenshot = np.array(await
                                  take_screenshot_binary(CFG.window_character))

            gray = cv.cvtColor(screenshot, cv.COLOR_BGR2GRAY)
            gray, img_bin = cv.threshold(gray, 240, 255, cv.THRESH_BINARY)
            gray = cv.bitwise_not(img_bin)
            kernel = np.ones((2, 1), np.uint8)
            img = cv.erode(gray, kernel, iterations=1)
            img = cv.dilate(img, kernel, iterations=1)

            ocr_data = pytesseract.image_to_data(
                img, config="--oem 1", output_type=pytesseract.Output.DICT)
            ocr_data["text"] = [word.lower() for word in ocr_data["text"]]
            print(ocr_data["text"])
            for entry in ocr_data["text"]:
                if desired_character in entry:
                    if click_option:
                        found_character = True
                        break
                    else:
                        return True
            if found_character:
                break
            sleep(0.1)

        if click_option and found_character:
            break
        elif not click_option and not found_character:
            return False  # Do not scroll

        if last_ocr_text == ocr_data["text"]:
            times_no_movement += 1
        else:
            times_no_movement = 0
            last_ocr_text = ocr_data["text"]

        if times_no_movement > 3:
            break  # We reached the bottom of the list, OCR isnt changing

        ACFG.scrollMouse(1, down=True)
        scroll_amount += 1
        sleep(0.4)

    if not found_character:
        if click_option:
            log(f"Failed to find '{desired_character.capitalize()}'.\n Notifying Dev..."
                )
            notify_admin(f"Failed to find `{desired_character}`")
            sleep(5)
        return False

    # We found the character, lets click it
    ocr_index = ocr_data["text"].index(desired_character)
    desired_character_height = int(ocr_data["top"][ocr_index] +
                                   (ocr_data["height"][ocr_index] / 2))
    desired_character_height += CFG.window_character["top"]
    with open(OBS.output_folder / "character_select.json", "w") as f:
        json.dump(
            {
                "scroll_amount": scroll_amount,
                "desired_character_height": desired_character_height,
            },
            f,
        )

    CFG.character_select_screen_height_to_click = desired_character_height

    CFG.character_select_scroll_down_amount = scroll_amount
    ACFG.moveMouseAbsolute(x=CFG.screen_res["center_x"],
                           y=int(desired_character_height))
    sleep(0.5)
    ACFG.left_click()
    sleep(0.5)
    return True
Example #14
0
async def check_for_better_server():
    last_check_time = time()
    output_log("last_check_for_better_server", last_check_time)
    previous_status_text = read_output_log("change_server_status_text")
    output_log("change_server_status_text", "")

    log_process("Checking for better server")
    current_server_id = await get_current_server_id()
    if current_server_id == "ERROR":
        for i in range(CFG.max_attempts_better_server):
            log_process(
                f"Attempt {i+1}/{CFG.max_attempts_better_server} failed! Retrying better server check..."
            )
            await async_sleep(5)
            current_server_id = await get_current_server_id()
            if current_server_id != "ERROR":
                break
    if current_server_id == "ERROR":
        log_process(
            f"Failed to connect to Roblox API {CFG.max_attempts_better_server} times! Skipping..."
        )
        await async_sleep(5)
        log_process("")
        return True
    if current_server_id == "N/A":
        for id in list(CFG.game_ids_other.keys()):
            current_server_id == await get_current_server_id(id)
            if current_server_id != "N/A":
                break
        if current_server_id == "N/A":
            log_process("Could not find FumoCam in any servers")
            await CFG.add_action_queue(ActionQueueItem("handle_crash"))
            return False
        else:
            log_process("")
            log("")
            return True
    (
        should_change_servers,
        change_server_status_text,
    ) = await check_if_should_change_servers(current_server_id)

    log(change_server_status_text)
    output_log("change_server_status_text", change_server_status_text)

    if not should_change_servers:
        log("PASS! Current server has sufficient players")
        log("")
        log_process("")
        return True
    elif previous_status_text != change_server_status_text:
        if "Could not find FumoCam" in change_server_status_text:
            for attempt in range(CFG.max_attempts_better_server):
                log(f"Rechecking (attempt {attempt+1}/{CFG.max_attempts_better_server}"
                    )
                current_server_id = await get_current_server_id()
                while current_server_id == "":
                    log_process("Retrying get current server check")
                    await async_sleep(5)
                    current_server_id = await get_current_server_id()
                (
                    should_change_servers,
                    change_server_status_text,
                ) = await check_if_should_change_servers(current_server_id)
                if "Could not find FumoCam" not in change_server_status_text:
                    break
            if should_change_servers:
                notify_admin(change_server_status_text)
                await CFG.add_action_queue(
                    ActionQueueItem("handle_join_new_server"))
        else:
            await CFG.add_action_queue(
                ActionQueueItem("handle_join_new_server"))
    log("")
    log_process("")