Skip to content
View in the app

A better way to browse. Learn more.

ernestdefoe.online

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
ernestdefoe.online

Extensions, themes & support for Flarum and Invision Community

Vibe coding for the community web. Report a bug, request a feature, or dig into the source — this is where the tools you use get built, in the open.

We do custom Bespoke Invision Community apps. If you have an idea for something you want then use the contact form to get in touch with us.

Pick'em — support and discussion

Featured Replies

  • Administrators
  • Community Expert

Picks

Floxum

Version

Downloads

Review

License

A Flarum 2.x extension that adds a college-football

pick'em game to your forum. Members predict the winners of each week's games,

earn points, and compete on a season leaderboard. Schedules and teams are synced

from CollegeFootballData (CFBD), and team

logos / live scores come from ESPN.

Features

  • Weekly picks — members pick winners for each game in a week; picks lock at

    kickoff (with an optional offset).

  • Live scoring — a scheduled task polls ESPN for in-progress scores and grades

    picks automatically as games finish.

  • Leaderboard — season standings with per-week history and per-user pick

    history (/u/{username}/picks-history).

  • Confidence mode (optional) — members rank their picks by confidence for

    weighted scoring, with a configurable penalty for missed high-confidence picks.

  • Admin management — sync teams/schedule/scores, open/unlock weeks, enter or

    override results, refresh team logos, and reset data, all from the admin panel.

  • Per-permission access — separate abilities for viewing, making picks,

    viewing history, and managing.

Requirements

Installation


php flarum cache:clear```

Then enable **Picks** in the admin panel under Extensions.

## Setup

1. Open the **Picks** page in the admin panel.
2. Paste your **CFBD API key** and set the **season year** (and optionally a

   conference filter).
3. **Sync Teams**, then **Sync Schedule** to pull the season's games.
4. **Open** the week(s) you want members to pick.
5. Grant the picks permissions to the appropriate groups (see below).

To keep live scores updating, make sure Flarum's scheduler is running (see

**Scheduler** below) and enable **ESPN polling** in the settings.

## Permissions

Set these under **Admin → Permissions**:

| Ability | Lets a user… |

|---|---|

| `picks.view` | See the Picks page and leaderboard. |

| `picks.makePicks` | Submit and change their own picks. |

| `picks.viewHistory` | View pick history for users. |

| `picks.manage` | Manage the game (sync, results, weeks, settings). Admins always have this. |

## Settings

Configured on the admin **Picks** page (stored under the `ernestdefoe-picks.*`

namespace):

* **CFBD API key**, **season year**, **conference filter**
* **Sync regular season / postseason**, **auto-sync**
* **Picks lock offset** (minutes before kickoff)
* **Confidence mode** + **confidence penalty** (`none` / `half` / `full`)
* **Auto-unlock weeks**, **default week view**
* **ESPN polling** + **poll interval**
* **Nav label** (the forum nav link text)

## Scheduler

Live-score polling runs as a scheduled command (`PollLiveScoresCommand`, every 5

minutes). For it to fire, Flarum's scheduler must be invoked once a minute by

cron:

```* * * * * cd /path/to/forum && php flarum schedule:run >> /dev/null 2>&1```

You can also run syncs manually:

```php flarum picks:sync-teams      # sync the FBS team list from CFBD
php flarum picks:poll-scores     # poll ESPN for live scores once```

## Data sources

* **CollegeFootballData (CFBD)** — teams and game schedules (API key required).
* **ESPN** — team logos and live in-game scores (public endpoints, no key).

Team names, logos, and data are the property of their respective owners and the

providers above. This is an unofficial fan tool and is not affiliated with or

endorsed by the NCAA, CFBD, ESPN, or any team.

## Credits

