Skip to content

Chore/refactor#45

Open
JeromeM wants to merge 17 commits into
safarsin:mainfrom
JeromeM:chore/refactor
Open

Chore/refactor#45
JeromeM wants to merge 17 commits into
safarsin:mainfrom
JeromeM:chore/refactor

Conversation

@JeromeM
Copy link
Copy Markdown
Contributor

@JeromeM JeromeM commented May 25, 2026

Summary

Structural refactor of src/ into feature-based subpackages, alignment
with main v3.3, and a redesign of the OS-level daily autostart:
fire-on-login → one scheduled task per account, each with its own
configurable HH:MM trigger time
. Also adds a user-controlled
Close-to-tray toggle so the tray behavior is opt-out.

No user-visible behavior change in the GUI beyond the new per-account
"Daily run time" field and the new "Close to tray" toggle — the
reorganization is purely internal, and every v3.3 functional addition
from main was ported as-is.

Why

The src/daily_set.py monolith (1037 lines) and the loose file layout
at the root of src/ made navigation and maintenance painful. In
parallel, the original "HKCU Run / .desktop" autostart only fired on
login — on a machine left logged in for several days, the daily run
was never triggered, and it always popped a visible GUI window at
every session start. A first iteration moved this to a single
scheduled task at 09:00, but multiple accounts then ran concurrently
and there was no way to stagger them.

What changed

1. src/ reorganization into subpackages

Before After
src/account_manager.py src/accounts/manager.py
src/settings_manager.py src/accounts/{meta,settings}.py (split by class)
src/daily_set.py (1037 L) src/dailytasks/{runner,card,card_js}.py
src/driver_manager.py src/emulator/driver.py
src/edge_policy.py src/emulator/edge_policy.py
src/human_behavior.py src/emulator/human.py
src/search_engine.py src/search/engine.py
src/history.py src/search/history.py
GUI/ gui/ (lowercase)

Each subpackage exposes a stable API through its __init__.py. No
duplication, no class renames.

2. Per-account daily-scheduler autostart

Each account now owns its own OS-level scheduled task that fires at
its own configurable wall-clock time:

  • Windows: schtasks /Create /TN AutoRewarder.<account_id> /TR "...exe --headless --account <id>" /SC DAILY /ST HH:MM /F
  • Linux: systemd --user autorewarder-<account_id>.{service,timer}
    with OnCalendar=*-*-* HH:MM:00 and Persistent=true (catches up
    on missed triggers if the machine was suspended / off)
  • The CLI invocation includes --headless --account <id>, so the
    scheduled run goes straight to AutoRewarder_CLI.main() for that
    one account — no GUI window, no other accounts touched.

Per-account state lives in accounts/<id>/meta.json:

{
  "schedule": {
    "enabled": true,
    "run_time": "09:00",   // NEW — HH:MM, validated
    "queries_pc": 30,
    ...
  }
}

The global "Start with Windows/Linux" toggle remains, but it's now a
master switch: ON syncs every account whose schedule.enabled = true to its own task; OFF removes all per-account tasks at once.
Toggling a single account's schedule on/off (or changing its
run_time) re-syncs that one task in real time via set_schedule.
Deleting an account tears down its scheduled task before file
deletion.

The fire time is exposed in the GUI as a new "Daily run time" input
on each schedule card (HTML5 <input type="time">).

3. Close-to-tray toggle

New global setting close_to_tray (default True, preserves the v3.3
tray rollout) with a toggle in Settings → General. When OFF, the X
button quits the app like a normal window and the tray icon is never
installed in the first place. Read once at startup, mirroring how
hide_browser and autoStartUp are loaded — the in-app log confirms
the change with a "Restart to apply" hint.

4. v3.3 alignment (ported from main)

  • Advanced scheduling backend (_run_advanced_schedule,
    _sleep_with_stop) + guard when advancedScheduling is enabled
    but the master Schedule toggle is off
  • System tray (pystray + Pillow) — close-to-tray, Open/Exit menu,
    graceful fallback if the deps aren't installed
  • Module docstrings + Args/Returns/Raises on every src/ file
  • Version bump to 3.3 (AutoRewarder.iss, gui/index.html,
    src/config.py)
  • Conditional "done" message at the end of an advanced schedule run

