From 27a98d9b939f98c90795b645727c128a98bba78c Mon Sep 17 00:00:00 2001 From: abhijitgorde5-crypto Date: Sat, 29 Aug 2026 23:41:57 +0530 Subject: [PATCH 1/4] Fix StaleElementReferenceException, add Ollama offline fallback, and PIL visual search generator --- src/constants.py | 6 +- src/element_selectors.py | 23 +++++-- src/llm_utils.py | 63 +++++++++++++------ src/random_image_for_visual_search.py | 87 ++++++++++++--------------- src/rewards_tasks.py | 82 +++++++++++++++++++------ src/tab_utils.py | 40 ++++++++---- 6 files changed, 196 insertions(+), 105 deletions(-) diff --git a/src/constants.py b/src/constants.py index d675096..2bf35c7 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,4 +1,8 @@ +import os from os.path import abspath USER_DATA_DIR = abspath("./data-dir") -PROFILE_NAME = "Default" \ No newline at end of file + +PROFILE_NAME = "Default" + + diff --git a/src/element_selectors.py b/src/element_selectors.py index 0dc565c..3fbead4 100644 --- a/src/element_selectors.py +++ b/src/element_selectors.py @@ -122,11 +122,20 @@ class ElementSelectionUtils: return self.driver.find_element(By.CSS_SELECTOR, '[id$="-tab-/dashboard"]') def get_sidebar_section(self): - for section in self.driver.find_elements(By.TAG_NAME, "section"): + sections = self.driver.find_elements(By.TAG_NAME, "section") + for section in sections: try: - # get_dom_attribute returns None for sections without an id, - # so normalise before comparing. - if (section.get_dom_attribute("id") or "").startswith("react-aria"): + sec_id = section.get_dom_attribute("id") or "" + if sec_id.startswith("react-aria") and section.is_displayed(): + if section.find_elements(By.TAG_NAME, "a") or section.find_elements(By.TAG_NAME, "button"): + return section + except StaleElementReferenceException: + continue + + for section in sections: + try: + sec_id = section.get_dom_attribute("id") or "" + if sec_id.startswith("react-aria"): return section except StaleElementReferenceException: continue @@ -160,6 +169,12 @@ class ElementSelectionUtils: # The first link in the opened panel is the progress row, not an activity. return self.get_sidebar_section().find_elements(By.TAG_NAME, "a")[1:] + def get_daily_set_element_by_index(self, index: int): + elements = self.get_daily_set_elements() + if index < len(elements): + return elements[index] + raise NoSuchElementException(f"daily set element at index {index} not found") + # ------------------------------------------------------------------ # explore on bing (absent in en-US, present in some other markets) # ------------------------------------------------------------------ diff --git a/src/llm_utils.py b/src/llm_utils.py index 6dfa9f2..d6c8bc0 100644 --- a/src/llm_utils.py +++ b/src/llm_utils.py @@ -1,3 +1,4 @@ +import re from typing import Generator import random import ollama @@ -38,13 +39,19 @@ _CLIENT = ollama.Client(timeout=180) MAX_EMPTY_RETRIES = 5 -def get_ollama_response(messages: list[dict[str, str]], model: str="gemma4:cloud") -> str: - response = _CLIENT.chat( - model=model, - messages=messages - ) +class OllamaOfflineException(Exception): + pass - return response.message.content + +def get_ollama_response(messages: list[dict[str, str]], model: str="gemma4:cloud") -> str: + try: + response = _CLIENT.chat( + model=model, + messages=messages + ) + return response.message.content + except Exception as exc: + raise OllamaOfflineException(f"Ollama service error: {exc}") from exc def get_nonempty_ollama_response(messages: list[dict[str, str]]) -> str: @@ -59,6 +66,7 @@ def get_nonempty_ollama_response(messages: list[dict[str, str]]) -> str: raise RuntimeError(f"LLM returned nothing usable after {MAX_EMPTY_RETRIES} attempts") + def get_search_query_from_task_description(task_description: str) -> str: # compat if "lyrics of your favorite song" in task_description.lower(): return "sweet caroline lyrics" @@ -74,9 +82,15 @@ def get_search_query_from_task_description(task_description: str) -> str: } ] - response = get_nonempty_ollama_response(messages) + try: + response = get_nonempty_ollama_response(messages) + return response.lower() + except Exception as exc: + print(f"[WARNING] Ollama is offline or unavailable ({exc}). Using fallback search query generator.") + words = [w for w in re.sub(r"[^\w\s]", "", task_description).split() if len(w) > 3 and w.lower() not in {"search", "bing", "find", "about", "with", "from", "that", "this"}] + fallback_query = " ".join(words[:4]) if words else f"{get_random_noun()} search" + return fallback_query.lower() - return response.lower() def get_related_search_queries(seed_word: str, num_queries: int=20) -> Generator[str, None, None]: messages = [ @@ -90,25 +104,38 @@ def get_related_search_queries(seed_word: str, num_queries: int=20) -> Generator } ] + use_fallback = False + for _ in range(num_queries): - response = get_nonempty_ollama_response(messages) + if not use_fallback: + try: + response = get_nonempty_ollama_response(messages) + yield response.lower() - yield response.lower() + messages.append({ + "role": "assistant", + "content": response + }) - messages.append({ - "role": "assistant", - "content": response - }) + messages.append({ + "role": "user", + "content": USER_PROMPT_FOR_SEARCH_QUERY_CONTINUATION + }) + continue + except Exception as exc: + print(f"[WARNING] Ollama is offline or unavailable ({exc}). Using built-in generator for remaining queries.") + use_fallback = True + + noun1 = get_random_noun() + noun2 = get_random_noun() + yield f"{seed_word} {noun1} {noun2}".lower() - messages.append({ - "role": "user", - "content": USER_PROMPT_FOR_SEARCH_QUERY_CONTINUATION - }) NOUNS = [ noun.strip().lower() for noun in open("nouns.txt", "r").read().splitlines() if len(noun.strip()) >= 3 ] + def get_random_noun() -> str: return random.choice(NOUNS) \ No newline at end of file diff --git a/src/random_image_for_visual_search.py b/src/random_image_for_visual_search.py index 0cf22d2..dc7711a 100644 --- a/src/random_image_for_visual_search.py +++ b/src/random_image_for_visual_search.py @@ -117,7 +117,7 @@ def download_image(url): allow_redirects=True, ) - except requests.RequestException as e: + except (requests.RequestException, ConnectionResetError, OSError) as e: print(f"Download failed: {e}") return None @@ -194,6 +194,31 @@ def convert_to_jpeg(image_data): return None +def generate_fallback_image(): + """Generate a synthetic local JPEG image using PIL as a fallback.""" + print("[INFO] Generating synthetic local fallback image for visual search...") + from PIL import ImageDraw + import random + + img = Image.new("RGB", (800, 600), color=(random.randint(50, 200), random.randint(50, 200), random.randint(50, 200))) + draw = ImageDraw.Draw(img) + for _ in range(10): + x0 = random.randint(0, 700) + y0 = random.randint(0, 500) + x1 = x0 + random.randint(50, 200) + y1 = y0 + random.randint(50, 200) + fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + draw.rectangle([x0, y0, x1, y1], fill=fill) + + output = io.BytesIO() + img.save(output, format="JPEG", quality=90) + jpeg_data = output.getvalue() + + OUTPUT_FILE.write_bytes(jpeg_data) + print(f"Saved fallback image to {OUTPUT_FILE.absolute()}") + return {"title": "Fallback Synthetic Image", "width": 800, "height": 600} + + # ============================================================ # RANDOM IMAGE # ============================================================ @@ -213,10 +238,6 @@ def get_random_image(): f"{attempt}/{MAX_ATTEMPTS}" ) - # ---------------------------------------------------- - # RANDOM FILE - # ---------------------------------------------------- - params = { "action": "query", "format": "json", @@ -241,14 +262,10 @@ def get_random_image(): timeout=20, ) - except requests.RequestException as e: + except (requests.RequestException, ConnectionResetError, OSError) as e: print(f"API request failed: {e}") continue - # ---------------------------------------------------- - # API RATE LIMIT - # ---------------------------------------------------- - if response.status_code == 429: wait_after_429( response, @@ -267,10 +284,6 @@ def get_random_image(): print(f"API error: {e}") continue - # ---------------------------------------------------- - # GET PAGE - # ---------------------------------------------------- - pages = ( data .get("query", {}) @@ -330,10 +343,6 @@ def get_random_image(): "url" ) - # ---------------------------------------------------- - # FILTER - # ---------------------------------------------------- - if mime not in { "image/jpeg", "image/png", @@ -363,27 +372,15 @@ def get_random_image(): print("No thumbnail URL.") continue - # ---------------------------------------------------- - # FOUND - # ---------------------------------------------------- - print(f"Found: {title}") print( f"Size: {width}x{height}" ) - # ---------------------------------------------------- - # DOWNLOAD THUMBNAIL - # ---------------------------------------------------- - image_data = download_image( thumbnail_url ) - # ---------------------------------------------------- - # FALLBACK TO ORIGINAL - # ---------------------------------------------------- - if image_data is None and original_url: print( "Trying original..." @@ -399,10 +396,6 @@ def get_random_image(): ) continue - # ---------------------------------------------------- - # CONVERT TO JPEG - # ---------------------------------------------------- - print("Converting to JPEG...") jpeg_data = convert_to_jpeg( @@ -412,10 +405,6 @@ def get_random_image(): if jpeg_data is None: continue - # ---------------------------------------------------- - # SAVE JPEG - # ---------------------------------------------------- - try: OUTPUT_FILE.write_bytes( jpeg_data @@ -427,10 +416,6 @@ def get_random_image(): ) continue - # ---------------------------------------------------- - # SAVE METADATA - # ---------------------------------------------------- - metadata = { "title": title, "source": "Wikimedia Commons", @@ -476,10 +461,6 @@ def get_random_image(): f"metadata: {e}" ) - # ---------------------------------------------------- - # DONE - # ---------------------------------------------------- - print() print("=" * 50) print("SUCCESS") @@ -498,10 +479,16 @@ def get_random_image(): return metadata - raise RuntimeError( - "Unable to obtain a suitable " - "Wikimedia image." - ) + print("[WARNING] Could not download image from Wikimedia Commons. Generating local fallback image.") + return generate_fallback_image() + + +# ============================================================ +# MAIN +# ============================================================ + +if __name__ == "__main__": + get_random_image() # ============================================================ diff --git a/src/rewards_tasks.py b/src/rewards_tasks.py index cc4764e..9beca61 100644 --- a/src/rewards_tasks.py +++ b/src/rewards_tasks.py @@ -28,6 +28,17 @@ class RewardsTaskUtils: self.mouse = mouse_trajectory.MouseUtils(driver) self.keyboard = mimic_typing.KeyboardUtils(driver) self.elements = element_selectors.ElementSelectionUtils(driver) + self.verify_signed_in_state() + + def verify_signed_in_state(self): + try: + time.sleep(2) + url = self.driver.current_url.lower() + if "login.live.com" in url or "account.microsoft.com" in url or "signup" in url: + print("\n[WARNING] Microsoft Rewards is NOT signed in on rewards.bing.com for this profile!") + print("[WARNING] Please sign in once on rewards.bing.com in this Edge profile window.\n") + except Exception: + pass def find_element(self, xpath: str): return self.driver.find_element(By.XPATH, xpath) @@ -49,13 +60,26 @@ class RewardsTaskUtils: def switch_to_dashboard(self): self.move_to_and_click(self.elements.get_dashboard_tab()) - def move_to_and_click(self, elem: WebElement): - self.mouse.move_to_element(elem) - self.mouse.human_like_click() + def move_to_and_click(self, elem_or_getter: WebElement | Callable[[], WebElement], retries: int = 3): + for attempt in range(retries): + try: + if callable(elem_or_getter): + target_elem = elem_or_getter() + else: + target_elem = elem_or_getter + + self.mouse.move_to_element(target_elem) + self.mouse.human_like_click() + return + except StaleElementReferenceException as exc: + if attempt == retries - 1: + raise exc + print(f"[WARNING] StaleElementReferenceException during click attempt {attempt + 1}/{retries}, retrying...") + time.sleep(0.5) def wait_for_then_click(self, element_getter: Callable[[], WebElement], timeout: int = 10): elem = self.wait_for_element(element_getter, timeout) - self.move_to_and_click(elem) + self.move_to_and_click(element_getter if callable(element_getter) else elem) def complete_bing_daily_set(self, expected_activities: int = 3): self.switch_to_earn_page() @@ -79,19 +103,24 @@ class RewardsTaskUtils: print(f"[WARNING] Daily set panel only shows {len(daily_set_links)} of {expected_activities} activities") - # Re-read the panel per index: clicking an activity can re-render it and + main_tab = self.driver.current_window_handle + + # Re-read the panel per index immediately before interaction: clicking an activity can re-render it and # stale the captured references. for index in range(len(daily_set_links)): - activities = self.elements.get_daily_set_elements() + def get_activity_elem(idx=index): + return self.elements.get_daily_set_element_by_index(idx) - if index >= len(activities): - break + try: + self.move_to_and_click(get_activity_elem) + except Exception as exc: + print(f"[WARNING] Failed to click daily set activity {index + 1}: {exc}") + continue - self.move_to_and_click(activities[index]) time.sleep(random.uniform(2, 3)) - self.driver.switch_to.window(self.driver.current_window_handle) # refocus on the main tab + self.tab_utils.close_all_other_tabs(exceptions=[main_tab]) - self.tab_utils.close_all_other_tabs() + self.tab_utils.close_all_other_tabs(exceptions=[main_tab]) def complete_explore_on_bing_tasks(self): self.switch_to_earn_page() @@ -132,6 +161,11 @@ class RewardsTaskUtils: def complete_visual_search(self): self.switch_to_earn_page() + if not os.path.exists(VISUAL_SEARCH_IMAGE_PATH): + print("[INFO] visual_search.jpg not found. Generating visual search image...") + import random_image_for_visual_search + random_image_for_visual_search.get_random_image() + self.wait_for_then_click(self.elements.get_open_visual_search_sidebar) self.wait_for_then_click(self.elements.get_search_now_link_from_visual_search_sidebar) @@ -151,22 +185,32 @@ class RewardsTaskUtils: def complete_misc_cards(self): self.switch_to_earn_page() + main_tab = self.driver.current_window_handle misc_cards: list[WebElement] = self.wait_for_element(self.elements.get_all_misc_cards) - for card in misc_cards: - self.mouse.wheel_scroll_element_into_view(card) + for index in range(len(misc_cards)): + cards = self.elements.get_all_misc_cards() + if index >= len(cards): + break + card = cards[index] - if not self.elements.card_is_complete(card) and self.elements.get_card_point_value(card) > 0: - self.move_to_and_click(card) - time.sleep(random.uniform(1, 2)) - self.driver.switch_to.window(self.driver.current_window_handle) + try: + self.mouse.wheel_scroll_element_into_view(card) - for card in misc_cards: + if not self.elements.card_is_complete(card) and self.elements.get_card_point_value(card) > 0: + self.move_to_and_click(card) + time.sleep(random.uniform(1, 2)) + self.tab_utils.close_all_other_tabs(exceptions=[main_tab]) + except Exception as exc: + print(f"[WARNING] Misc Card [{index}] interaction failed: {exc}") + continue + + for card in self.elements.get_all_misc_cards(): if not self.elements.card_is_complete(card) and self.elements.get_card_point_value(card) > 0: print(f"[WARNING] Misc Card [desc={self.elements.extract_card_descriptions(card)!r}] is not complete after clicking. Please check manually.") - self.tab_utils.close_all_other_tabs() + self.tab_utils.close_all_other_tabs(exceptions=[main_tab]) self.mouse.wheel_scroll_to_top() diff --git a/src/tab_utils.py b/src/tab_utils.py index a72012d..dbe28d1 100644 --- a/src/tab_utils.py +++ b/src/tab_utils.py @@ -38,27 +38,41 @@ document.dispatchEvent(new Event('visibilitychange')); def close_all_other_tabs(self, exceptions: list[str] = None): if exceptions is None: - exceptions = [self.driver.current_window_handle] + try: + exceptions = [self.driver.current_window_handle] + except WebDriverException: + handles = self.driver.window_handles + exceptions = [handles[0]] if handles else [] - switch_back_to = exceptions[0] + switch_back_to = exceptions[0] if exceptions else None - for handle in self.driver.window_handles: + for handle in list(self.driver.window_handles): if handle not in exceptions and handle not in self.problematic_tabs: - self.driver.switch_to.window(handle) - - if self.driver.current_url in GHOST_TAB_URLS: - print(f"[INFO] Found ghost tab with handle {handle} and URL {self.driver.current_url}, not closing.") - continue - - tab_url = self.driver.current_url - try: + self.driver.switch_to.window(handle) + + if self.driver.current_url in GHOST_TAB_URLS: + print(f"[INFO] Found ghost tab with handle {handle} and URL {self.driver.current_url}, not closing.") + continue + + tab_url = self.driver.current_url + self.driver.close() print(f"[INFO] Closed tab with handle {handle} and URL {tab_url}.") except WebDriverException: - print(f"[WARNING] Could not close tab with handle {handle} and URL {tab_url}.") + print(f"[WARNING] Could not close tab with handle {handle}.") self.problematic_tabs.add(handle) pass - self.driver.switch_to.window(switch_back_to) \ No newline at end of file + handles = self.driver.window_handles + if switch_back_to and switch_back_to in handles: + try: + self.driver.switch_to.window(switch_back_to) + except WebDriverException: + if handles: + self.driver.switch_to.window(handles[0]) + elif handles: + self.driver.switch_to.window(handles[0]) + + self.ensure_focus() \ No newline at end of file From 049a119e365c7b8ba1643d1c287d54f72226b90c Mon Sep 17 00:00:00 2001 From: abhijitgorde5-crypto Date: Tue, 8 Sep 2026 00:23:24 +0530 Subject: [PATCH 2/4] Pacing Bing searches to 5.5-7.5s delay to prevent MS Rewards rate-limiting throttling --- src/constants.py | 1 + src/rewards_tasks.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/constants.py b/src/constants.py index 2bf35c7..a552bcc 100644 --- a/src/constants.py +++ b/src/constants.py @@ -6,3 +6,4 @@ USER_DATA_DIR = abspath("./data-dir") PROFILE_NAME = "Default" + diff --git a/src/rewards_tasks.py b/src/rewards_tasks.py index c1b5762..3e265e2 100644 --- a/src/rewards_tasks.py +++ b/src/rewards_tasks.py @@ -386,7 +386,7 @@ class RewardsTaskUtils: ): self.keyboard.send_keys(f"{query} -noai{Keys.ENTER}") - time.sleep(random.uniform(0.5, 1)) + time.sleep(random.uniform(5.5, 7.5)) try: self.wait_for_then_click(self.elements.get_clear_bing_search_query_button) except StaleElementReferenceException: From 03636ac9848a3b7c83143eafc94e40841e8f0c21 Mon Sep 17 00:00:00 2001 From: abhijitgorde5-crypto Date: Tue, 8 Sep 2026 22:55:59 +0530 Subject: [PATCH 3/4] Replace print() with logger calls in llm_utils, random_image_for_visual_search, and rewards_tasks --- src/constants.py | 1 - src/llm_utils.py | 4 +- src/random_image_for_visual_search.py | 106 +++++++++----------------- src/rewards_tasks.py | 10 +-- 4 files changed, 41 insertions(+), 80 deletions(-) diff --git a/src/constants.py b/src/constants.py index a552bcc..2bf35c7 100644 --- a/src/constants.py +++ b/src/constants.py @@ -6,4 +6,3 @@ USER_DATA_DIR = abspath("./data-dir") PROFILE_NAME = "Default" - diff --git a/src/llm_utils.py b/src/llm_utils.py index 38d598c..4cb44bd 100644 --- a/src/llm_utils.py +++ b/src/llm_utils.py @@ -89,7 +89,7 @@ def get_search_query_from_task_description(task_description: str) -> str: response = get_nonempty_ollama_response(messages) return response.lower() except Exception as exc: - print(f"[WARNING] Ollama is offline or unavailable ({exc}). Using fallback search query generator.") + logger.warning("Ollama is offline or unavailable (%s). Using fallback search query generator.", exc) words = [w for w in re.sub(r"[^\w\s]", "", task_description).split() if len(w) > 3 and w.lower() not in {"search", "bing", "find", "about", "with", "from", "that", "this"}] fallback_query = " ".join(words[:4]) if words else f"{get_random_noun()} search" return fallback_query.lower() @@ -126,7 +126,7 @@ def get_related_search_queries(seed_word: str, num_queries: int=20) -> Generator }) continue except Exception as exc: - print(f"[WARNING] Ollama is offline or unavailable ({exc}). Using built-in generator for remaining queries.") + logger.warning("Ollama is offline or unavailable (%s). Using built-in generator for remaining queries.", exc) use_fallback = True noun1 = get_random_noun() diff --git a/src/random_image_for_visual_search.py b/src/random_image_for_visual_search.py index f5dd5c8..00b45ed 100644 --- a/src/random_image_for_visual_search.py +++ b/src/random_image_for_visual_search.py @@ -1,5 +1,6 @@ import io import json +import logging import time from pathlib import Path from urllib.parse import urlsplit, urlunsplit @@ -7,6 +8,8 @@ from urllib.parse import urlsplit, urlunsplit import requests from PIL import Image +logger = logging.getLogger(__name__) + # ============================================================ # CONFIG @@ -93,10 +96,7 @@ def wait_after_429(response, attempt): wait_time = max(5, wait_time) - print( - f"Rate limited. Waiting " - f"{wait_time} seconds..." - ) + logger.warning("Rate limited. Waiting %d seconds...", wait_time) time.sleep(wait_time) @@ -118,7 +118,7 @@ def download_image(url): ) except (requests.RequestException, ConnectionResetError, OSError) as e: - print(f"Download failed: {e}") + logger.warning("Download failed: %s", e) return None if response.status_code == 429: @@ -126,13 +126,13 @@ def download_image(url): return None if response.status_code == 403: - print("Wikimedia returned 403 Forbidden.") + logger.warning("Wikimedia returned 403 Forbidden.") return None try: response.raise_for_status() except requests.RequestException as e: - print(f"HTTP error: {e}") + logger.warning("HTTP error: %s", e) return None content_type = response.headers.get( @@ -141,13 +141,11 @@ def download_image(url): ).lower() if not content_type.startswith("image/"): - print( - f"Not an image: {content_type}" - ) + logger.warning("Not an image: %s", content_type) return None if not response.content: - print("Downloaded image is empty.") + logger.warning("Downloaded image is empty.") return None return response.content @@ -188,15 +186,13 @@ def convert_to_jpeg(image_data): return output.getvalue() except Exception as e: - print( - f"JPEG conversion failed: {e}" - ) + logger.warning("JPEG conversion failed: %s", e) return None def generate_fallback_image(): """Generate a synthetic local JPEG image using PIL as a fallback.""" - print("[INFO] Generating synthetic local fallback image for visual search...") + logger.info("Generating synthetic local fallback image for visual search...") from PIL import ImageDraw import random @@ -215,7 +211,7 @@ def generate_fallback_image(): jpeg_data = output.getvalue() OUTPUT_FILE.write_bytes(jpeg_data) - print(f"Saved fallback image to {OUTPUT_FILE.absolute()}") + logger.info("Saved fallback image to %s", OUTPUT_FILE.absolute()) return {"title": "Fallback Synthetic Image", "width": 800, "height": 600} @@ -233,10 +229,7 @@ def get_random_image(): if attempt > 1: time.sleep(REQUEST_DELAY) - print( - f"\nAttempt " - f"{attempt}/{MAX_ATTEMPTS}" - ) + logger.debug("Attempt %d/%d", attempt, MAX_ATTEMPTS) params = { "action": "query", @@ -263,7 +256,7 @@ def get_random_image(): ) except (requests.RequestException, ConnectionResetError, OSError) as e: - print(f"API request failed: {e}") + logger.warning("API request failed: %s", e) continue if response.status_code == 429: @@ -281,7 +274,7 @@ def get_random_image(): requests.RequestException, ValueError, ) as e: - print(f"API error: {e}") + logger.warning("API error: %s", e) continue pages = ( @@ -291,7 +284,7 @@ def get_random_image(): ) if not pages: - print("No page returned.") + logger.debug("No page returned.") continue page = next( @@ -308,9 +301,7 @@ def get_random_image(): ) if not imageinfo: - print( - "No image information." - ) + logger.debug("No image information.") continue info = imageinfo[0] @@ -348,55 +339,39 @@ def get_random_image(): "image/png", "image/webp", }: - print( - f"Skipping unsupported type: " - f"{mime}" - ) + logger.debug("Skipping unsupported type: %s", mime) continue if width < MIN_WIDTH or height < MIN_HEIGHT: - print( - f"Skipping small image: " - f"{width}x{height}" - ) + logger.debug("Skipping small image: %dx%d", width, height) continue if size > MAX_FILE_SIZE: - print( - f"Skipping large image: " - f"{size / 1024 / 1024:.1f} MB" - ) + logger.debug("Skipping large image: %.1f MB", size / 1024 / 1024) continue if not thumbnail_url: - print("No thumbnail URL.") + logger.debug("No thumbnail URL.") continue - print(f"Found: {title}") - print( - f"Size: {width}x{height}" - ) + logger.debug("Found: %s (%dx%d)", title, width, height) image_data = download_image( thumbnail_url ) if image_data is None and original_url: - print( - "Trying original..." - ) + logger.debug("Thumbnail download failed, trying original URL...") image_data = download_image( original_url ) if image_data is None: - print( - "Couldn't download image." - ) + logger.debug("Couldn't download image.") continue - print("Converting to JPEG...") + logger.debug("Converting to JPEG...") jpeg_data = convert_to_jpeg( image_data @@ -411,9 +386,7 @@ def get_random_image(): ) except OSError as e: - print( - f"Couldn't save image: {e}" - ) + logger.warning("Couldn't save image: %s", e) continue metadata = { @@ -456,30 +429,19 @@ def get_random_image(): ) except OSError as e: - print( - f"Warning: couldn't save " - f"metadata: {e}" - ) + logger.warning("Couldn't save metadata: %s", e) - print() - print("=" * 50) - print("SUCCESS") - print("=" * 50) - print( - f"Image: " - f"{OUTPUT_FILE.absolute()}" - ) - print( - f"Size: " - f"{len(jpeg_data) / 1024:.1f} KB" - ) - print( - f"Source: {title}" + logger.info( + "Visual search image saved: %s (%s, %.1f KB, source: %s)", + OUTPUT_FILE.absolute(), + f"{width}x{height}", + len(jpeg_data) / 1024, + title, ) return metadata - print("[WARNING] Could not download image from Wikimedia Commons. Generating local fallback image.") + logger.warning("Could not download image from Wikimedia Commons. Generating local fallback image.") return generate_fallback_image() diff --git a/src/rewards_tasks.py b/src/rewards_tasks.py index 3e265e2..0c327b4 100644 --- a/src/rewards_tasks.py +++ b/src/rewards_tasks.py @@ -91,8 +91,8 @@ class RewardsTaskUtils: time.sleep(2) url = self.driver.current_url.lower() if "login.live.com" in url or "account.microsoft.com" in url or "signup" in url: - print("\n[WARNING] Microsoft Rewards is NOT signed in on rewards.bing.com for this profile!") - print("[WARNING] Please sign in once on rewards.bing.com in this Edge profile window.\n") + logger.warning("Microsoft Rewards is NOT signed in on rewards.bing.com for this profile!") + logger.warning("Please sign in once on rewards.bing.com in this Edge profile window.") except Exception: pass @@ -203,7 +203,7 @@ class RewardsTaskUtils: try: self.move_to_and_click(get_activity_elem) except Exception as exc: - print(f"[WARNING] Failed to click daily set activity {index + 1}: {exc}") + logger.warning("Failed to click daily set activity %d: %s", index + 1, exc) continue time.sleep(random.uniform(2, 3)) @@ -254,7 +254,7 @@ class RewardsTaskUtils: self.switch_to_earn_page() if not os.path.exists(VISUAL_SEARCH_IMAGE_PATH): - print("[INFO] visual_search.jpg not found. Generating visual search image...") + logger.info("visual_search.jpg not found. Generating visual search image...") import random_image_for_visual_search random_image_for_visual_search.get_random_image() @@ -295,7 +295,7 @@ class RewardsTaskUtils: time.sleep(random.uniform(1, 2)) self.tab_utils.close_all_other_tabs(exceptions=[main_tab]) except Exception as exc: - print(f"[WARNING] Misc Card [{index}] interaction failed: {exc}") + logger.warning("Misc Card [%d] interaction failed: %s", index, exc) continue for card in self.elements.get_all_misc_cards(): From 914d910922eae9cda24e9bf927c2c19f41738f8f Mon Sep 17 00:00:00 2001 From: Carl Furtado Date: Tue, 8 Sep 2026 17:04:21 -0400 Subject: [PATCH 4/4] clean up constants.py --- src/constants.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/constants.py b/src/constants.py index 2bf35c7..d675096 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,8 +1,4 @@ -import os from os.path import abspath USER_DATA_DIR = abspath("./data-dir") - -PROFILE_NAME = "Default" - - +PROFILE_NAME = "Default" \ No newline at end of file