Originally created by [resofire](https://github.com/resofire); now maintained by

[ernestdefoe](https://github.com/ernestdefoe) as `ernestdefoe/picks`.

## Support

Questions, bug reports, and feature requests:

* **Support forum:** https://ernestdefoe.online
* **Issues:** https://github.com/ernestdefoe/picks/issues

## License

MIT
  • Replies 9
  • Views 45
  • Created
  • Last Reply

Top Posters In This Topic

  • Author
  • Administrators
  • Community Expert

picks 2.0.3

What's Changed

  • Add author email to composer.json
  • ci: add support-site release announcer (publish-to-flarum)
  • docs: add Floxum badges to README
  • docs: add support site (ernestdefoe.online) to README

Full Changelog: https://github.com/ernestdefoe/picks/compare/2.0.2...2.0.3

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.0.4

What's Changed

Maintenance release — a full internal code audit. No user-facing feature changes; one scoring bug fixed and broad performance/quality improvements under the hood.

Fixed

  • Test-data scoring with confidence mode. The admin Testing tab's seeder computed point totals with a duplicate routine that ignored confidence_mode/confidence_penalty, so seeded leaderboards didn't match live scoring. Scoring now runs through a single shared ScoreAggregator used by both live scoring and the seeder.
  • Testing-tab buttons. The seed/clean/wipe endpoints were never routed and returned 404; they now work.

Performance

  • Admin stats no longer fire 260+ queries on a full FBS season — upset rate and consensus are computed from one aggregated pick-count query.
  • Profile history no longer fires 30+ rank-count queries — week/season/all-time ranks are batch-loaded and ranked in PHP.

Internal

  • All raw database access migrated from Capsule DB::table() to Eloquent across 7 controllers and the seeder.
  • Logo storage now writes through Flarum's asset filesystem disk (cloud/CDN-friendly) instead of native filesystem calls.
  • CFBD API and logo downloads use the Guzzle HTTP client instead of raw curl.
  • Exceptions in the leaderboard/history/scores endpoints are now logged instead of silently swallowed.
  • Settings, current-season lookup, and service dependencies are injected rather than resolved ad hoc.

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.0.5

What's Changed

A maintenance release — performance, correctness, and convention fixes under the hood. No user-facing feature changes.

Fixed

  • Leaderboard totals can no longer be corrupted by a race. When two score updates for the same user ran at the same time, both could insert a duplicate all-time score row (the all-time scope isn't covered by the unique index, since SQL treats NULL != NULL). Score writes are now serialized per user and scope, so the duplicate can't happen.

Performance

  • Live scoring marks a game's picks correct/incorrect in two batched database updates instead of one update per pick — a popular game with hundreds of picks no longer fires hundreds of separate writes.
  • Admin test-data seeding loads existing picks in a single query instead of one existence check per user-per-game (which previously ran tens of thousands of queries and could time out on a real data set).
  • ESPN score sync now goes through the shared Guzzle HTTP client (honouring the host's proxy/SSL config) instead of a raw curl handle.

Internal

  • Migrations now use Flarum's createTableIfNotExists() / addColumns() helpers consistently.
  • Removed redundant manual wasRecentlyCreated bookkeeping in the schedule sync, relying on Eloquent's own value.

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.0.6

What's Changed

Fixed

  • Route id handling for admin/user actions. Result entry, week open/close, pick deletion, and individual team-logo refresh now read the {id} path segment via $request->getAttribute('routeParameters') — the canonical Flarum 2 source for route parameters.
  • Leaderboard movement arrows. previous_rank was freezing at a user's first-ever rank, so the up/down arrows showed stale deltas. A new current_rank column now lets each scoring pass roll the prior rank into previous_rank, so the arrows reflect the real change since the last scoring.

Performance

  • Live scoring writes rank movement in one batched database statement per scope instead of one update per player (a popular game previously fired hundreds of updates).
  • The public stats widget resolves all referenced teams in a single query instead of up to ten lookups per request.
  • Profile history loads a user's season scores in one query instead of one per season.

Security / packaging

  • The admin "Testing" data-seeding tools are now gated behind debug mode, so they aren't a permanent admin API on production installs. The seeding engine was extracted into its own service and made more memory-efficient.
  • composer.json now explicitly requires guzzlehttp/guzzle and intervention/image (previously relied on transitively).

Internal

  • Split the profile-history controller into focused builder methods.

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.0.7

What's Changed

A maintenance release — query-performance, robustness, and code-structure fixes. No user-facing feature changes.

Performance

  • Schedule sync builds the team lookup once per run instead of re-querying it for every week.
  • The leaderboard-history and profile-history endpoints load all seasons/weeks in batched queries instead of one query per season.
  • The public stats endpoint caches a database-schema check (1 hour) instead of probing it on every request.

Fixed

  • The user-scores and leaderboard-context endpoints now return a proper 500 error on an unexpected failure, so a real error is distinguishable from the normal "no picks yet" state.
  • The current_rank score column is now declared on the model, so it's read as an integer (no manual casting).

Internal

  • Removed redundant service-container bindings in favour of automatic injection.
  • Split the large /picks page component into a shared state store plus four focused tab components (Matches / My Picks / Leaderboard / History), keeping behaviour identical.

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.1.0

What's Changed

A performance + robustness release addressing an 11-item code audit. No user-facing pick/leaderboard behaviour changes.

Performance

  • Score sync preloads games and pick-existence in bulk instead of two queries per game — a full season went from ~1,800 queries to a couple.
  • Live ESPN polling (every 5 minutes) now bulk-loads the scoreboard's games in one query rather than one lookup per game.
  • Profile stat cards compute each scope's rank and player total in memory from a single load per scope, instead of a separate count query per scope.
  • Public stats resolve all followed-team logos in one query and count scored picks once (previously the same count ran twice).
  • Admin stats resolve the top contested games' teams in a single batched query.

Changed

  • Score sync now runs in the background. Clicking "Sync Scores" dispatches a queued job and returns immediately; the admin UI polls for completion and shows the result. Previously the multi-week fetch ran synchronously and could exceed PHP's execution limit on a full season, leaving scores half-synced. (For true async, configure a real queue driver — redis/database — in config.php.)
  • Test-data seeding bulk-inserts picks in chunks instead of one row at a time, so seeding a full fake season no longer times out on shared hosting.

Fixed

  • Confidence values are now ignored when confidence mode is disabled and range-checked only when enabled.
  • Logo conversion/save failures are written to the log instead of failing silently, so missing team logos can be diagnosed.

Internal

  • Removed redundant service-container bindings in favour of automatic injection.

View release on GitHub

  • Author
  • Administrators
  • Community Expert

picks 2.1.1

What's Changed

A performance + robustness release addressing an 8-item code audit. No user-facing pick/leaderboard behaviour changes.

Performance

  • Schedule sync preloads every existing game in one query instead of a lookup per game - a full 17-week FBS season went from ~1,500 individual SELECTs down to one.
  • Live ESPN polling now pre-builds the set of games that already have picks, replacing a per-game existence query on game-day Saturdays.
  • Score aggregation sums each user's totals (points, earned/penalty, accuracy) directly in SQL instead of loading every scored pick into memory - heavy multi-season users no longer pull hundreds of thousands of rows per scoring job.
  • Profile history computes a user's longest streak with a single window-function query rather than walking every pick in PHP.
  • Week pick list reuses the week record already eager-loaded on each game instead of a separate lookup.

Internal

  • Extracted the duplicated leaderboard rank helper into one shared UserScore::rankIn() method.
  • Score-sync uses the imported Week class name consistently; removed now-unused imports.

Fixed

  • A failed confidence change in the pick UI now rolls back the optimistic update instead of leaving the wrong confidence value on screen.

View release on GitHub

  • 3 weeks later...
  • Administrators
  • Community Expert

picks 2.1.3

What's Changed

  • Fix 5 Floxum audit findings (memory, PG portability, caching, dedup, N+1)
  • Make event scores read-only on EventResource (close audit finding #6)
  • Portable boolean SQL in ScoreAggregator (is_correct = true/false)

Full Changelog: https://github.com/ernestdefoe/picks/compare/2.1.2...2.1.3

View release on GitHub

Guest
Reply to this topic...

Want to reply?

Ask a question about any of these applications and it will be answered by the person who wrote them. It takes about thirty seconds, and you keep what you have already typed.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.