5. CI & tooling

  • Restored .pre-commit-config.yaml + requirements-dev.txt
  • .github/workflows/lint.yml aligned with the versions pinned in
    requirements-dev.txt
  • .flake8 with per-file-ignores for src/dailytasks/card_js.py
    (the embedded JS regex literals must stay on a single line)
  • .gitignore extended (lint caches + venvs)

User migration

No action required. On every launch, _migrate_legacy_autostart
detects any pre-per-account autostart artifact and either fully
migrates it or just cleans up the stale remnants:

  1. Fire-on-login (HKCU Run / .desktop) from older versions →
    removed; per-account tasks created for every enabled account
  2. Single-task daily scheduler (AutoRewarder task at hardcoded
    09:00) from an intermediate version → removed; per-account tasks
    created at each account's run_time (default 09:00)
  3. Already on per-account model AND a legacy entry still exists
    (e.g. a previous migration's schtasks /Delete silently failed
    due to permissions) → cleanup retried at every startup, with
    [WARNING] Could not delete legacy task '...' logged on failure
    so the user knows to delete it by hand
  4. Already on per-account model, no legacy → no-op

Per-account schedules (accounts/<id>/meta.json) are preserved.
Edge profiles, history, and settings remain intact.

To verify after the update:

schtasks /Query /FO LIST /V | findstr AutoRewarder    :: Windows
ls ~/.config/systemd/user/autorewarder-*.timer        :: Linux

You should see one entry per enabled account (with the .<id> /
-<id> suffix) and no entry for the unsuffixed legacy name.

Test plan

  • pre-commit run --all-files → green (black, flake8, mypy,
    whitespace hooks)
  • python -c "from src.api import AutoRewarderAPI; AutoRewarderAPI()"
    → instantiates without error
  • Manual GUI launch: window opens, existing accounts loaded,
    per-account schedule editable with the new "Daily run time"
    field
  • Close-to-tray toggle ON: clicking X hides the window, tray
    icon visible, "Open" restores it, "Exit" kills the process
    cleanly
  • Close-to-tray toggle OFF + restart: tray icon does NOT
    appear, X button quits the app normally
  • Headless: python AutoRewarder.py --headless → no window,
    logs land in background_log.txt
  • Advanced scheduling: enable Schedule + advancedScheduling
    on one account (1h / 10 qph) → run paced in ~10-minute batches
  • Stop button mid-run: closes Edge cleanly without leaving
    orphan instances
  • Per-account scheduling:
    • Toggle global Start-with-Windows ON with 2 accounts at
      different times → 2 scheduled tasks appear, each at its own
      time
    • Disable one account's schedule → only that account's task
      removed
    • Change one account's run_time and save → that account's
      task is re-registered at the new time
    • Delete an account → its scheduled task is removed
    • Toggle global OFF → all per-account tasks removed
  • Legacy migration:
    • On an install that had the single-task autostart enabled →
      old task disappears on next launch, per-account tasks appear
    • If the old task is sticky (e.g. previously created elevated),
      the cleanup is retried on every launch and logs a [WARNING]
      line with schtasks's error message instead of silently giving up

Notable trade-offs

  • The main merge resolves the conflicts inherent to structural
    renames (git doesn't detect daily_set.pydailytasks/* as a
    rename). This is intentional and clean on the code side, but
    GitHub displays an artificially large changeset, dominated by
    moved files.
  • _set_autostart_registry keeps its historical name (rather than
    becoming _set_autostart) so no existing caller in the CLI or in
    tests breaks.
  • The default fire time when an account has no run_time yet is
    "09:00" (constant AUTOSTART_TIME in src/api.py).
  • Several scheduled tasks fire simultaneously if the user sets all
    accounts to the same time — by design, since the user chose those
    times. The whole point of per-account times is to let them stagger.
  • The Close-to-tray setting is read once at app startup. Flipping
    the toggle requires a restart to take effect, consistent with how
    hide_browser and autoStartUp are loaded. The in-app log makes
    this explicit.

