mirror of
https://github.com/User0332/rewards-farmer.git
synced 2026-09-16 01:31:36 +00:00
fill the search quota by measuring instead of assuming a rate
searches_needed was computed once as (max - earned) // 5 and never re-checked. Two assumptions fail in practice: some markets award 3 points per search rather than 5, and the daily maximum itself is not stable, observed as 15, 30 and 60 on one account within a day with the counter resetting. The run therefore stopped around 18/30 and still reported success. Search in rounds instead: measure, run a batch sized on the lower known rate, measure again, stop when the quota is full or a round gains nothing, and warn instead of claiming success when it is not filled. Also give the ollama client a timeout and bound the empty-response retry, since both were unbounded and an unattended run hung for 14 minutes with 2.3 CPU-seconds. The bare while-not-response loop spins forever on empty responses.
This commit is contained in:
+23
-3
@@ -31,14 +31,34 @@ DEFAULT_USER_PROMPT_FOR_SEARCH_POINTS_WITHOUT_DESC = """Generate the first searc
|
|||||||
|
|
||||||
USER_PROMPT_FOR_SEARCH_QUERY_CONTINUATION = """Generate the next search query."""
|
USER_PROMPT_FOR_SEARCH_QUERY_CONTINUATION = """Generate the next search query."""
|
||||||
|
|
||||||
|
# Without an explicit timeout a stalled or cold ollama backend blocks the whole
|
||||||
|
# run forever, which is fatal for an unattended scheduled run.
|
||||||
|
_CLIENT = ollama.Client(timeout=180)
|
||||||
|
|
||||||
|
MAX_EMPTY_RETRIES = 5
|
||||||
|
|
||||||
|
|
||||||
def get_ollama_response(messages: list[dict[str, str]], model: str="gemma4:cloud") -> str:
|
def get_ollama_response(messages: list[dict[str, str]], model: str="gemma4:cloud") -> str:
|
||||||
response = ollama.chat(
|
response = _CLIENT.chat(
|
||||||
model=model,
|
model=model,
|
||||||
messages=messages
|
messages=messages
|
||||||
)
|
)
|
||||||
|
|
||||||
return response.message.content
|
return response.message.content
|
||||||
|
|
||||||
|
|
||||||
|
def get_nonempty_ollama_response(messages: list[dict[str, str]]) -> str:
|
||||||
|
"""Retry a bounded number of times instead of spinning forever on empties."""
|
||||||
|
for attempt in range(MAX_EMPTY_RETRIES):
|
||||||
|
response = get_ollama_response(messages)
|
||||||
|
|
||||||
|
if response and response.strip():
|
||||||
|
return response
|
||||||
|
|
||||||
|
print(f"[WARNING] Empty LLM response, retry {attempt + 1}/{MAX_EMPTY_RETRIES}")
|
||||||
|
|
||||||
|
raise RuntimeError(f"LLM returned nothing usable after {MAX_EMPTY_RETRIES} attempts")
|
||||||
|
|
||||||
def get_search_query_from_task_description(task_description: str) -> str:
|
def get_search_query_from_task_description(task_description: str) -> str:
|
||||||
# compat
|
# compat
|
||||||
if "lyrics of your favorite song" in task_description.lower(): return "sweet caroline lyrics"
|
if "lyrics of your favorite song" in task_description.lower(): return "sweet caroline lyrics"
|
||||||
@@ -54,7 +74,7 @@ def get_search_query_from_task_description(task_description: str) -> str:
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
while not (response := get_ollama_response(messages)): pass # ensure non-empty response
|
response = get_nonempty_ollama_response(messages)
|
||||||
|
|
||||||
return response.lower()
|
return response.lower()
|
||||||
|
|
||||||
@@ -71,7 +91,7 @@ def get_related_search_queries(seed_word: str, num_queries: int=20) -> Generator
|
|||||||
]
|
]
|
||||||
|
|
||||||
for _ in range(num_queries):
|
for _ in range(num_queries):
|
||||||
while not (response := get_ollama_response(messages)): pass # ensure non-empty response
|
response = get_nonempty_ollama_response(messages)
|
||||||
|
|
||||||
yield response.lower()
|
yield response.lower()
|
||||||
|
|
||||||
|
|||||||
+47
-16
@@ -154,14 +154,57 @@ class RewardsTaskUtils:
|
|||||||
for i in range(scroll_times):
|
for i in range(scroll_times):
|
||||||
ActionChains(self.driver).scroll_by_amount(0, -100).perform() # scroll back to top of page
|
ActionChains(self.driver).scroll_by_amount(0, -100).perform() # scroll back to top of page
|
||||||
|
|
||||||
def complete_required_searches(self):
|
def complete_required_searches(self, max_rounds: int = 6):
|
||||||
|
# Points per search are not fixed. Some markets award 3 rather than 5,
|
||||||
|
# the daily maximum itself changes (observed 15, 30 and 60 on the same
|
||||||
|
# account within one day, with the counter resetting), and daily set and
|
||||||
|
# card searches count towards the same quota. A single up front division
|
||||||
|
# therefore leaves points on the table and still reports success.
|
||||||
|
# Measure, search, measure again.
|
||||||
|
points_earned, max_pts = self.read_search_points()
|
||||||
|
|
||||||
|
print(f"[INFO] Search points before: {points_earned}/{max_pts}")
|
||||||
|
|
||||||
|
for round_number in range(1, max_rounds + 1):
|
||||||
|
if points_earned >= max_pts:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Assume the lower known rate so a round never overshoots by much.
|
||||||
|
searches = max(1, (max_pts - points_earned) // 3)
|
||||||
|
|
||||||
|
self.run_search_batch(searches)
|
||||||
|
|
||||||
|
previous = points_earned
|
||||||
|
points_earned, max_pts = self.read_search_points()
|
||||||
|
|
||||||
|
print(f"[INFO] Round {round_number}: {searches} searches -> {points_earned}/{max_pts}")
|
||||||
|
|
||||||
|
if points_earned <= previous:
|
||||||
|
print("[WARNING] Round produced no points, stopping instead of searching pointlessly.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if points_earned < max_pts:
|
||||||
|
print(f"[WARNING] Search quota not filled: {points_earned}/{max_pts}")
|
||||||
|
else:
|
||||||
|
print(f"Search quota complete: {points_earned}/{max_pts}")
|
||||||
|
|
||||||
|
def read_search_points(self):
|
||||||
|
"""Open the points breakdown, read the Bing search row, close it again."""
|
||||||
self.switch_to_earn_page()
|
self.switch_to_earn_page()
|
||||||
self.wait_for_then_click(self.elements.get_points_breakdown_button)
|
self.wait_for_then_click(self.elements.get_points_breakdown_button)
|
||||||
self.wait_for_element(self.elements.get_close_button_on_points_breakdown) # make sure sidebar loads
|
|
||||||
|
close_btn = self.wait_for_element(self.elements.get_close_button_on_points_breakdown)
|
||||||
|
|
||||||
points_earned, max_pts = self.elements.get_points_earned_from_searches_on_points_breakdown()
|
points_earned, max_pts = self.elements.get_points_earned_from_searches_on_points_breakdown()
|
||||||
searches_needed = (max_pts - points_earned) // 5
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.move_to_and_click(close_btn)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return points_earned, max_pts
|
||||||
|
|
||||||
|
def run_search_batch(self, count: int):
|
||||||
self.driver.get("https://www.bing.com/")
|
self.driver.get("https://www.bing.com/")
|
||||||
self.tab_utils.ensure_focus()
|
self.tab_utils.ensure_focus()
|
||||||
|
|
||||||
@@ -171,7 +214,7 @@ class RewardsTaskUtils:
|
|||||||
|
|
||||||
for i, query in enumerate(
|
for i, query in enumerate(
|
||||||
llm_utils.get_related_search_queries(
|
llm_utils.get_related_search_queries(
|
||||||
llm_utils.get_random_noun(), num_queries=searches_needed
|
llm_utils.get_random_noun(), num_queries=count
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self.keyboard.send_keys(query+Keys.ENTER)
|
self.keyboard.send_keys(query+Keys.ENTER)
|
||||||
@@ -186,18 +229,6 @@ class RewardsTaskUtils:
|
|||||||
self.driver.get("https://rewards.bing.com/")
|
self.driver.get("https://rewards.bing.com/")
|
||||||
self.tab_utils.ensure_focus()
|
self.tab_utils.ensure_focus()
|
||||||
|
|
||||||
self.switch_to_earn_page()
|
|
||||||
|
|
||||||
self.wait_for_then_click(self.elements.get_points_breakdown_button)
|
|
||||||
|
|
||||||
close_btn = self.wait_for_element(self.elements.get_close_button_on_points_breakdown)
|
|
||||||
|
|
||||||
points_earned, max_pts = self.elements.get_points_earned_from_searches_on_points_breakdown()
|
|
||||||
|
|
||||||
self.move_to_and_click(close_btn)
|
|
||||||
|
|
||||||
print(f"Points earned from {searches_needed} searches: {points_earned}/{max_pts}")
|
|
||||||
|
|
||||||
def claim_bonus_points(self):
|
def claim_bonus_points(self):
|
||||||
self.switch_to_dashboard()
|
self.switch_to_dashboard()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user