
A lightweight C++17 library + CLI to curate a “Top 100” movies list. It persists to JSON, fetches details from OMDb over HTTPS, and lets you rank films via quick pairwise comparisons using an Elo-like system.
✨ Features
- 📦 Library-first design (
lib/top100.*
) so a future GUI can reuse it
- 💾 JSON persistence using
nlohmann/json
- 🔎 OMDb integration (title search and add-by-ID) over HTTPS via
cpr
- 🧠 Duplicate handling with policy (prompt/overwrite/skip)
- ⭐ Ratings support: IMDb, Metascore, Rotten Tomatoes
- 🏅 Personal ranking: Elo-style
userScore
and 1-based userRank
- 📋 CLI to add/remove/list/details/compare
- ✅ Unit tests with Boost.Test
- 🔧 Per-user config at
~/.top100_config.json
(data file path, OMDb enable, API key) with menu that adapts to settings
- 📁 Safe persistence: creates parent directories for your chosen data file automatically
- 📝 Plot handling: stores both short and full plots (details view uses full; social posts use short)
- 📣 Social sharing: post a selected movie to BlueSky and Mastodon (uploads poster image when available)
- 🧩 Post customization: configurable header/footer text included in social posts
🧭 Project layout
lib/
Movie.h # Movie model + JSON (de)serialization
top100.h/.cpp # Core list persistence and sorting
omdb.h/.cpp # OMDb HTTP integration
bluesky.h/.cpp # BlueSky client (session, image upload, create post)
mastodon.h/.cpp # Mastodon client (verify, upload media, post status)
cli/
main.cpp # CLI entry
displaymenu.cpp # Menu
addmovie.cpp # Manual add
removemovie.cpp # Remove
listmovies.cpp # List & sort (incl. rank/score)
viewdetails.cpp # Details view
addfromomdb.cpp # OMDb search + add
comparemovies.* # Pairwise ranking loop (Elo)
dup_policy.h # Duplicate handling policy (env-driven)
lib/
config.h/.cpp # Per-user config: data path; OMDb; BlueSky; Mastodon; post header/footer
config_utils.* # High-level helpers (set data path, configure/disable OMDb)
ui/
common/
strings.h # Shared UI strings (window titles, labels, menu text)
constants.h # Shared UI constants (spacing, poster ratios)
Top100ListModel.h/.cpp # Shared model used by Qt/KDE frontends
qt/
app.h/.cpp # Qt application/bootstrap
window.h/.cpp # Main window, two‑pane layout, status bar
adddialog.h/.cpp # Rich Add Movie (OMDb) dialog
toolbar.h/.cpp # Toolbar actions (Add/Delete/Refresh/Update/Post)
menu.h/.cpp # Menu bar wiring
poster.h/.cpp # Poster fetch/scale helpers
gtk/
app.h/.cpp # GTK application/bootstrap (gtkmm 3)
window.h/.cpp # Main window, two‑pane layout, status bar
adddialog.h/.cpp # Rich Add Movie (OMDb) dialog
toolbar.h/.cpp # Toolbar actions
menu.h/.cpp # Menu bar wiring
poster.h/.cpp # Poster fetch/scale helpers
kde/
main.cpp # QML/Kirigami bootstrap
qml/Main.qml # Kirigami UI (two‑pane, footer count, Add dialog)
tests/
test_*.cpp # Granular unit tests (incl. ranking)
CMakeLists.txt # Build and test wiring
top100.json # Example data file (actual location is configurable)
🏗️ Build
Prereqs: a C++17 compiler, CMake 3.11+, and OpenSSL dev headers (for HTTPS via cpr
).
Preferred (presets + nested build dirs):
# Configure into build/linux using Ninja
cmake --preset linux
# Build
cmake --build --preset linux --parallel
# Run tests
ctest --preset linux --output-on-failure
# Run the CLI
./build/linux/top100_cli
If you use VS Code, this repo includes .vscode/tasks.json
with handy tasks. In particular, “Clean & Rebuild (UIs + Tests)” will:
- Remove the
build/
folder
- Reconfigure with
-DTOP100_UI_QT=ON -DTOP100_UI_KDE=ON -DTOP100_ENABLE_TESTS=ON
- Build everything and run the full test suite
You can run it via Terminal > Run Task… (or Ctrl/Cmd+Shift+P → “Run Task”).
🖼️ Optional UI frontends
In addition to the CLI, you can build optional desktop UIs:
- Qt (cross‑platform) UI — Widgets‑based, no KDE dependencies
- GTK UI — gtkmm 3 (GNOME‑friendly) with the same two‑pane layout and Add dialog
- KDE UI — Kirigami/QML frontend with KDE Frameworks integration
- Haiku UI — BeAPI-based minimal frontend (experimental)
All UIs are disabled by default. Enable with CMake options:
-DTOP100_UI_QT=ON
to build top100_qt
-DTOP100_UI_GTK=ON
to build top100_gtk
-DTOP100_UI_KDE=ON
to build top100_kde
-DTOP100_UI_HAIKU=ON
to build top100_haiku
(only on Haiku OS; otherwise skipped)
Dependencies:
- Qt UI: Qt 5 (Widgets) or Qt 6 (Widgets)
- GTK UI: gtkmm 3.0 (via pkg‑config)
- KDE UI: Qt Quick/QML (5 or 6) and Kirigami2 from KDE Frameworks (KF5 or KF6)
Example build (out-of-tree, manual):
# Qt UI only (manual configure dir)
cmake -S . -B build/linux -DTOP100_UI_QT=ON
cmake --build build/linux --target top100_qt
# KDE UI (Kirigami)
cmake -S . -B build/linux -DTOP100_UI_KDE=ON
cmake --build build/linux --target top100_kde
# GTK UI (gtkmm 3)
cmake -S . -B build/linux -DTOP100_UI_GTK=ON
cmake --build build/linux --target top100_gtk
# Haiku UI (BeAPI) — on Haiku OS
cmake -S . -B build/haiku -DTOP100_UI_HAIKU=ON
cmake --build build/haiku --target top100_haiku
If both are enabled, both executables are built. The UIs link the same core libraries; we’ll progressively wire features without duplicating logic.
Consistent UI strings and behavior:
- Both UIs read common display text (app names, window titles, hello text) from
ui/common/strings.h
so wording stays in sync.
- A unit test named
ui_strings_constants
verifies these constants.
- The Add dialog UX is kept in parity across Qt/GTK/KDE: a larger centered window titled “Add Movie (OMDb)” with a search row (label, entry with placeholder “title keyword”, and Search button), results list on the left, and details (title/year/poster/plot) on the right. Bottom row has Enter manually, Cancel, Add (Add is default).
�🖥️ CLI usage
By default, the first run creates ~/.top100_config.json
and stores your data at ~/top100/top100.json
(the folder is created if needed). You can change the data file path from the menu at any time. On startup, ranks are recomputed from scores to keep things consistent.
Common actions:
- Add a movie manually
- Remove a movie
- List movies (by default, by year, alphabetically, by rank, or by score)
- Add from OMDb (search, pick, and add) — shown only when OMDb is enabled
- View details of a selected movie
- Compare two random movies repeatedly to evolve your ranking (press
q
to stop)
- Post a movie to BlueSky (configure once, then post)
- Post a movie to Mastodon (configure once, then post)
- Edit social post header/footer text
🌐 OMDb integration
- HTTPS is enabled. Requests use
https://www.omdbapi.com/
via cpr
.
- OMDb is controlled by your per-user config. Use the CLI to enable/disable and set your API key.
- When enabling OMDb, the CLI verifies your key with a lightweight request and persists it if valid.
Dynamic menu items:
- When OMDb is disabled: you’ll see “Configure OMDb API key” and “Set data file path”.
- When OMDb is enabled: you’ll see “Search and add from OMDb” and “Disable OMDb”.
Search flow (Add from OMDb
): 1) Enter a title query 2) Pick a result to add 3) The app fetches full details by IMDb ID and saves the movie with source = "omdb"
Plot length handling:
- When retrieving details by IMDb ID, both the full plot and the short plot are fetched and stored.
- The Details screen shows the full plot when available; social posts use the short plot (falling back to full if needed).
📣 Social posting
You can share a selected movie to BlueSky and Mastodon from the CLI. Posts include your configurable header/footer, the title/year, key credits, your personal ranking line, IMDb’s rating, a link to the IMDb page, and the poster image if available.
BlueSky:
- Configure once with your BlueSky identifier (handle or DID), app password, and service URL (default
https://bsky.social
).
- Posting uploads the poster as a blob and creates a post with the composed text and image.
Mastodon:
- Configure once with your instance base URL (default
https://mastodon.social
) and access token.
- Posting uploads the poster via multipart media, then posts the status with the attached image.
Post customization:
- Header text default: “I’d like to share one of my top 100 #movies!”
- Footer text default: “Posted with Top 100!”
- You can edit both from the CLI (they may be empty to omit).
Character limits (auto-fit behavior):
- BlueSky: 300 characters; Mastodon: typically 500 characters.
- The app automatically fits the post using this rule: 1) If the full post (including footer) fits, post it as-is. 2) Otherwise, remove the footer and try again. 3) If still too long, keep the footer and truncate only the plot, appending “...”, until it fits.
- Truncation is Unicode-safe (doesn’t split emojis or multibyte characters).
Example (BlueSky, auto-fit under 300 chars):
I’d like to share one of my top 100 #movies!
🎬 The Matrix (1999)
🎥 Director: Lana Wachowski, Lilly Wachowski
Plot: A hacker learns reality is a simulation and must fight to free humanity...
⭐ My ranking:
⭐ IMDb ranking: 8.7/10
🔗 https://www.imdb.com/title/tt0133093/
Posted with Top 100!
🧩 Duplicate policy
When adding from OMDb, duplicates are detected by imdbID
(preferred) or title+year
. Control behavior via environment variable TOP100_DUPLICATE_POLICY
:
prompt
(default) — Ask before overwriting
prefer_omdb
or omdb
— Overwrite automatically
prefer_manual
or manual
— Keep manual entry automatically
skip
— Never overwrite
Example:
export TOP100_DUPLICATE_POLICY=prefer_omdb
./build/top100_cli
🏅 Ranking (Elo-like)
- Each movie has
userScore
(default 1500.0) and userRank
(-1 until ranked).
- Use the CLI option “Compare two movies (rank)” to see two random movies repeatedly and choose a winner. The Elo update moves the scores. After each comparison the app recomputes ranks so
1
is the highest score.
- You can list by rank or by score to see your evolving Top 100.
Notes:
- Ties are broken consistently by title when sorting by score.
- Ranking state is persisted in
top100.json
.
🧪 Tests
This project uses Boost.Test and registers individual test cases with CTest. Highlights include:
- Core: add/remove/save/load
- Sorting: by year, alphabetical
- Movie JSON: round-trip including ratings and new fields (incl. short/full plot)
- Find/replace helpers
- Ranking: JSON fields, recompute ordering, deterministic Elo update
- Config: default creation, load/save round trip, and high-level utilities (incl. BlueSky/Mastodon and header/footer defaults)
- Menu: dynamic items based on OMDb enabled/disabled, BlueSky, Mastodon, and the header/footer editor
Run with:
cd build && ctest --output-on-failure
# or only ranking-related
cd build && ctest -R ranking_ -V
⚙️ Configuration
Top100 keeps per-user settings in ~/.top100_config.json
and shows a menu that adapts to your choices.
Fields:
dataFile
— full path to your JSON data file
omdbEnabled
— show/hide OMDb features
omdbApiKey
— your OMDb API key
blueSkyEnabled
, blueSkyIdentifier
, blueSkyAppPassword
, blueSkyService
— BlueSky settings (service default https://bsky.social
)
mastodonEnabled
, mastodonInstance
, mastodonAccessToken
— Mastodon settings (instance default https://mastodon.social
)
postHeaderText
, postFooterText
— customizable text included in social posts
Defaults on first run:
dataFile
: ~/top100/top100.json
(its parent directories are created automatically)
omdbEnabled
: false
, omdbApiKey
: empty
blueSkyEnabled
: false
, blueSkyService
: https://bsky.social
mastodonEnabled
: false
, mastodonInstance
: https://mastodon.social
postHeaderText
: “I’d like to share one of my top 100 #movies!”, postFooterText
: “Posted with Top 100!”
From the CLI you can:
- Configure OMDb API key (verifies the key and enables OMDb if valid)
- Disable OMDb (clears the key and hides OMDb menu options)
- Set data file path (updates the config and ensures directories exist)
- Configure BlueSky and post a movie
- Configure Mastodon and post a movie
- Edit the header/footer text for posts
Environment overrides:
TOP100_CONFIG_PATH
— path to an alternate config file (useful for testing or multiple profiles)
TOP100_DUPLICATE_POLICY
— duplicate handling policy (see section above)
Schema example:
{
"dataFile": "/home/you/top100/top100.json",
"omdbEnabled": false,
"omdbApiKey": "",
"blueSkyEnabled": false,
"blueSkyIdentifier": "",
"blueSkyAppPassword": "",
"blueSkyService": "https://bsky.social",
"postHeaderText": "I’d like to share one of my top 100 #movies!",
"postFooterText": "Posted with Top 100!",
"mastodonEnabled": false,
"mastodonInstance": "https://mastodon.social",
"mastodonAccessToken": ""
}
Precedence notes:
- Exactly one config file is active per run. If
TOP100_CONFIG_PATH
is set, that file is used; otherwise ~/.top100_config.json
.
- No merging across files. Changing
TOP100_CONFIG_PATH
switches the entire profile.
Using multiple configs with TOP100_CONFIG_PATH
You can keep separate profiles (e.g., home/work/testing) by pointing the app at a different config file.
Examples:
# One-off run with a custom config
TOP100_CONFIG_PATH=~/top100/dev_config.json ./build/top100_cli
# Use a temp config for testing
TOP100_CONFIG_PATH=$(mktemp) ./build/top100_cli
# Persistent shell session using an alternate profile
export TOP100_CONFIG_PATH=~/top100/work_profile.json
./build/top100_cli
Notes:
- If the file doesn’t exist, the app will create it with default values.
- This only changes where settings are stored; your movie data file location is still controlled by the
dataFile
setting inside the chosen config.
🐛 Troubleshooting
- Build fails finding OpenSSL: install development headers (e.g.,
libssl-dev
) and reconfigure.
- HTTPS issues at runtime: ensure your system CA bundle is present and accessible;
cpr
uses libcurl + OpenSSL.
- Tests not discovered: ensure you configured and built from the project root with CMake and that you run
ctest
from the build
folder.
- Default data path not writable: use “Set data file path” from the menu to choose a path you own; the app creates missing directories automatically.
- Want to reset settings: delete
~/.top100_config.json
(a fresh default will be created on next run).
- OMDb key rejected: re-run “Configure OMDb API key” and double-check the key; you can also disable OMDb from the menu.
- BlueSky login fails: ensure you’re using an app password and that the service URL matches your instance (
https://bsky.social
by default).
- Mastodon media upload fails: some instances reject very large images; try a smaller poster or ensure your token has write permissions.
📄 License
This project is licensed under the Apache License, Version 2.0. See LICENSE
for details.
Third-party components are used under their respective licenses; see THIRD-PARTY-NOTICES.md
for an overview and links, and NOTICE
for attributions.
📚 Developer docs (Doxygen)
You can generate API documentation with Doxygen; it will be written to the docs/
folder.
- Prerequisites: Doxygen (and Graphviz for call/caller graphs).
- Run the docs build via the CMake target named
docs
.
- Open
docs/html/index.html
in your browser when done. XML output is also generated at docs/xml
for possible Sphinx/Breathe integration later.
Online docs:
Scope and notes:
- The docs focus on public headers in
lib/
plus key CLI entry points in cli/
.
- Tests are excluded from the generated documentation to keep the API surface clear.
🚀 Branching, releases, and docs
- Branch strategy:
main
: stable; always releasable
develop
: integration branch for completed features
feature/*
: short-lived branches for focused changes; merge into develop
- Releases:
- Create a tag like
vX.Y.Z
on main
to trigger a GitHub Release build.
- The CI bundles
top100_cli
(Linux) with README.md
into top100_cli-linux.tar.gz
attached to the release.
- Docs:
- On pushes to
main
, CI builds Doxygen and publishes docs/html
to GitHub Pages.
- After enabling Pages for this repository (Source: GitHub Actions), the site will auto-update on merges to
main
.