Reviewers

Suggested reading order:

  1. src/accounts/__init__.py, src/emulator/__init__.py,
    src/search/__init__.py, src/dailytasks/__init__.py — public
    surface of the new packages
  2. src/accounts/meta.py — the new run_time schedule field
  3. src/accounts/settings.py — the new close_to_tray global setting
  4. src/api.py — where everything wires together:
    • _normalize_run_time, _windows_task_name,
      _systemd_unit_base (per-account naming helpers)
    • _sync_account_autostart / _sync_all_autostart (the new
      sync layer)
    • set_schedule (re-syncs the OS task after every save)
    • _detect_legacy_autostart / _migrate_legacy_autostart /
      _cleanup_legacy_autostart (idempotent cleanup with logging)
    • get_close_to_tray / set_close_to_tray (JS-exposed)
  5. gui/script.js + gui/index.html — new "Daily run time" and
    "Close to tray" UI
  6. AutoRewarder.py — tray icon installation gated on the setting
  7. */manager.py, */runner.py etc. are essentially the content of
    the old files, resplit by responsibility

JeromeM added 14 commits May 1, 2026 10:18
Add option to only do daily tasks
Add a Stop button to properly shutdown every Edge instances
Ports release-prep content from main (bc88a5a + da7dd64 USER_GUIDE bits)
onto the refactor layout. Version bumped in AutoRewarder.iss, gui/index.html
and src/config.py. README/USER_GUIDE refreshed with v3.3 sections
(tray, daily-tasks-only, advanced scheduling), keeping the refactored
src/ directory tree.
Ports ff2a5b2 (advanced scheduling) + cea6f3f (schedule control guard)
onto the refactored api.py. Drip-feeds queries across the configured
duration in ~10-minute batches with jittered sleeps, respects the Stop
event, and warns when advancedScheduling is enabled but the master
Schedule toggle is off.

The GUI plumbing (account_meta.get_schedule / set_schedule, modal
toggles) was already in place on refactor — only the backend was
missing.
Ports dc0f47f + 1d9ba94 + b9b1057 (combined). Closing the main window
now hides it to a system tray (pystray + Pillow). Tray menu: Open
(default — restore window), Exit (force destroy). Falls back gracefully
if pystray/PIL aren't installed.

Spec file: adds pystray/_win32 and PIL/Image to hiddenimports so the
PyInstaller bundle includes them.
Adds module-level docstrings to every src/ module and expands method
docstrings with Args/Returns/Raises sections. Where files were split by
the refactor (settings_manager → accounts/{meta,settings}, daily_set →
dailytasks/{runner,card,card_js}), the docstrings landed on the
corresponding new class/function.
The refactor commit had dropped .pre-commit-config.yaml and
requirements-dev.txt (added on main via the daily-tasks PR). Restored
them verbatim from main so local hooks and CI mypy match again. Also
ported main's lint.yml — it now installs from requirements-dev.txt
instead of pinning tool versions inline. Added linter/venv cache
patterns to .gitignore.
Replaces the previous fire-on-login autostart (HKCU Run on Windows,
~/.config/autostart/*.desktop on Linux) with a daily wall-clock
trigger at AUTOSTART_TIME (09:00 by default):

  * Windows: schtasks /Create /SC DAILY /ST 09:00 /F
  * Linux:   systemd --user .service + .timer with OnCalendar=*-*-*
             09:00:00 and Persistent=true (catches up after suspend/off)

Adds _cleanup_legacy_autostart() so users transitioning from the old
mechanism don't end up with both fires registered. is_autostart_enabled
now queries the scheduler directly (schtasks /Query, systemctl --user
is-enabled) instead of poking the registry / scanning files.

Solves the "always-logged-in machines never re-fire" failure mode of
the previous design.

(Sourced from the fix/daily WIP stash, ported manually onto the
refactored layout to preserve all docstrings and the scheduling
backend untouched.)
Pre-commit hooks (end-of-file-fixer, trailing-whitespace, mixed-line-ending)
auto-fixed pre-existing inconsistencies across docs, configs and assets.
Also adds a .flake8 config with a per-file E501 ignore for
src/dailytasks/card_js.py — that module embeds JS regex literals as
Python triple-quoted strings; the regex bodies must stay on one line
for the JS engine to parse them.
Mirrors the _migrate_legacy_global_schedule pattern: on every app
launch, if a legacy HKCU Run entry (Windows) or .desktop autostart
file (Linux) exists but the new daily scheduler isn't set up yet,
transparently switch over to schtasks /SC DAILY (Windows) or systemd
--user timer (Linux). Users upgrading don't have to manually toggle
Autostart off-then-on to stop the visible GUI window from popping at
every login.

No-op when the new scheduler is already active. Failures are silently
swallowed — a stale legacy entry is not worth crashing app startup.
Port of upstream 594eb14. The trailing "Advanced scheduled run complete."
log fired unconditionally, even when the run was stopped or ran out of
queries early. Now logs "Advanced schedule completed!" only when the
loop exited cleanly with both counters at 0 and no Stop pressed.
Brings origin/main up to v3.3 (594eb14) into the refactored layout so
the PR can fast-forward. All substantive content from main was already
ported on this branch — conflicts were the structural ones git can't
auto-detect:

  * GUI/ vs gui/         → keep gui/ (lowercase)
  * src/daily_set.py     → keep deleted (split into src/dailytasks/*)
  * src/settings_manager → keep split into src/accounts/{meta,settings}.py
  * src/api.py           → keep refactor's autostart+scheduling rewrite

Screenshots: take main's v3.3 set (main_window_3.3.png/.gif), drop the
older main_2.png / main_demo.gif.
The previous design used a single OS-level scheduled task firing at
a hardcoded 09:00 that then iterated every account. This switches to
one scheduled task per account, each with its own configurable HH:MM
trigger.

* schedule.run_time (HH:MM, defaults to 09:00) added to per-account
  meta.json; validated by _normalize_run_time on both ends
* Windows: schtasks /Create /TN AutoRewarder.<account_id>
  /TR "...exe --headless --account <id>" /SC DAILY /ST HH:MM /F
* Linux:   systemd --user autorewarder-<account_id>.{service,timer}
  with OnCalendar=*-*-* HH:MM:00 and Persistent=true
* _sync_account_autostart(id) creates / updates / removes one
  account's task based on its schedule.enabled and run_time
* _set_autostart_registry now persists autoStartUp in global settings
  and syncs every account; off-toggle removes all per-account tasks
* set_schedule re-syncs the account's OS task on every save
* delete_account tears down the OS task before removing files
* _migrate_legacy_autostart now also detects the v3.3 single-task
  daily scheduler and migrates it to per-account
* _cleanup_legacy_autostart also strips the single-task remnants

GUI: each schedule card now has a "Daily run time" field (HTML5
time input). Live summary shows the time. Save path validates
HH:MM and includes run_time in the payload.

Users staggering several accounts (e.g. Alice 09:00, Bob 10:30) no
longer see concurrent Edge instances fighting for resources.
@JeromeM JeromeM marked this pull request as draft May 25, 2026 20:46
JeromeM added 2 commits May 25, 2026 22:49
Two bugs landed in the previous per-account autostart commit:

1. _migrate_legacy_autostart short-circuited as soon as
   autoStartUp=True was persisted. If the very first migration's
   schtasks delete had silently failed (returncode not checked, no
   log), every subsequent launch read autoStartUp=True and returned
   immediately — the stale legacy single-task `AutoRewarder` survived
   forever alongside the new per-account ones, firing concurrently.

2. _cleanup_legacy_autostart wrapped schtasks /Delete in
   capture_output=True without inspecting the result, so a failed
   delete left no trace in the log.

Fix:
* Split detection into _detect_legacy_autostart(); call it on every
  startup. If a stale legacy entry exists AND we're already on the
  per-account model (autoStartUp=True), just run cleanup without
  touching per-account tasks. If autoStartUp=False, do the full
  migration as before.
* _cleanup_legacy_autostart now queries before deleting on Windows
  (so we only log delete failures when the task actually existed),
  and logs both success and failure paths for schtasks + systemd +
  .desktop. Users hitting permission issues now have a "[WARNING]
  Could not delete legacy task..." line in the log to point at.

Users currently in the broken state (both tasks present) will see
the stale one removed on the next app launch.
New global setting `close_to_tray` (default True for backwards compat
with the v3.3 tray rollout) lets users opt out of the close-to-tray
behavior. When OFF, the X button quits the app like a normal window
and the tray icon is never installed in the first place.

The setting is read once at startup, mirroring how `hide_browser` and
`autoStartUp` are loaded — flipping the toggle in Settings shows a
log line confirming the change with a "Restart to apply" hint.

* src/accounts/settings.py: defaults + set_close_to_tray
* src/api.py: get_close_to_tray / set_close_to_tray exposed to JS
* AutoRewarder.py: read at startup, gate _install_tray on the value
* gui/index.html: new Close-to-tray toggle in the General section
* gui/script.js: load on Settings open, persist on Save
@JeromeM JeromeM marked this pull request as ready for review May 25, 2026 21:02
When the user runs from source (sys.frozen=False) on Windows, the
registered schtasks command pointed at python.exe — the console
variant. Every time the task fired, Windows allocated a console
window for that process, producing a brief Python terminal popup
that's exactly what the daily scheduler was supposed to prevent.

Fix: _autostart_command prefers pythonw.exe (same interpreter, no
console) when it exists next to sys.executable on Windows. The
bundled exe path is unaffected — PyInstaller's console=False already
guarantees silence there.

Existing users on autoStartUp=True have tasks still pointing at
python.exe. Introduce `autostart_schema_version` (current=1) in
global_settings so _migrate_legacy_autostart now also handles a
"refresh tasks under current schema" path: at next app launch, every
per-account task is re-registered with the new command. No user
action required.
@safarsin
Copy link
Copy Markdown
Owner

Hi @JeromeM, thanks for this PR.

I'll get to testing and reviewing this properly as soon as I have some free time.

Really appreciate your work on this.

@JeromeM
Copy link
Copy Markdown
Contributor Author

JeromeM commented May 26, 2026

As always, take your time and focus with your exams :)

@safarsin safarsin requested review from Copilot and safarsin June 3, 2026 21:36
@safarsin safarsin self-assigned this Jun 3, 2026
@safarsin safarsin added the enhancement New feature or request label Jun 3, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR performs a large internal refactor of src/ into feature-based subpackages while also redesigning OS-level autostart from “fire-on-login” to per-account daily scheduled tasks (each account has its own HH:MM trigger time). It also introduces a global Close-to-tray toggle and adds/updates GUI surfaces to configure these behaviors.

Changes:

  • Reorganized the Python backend into accounts/, emulator/, search/, and dailytasks/ subpackages with stable exports via each package’s __init__.py.
  • Reworked autostart/scheduling to create and manage one scheduled task per account (Windows Task Scheduler / Linux systemd user timers), including legacy artifact migration/cleanup.
  • Updated the GUI to support a per-account “Daily run time” field and a global “Close to tray” setting; added a new history window.

Reviewed changes

Copilot reviewed 21 out of 30 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/search/history.py Adds per-account history storage manager with backup-on-corruption handling.
src/search/engine.py Updates imports to align with the new package layout.
src/search/init.py Exposes SearchEngine and HistoryManager as the src.search public API.
src/emulator/human.py Adds/refactors human-like input simulation (mouse + touch) for Selenium.
src/emulator/edge_policy.py Adds Windows-only Edge policy helper module for sign-in behavior.
src/emulator/driver.py Introduces per-account Edge WebDriver setup and mobile emulation support.
src/emulator/init.py Exposes emulator public API (DriverManager, HumanBehavior, edge_policy).
src/dailytasks/runner.py Splits daily tasks orchestration from the old monolith into a focused runner.
src/dailytasks/card.py Adds a RewardsCard abstraction for DOM heuristics + click/tab handling.
src/dailytasks/card_js.py Centralizes JS heuristics and introduces CardStatus enum.
src/dailytasks/init.py Exposes daily-tasks public API (DailySet, RewardsCard, CardStatus).
src/daily_set.py Removes the previous monolithic daily set implementation (moved/split).
src/config.py Updates GUI directory constant to match GUI/gui/ rename.
src/api.py Major wiring changes: new packages, per-account scheduling/autostart sync, close-to-tray API.
src/accounts/settings.py Adds global settings persistence (including close_to_tray) under new layout.
src/accounts/meta.py Adds run_time to per-account schedule defaults and adjusts module responsibilities.
src/accounts/manager.py Updates imports to match new package structure; continues account CRUD.
src/accounts/init.py Exposes accounts public API (AccountManager, AccountMetaManager, etc.).
README.md Updates repository structure documentation to reflect the new layout.
gui/styles.css Replaces/introduces consolidated styling for the refactored GUI.
gui/settings.js Adds/refactors account management modal logic under new GUI layout.
gui/script.js Adds “Daily run time” + “Close to tray” wiring and schedule save/validation updates.
gui/normalize.css Adds a CSS reset for consistent styling across WebView contexts.
gui/index.html Adds Close-to-tray toggle in Settings UI and reflects GUI directory rename.
gui/history.html Adds a dedicated execution history window UI.
gui/history.css Adds styling for the new history window.
AutoRewarder.spec Updates PyInstaller datas to bundle gui/ instead of GUI/.
AutoRewarder.py Gates tray installation on the new close_to_tray setting.
AutoRewarder_CLI.py Updates imports for AccountMetaManager after refactor.
.flake8 Adds flake8 config and per-file ignore for embedded JS regex line length.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/dailytasks/runner.py
Comment on lines +229 to +236
current = driver.find_elements(By.CSS_SELECTOR, selector)
if idx >= len(current):
self._log(
f"[WARNING] {section_name} card #{idx + 1} disappeared between "
f"snapshot and click; skipping."
)
continue
target_card = current[idx]
Comment thread src/api.py
Comment on lines +534 to +543
# Path 1: full migration from legacy to per-account.
if legacy and not autostartup:
self._safe_log(
"Migrating legacy autostart entry to per-account daily tasks..."
)
try:
self._set_autostart_registry(True)
except Exception as e:
self._safe_log(f"[WARNING] Autostart migration failed: {e}")
else:
Comment thread src/accounts/settings.py
Comment on lines +90 to +92
# OS-level autostart. When True, the app registers a Run entry
# (Windows registry) or a .desktop autostart file (Linux).
"autoStartUp": False,
Comment thread .flake8
@@ -0,0 +1,7 @@
[flake8]
max-line-length = 140
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since max-line-length = 140 is now globally defined in .flake8, we can remove the arguments from .pre-commit-config.yaml and lint.yml to keep things DRY

Comment thread gui/script.js
// this account. Only effective when the global Start-with-Windows
// toggle is on AND this account's schedule is enabled.
const timeDefault = (sched.run_time && /^\d{2}:\d{2}$/.test(sched.run_time)) ? sched.run_time : '09:00';
body.appendChild(make_form_field('Daily run time', 'time', 'schedule-run-time', timeDefault, {}));
Copy link
Copy Markdown
Owner

@safarsin safarsin Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clock icon in the new time input blends with the dark background. Сould you add color-scheme: dark; to the CSS file for input[type="time"] so the icon renders in white?

Image Image

@safarsin
Copy link
Copy Markdown
Owner

safarsin commented Jun 4, 2026

I noticed a slight terminology collision here. We have a "Daily tasks only" toggle in the UI. However, the log message says Autostart enabled (per-account daily tasks registered), which refers to OS scheduled tasks.

This might confuse users into thinking the autostart will exclusively run the daily set and skip the searches. Could you reword the log to something like Autostart enabled (per-account scheduled tasks registered) or daily schedules registered to make it clear?
image

@JeromeM
Copy link
Copy Markdown
Contributor Author

JeromeM commented Jun 5, 2026

Thanks for the review. I'll look into it next week when I'll be back home.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants