Commit Graph
35 Commits
Author SHA1 Message Date
tim 9196dd75a8 Refuse OPDS logins when CWA accepts every password
A CWA with anonymous browsing can answer an OPDS request whatever the
password, so each password logged a user in to Bindery. Bindery now
tests CWA with a login that cannot exist, and refuses OPDS logins if
CWA accepts it. A login check no longer shares the session of the
account of Bindery. The CWA account stays optional for a CWA that asks
for no login, and a missing account now gets its own message.
2026-10-02 23:37:33 +02:00
tim 869e56eaf0 Do not make the ingest folder
A folder that Bindery made hid a missing volume mount. The books went
to a folder inside the container, and CWA never saw them. A missing
folder is now an error in the log at the start, and each upload fails
with a message that names the mount.
2026-10-02 23:37:33 +02:00
tim 679c1ede9f Serve a download from the built book, and delete stale uploads
A download built the book again each time. It now sends the kept build,
and builds only if the book changed after it. Stop tracking deletes the
built book. A cancelled import deletes its uploaded file, and the start
deletes the uploads that no job needs.
2026-10-02 23:37:33 +02:00
tim ff2fa0bf90 Keep a move or a delete of chapters that the user makes during a job
A job placed each new chapter with the chapter order from its start, so
a move by hand during the job went back. The store now places a new
chapter against the order that the story has at that moment, in one
transaction. A recheck of a chapter that the user deleted no longer
stops the job.
2026-10-02 23:37:33 +02:00
tim 81efc03b75 Keep the job thread alive after an error of the queue
An error outside a job, such as a full disk, stopped the thread, and no
job ran until a restart. The health check now answers 503 when a job
thread stopped.
2026-10-02 23:37:33 +02:00
tim 9c9c5b7dac Load a job record that holds unknown fields
A downgrade of Bindery no longer stops at the start.
2026-10-02 23:37:33 +02:00
tim 98c4a66da1 Answer 400 for a request value of the wrong type
A value such as chapter_ids that is not a list of numbers gave a 500.
2026-10-02 23:37:33 +02:00
tim 90457610f6 Wait only 5 seconds for CWA on the story page
A job still waits for the full timeout.
2026-10-02 23:37:33 +02:00
tim e196d1688e Keep server details out of API answers
An unexpected error now sends a general message, and the log keeps the
text. A job no longer shows the path of its uploaded file. A cover is
cached only by the browser of the user.
2026-10-02 23:37:33 +02:00
tim 0edfa1308c Stop the start with a clear message for a bad setting
A bad UPDATE_INTERVAL_HOURS or LOG_LEVEL no longer ends in a traceback.
Warn about a CWA_PASSWORD without a CWA_USERNAME.
2026-10-02 23:37:33 +02:00
tim 0506086533 Check every setting before the first one is stored
A bad value in a settings request no longer leaves the other values of
that request saved.
2026-10-02 23:37:33 +02:00
tim 7a36e26751 Build and send a book only when it changed
A key of the chapters, rules and metadata of a story tells if its book
changed. A check without a change no longer builds the book again, also
for a book that waits for an upload. A held book goes out without a new
build. The Rebuild button still builds and sends in each case.
2026-10-02 23:37:33 +02:00
tim f961e4781e Warn when the ingest folder has no library to match
Without CWA_URL and CALIBRE_DB_PATH, each update goes to CWA as a new book.
2026-10-02 21:04:49 +02:00
tim c188795852 Make CWA linking and uploads optional 2026-10-02 20:25:45 +02:00
tim 34ff432aac Remember story sorting in local storage 2026-10-01 16:08:46 +02:00
tim e4a17029c1 Sort stories through column headings 2026-10-01 16:08:42 +02:00
tim b536a1f116 Show story change times and compact list details 2026-10-01 15:42:48 +02:00
tim 8faab2463b Move and delete chapters by hand in the chapter list
The merge cannot always tell where new chapters go. For example, a site
can list an announcement before the chapters of an old book. You can now
select chapters and move them, in their order, to after another chapter
or to the start. You can also delete them with their revisions. A story
keeps at least one chapter. The book is then rebuilt. A deleted chapter
that the site still lists comes back with the next download.

The Move… and Delete… buttons open a dialog that names the selected
chapters before anything changes. A shift-click selects a range of
chapters.

The Select all checkbox moves into the first heading of the chapter
list. On a phone, that heading shows with the text "Select all".
2026-09-27 23:38:28 +02:00
tim ee611cd283 Sort the story list without a leading The, A or An 2026-09-27 23:38:28 +02:00
tim df9959a078 Add a script that cleans up smart punctuation in chapter revisions
Some revisions differ from others only by curly quotes, curly
apostrophes and the ellipsis. The script deletes those revisions and
keeps the one with the least smart punctuation. In revisions with other
text changes, it makes the punctuation straight and stores the new text
hash. It does not change the newest revision if it came from the site.

The script uses only the Python standard library, so you can run it on
the host against bindery.db. It is a dry run unless you give --apply,
and --apply makes a backup first.
2026-09-27 23:38:28 +02:00
tim 5df2806694 Add a script that fills a gap in a story from an ePub without chapter links
A FanFicFare ePub records the site address of each chapter, so Bindery
can import it. Other ePubs, such as the author's own ebook of a story,
have no chapter links. The script asks which stored chapters come before
and after the gap, and which ePub chapters match them. It suggests
matches by title and puts the suggestion on the input line. It then adds
the ePub chapters between the matches, with their images, in the order
of the ePub.

Each added chapter gets a stable URL with the #bindery-import- marker
and the revision source "epub-import". A second run with the same ePub
does not add the same chapters again. --remove deletes the chapters that
the script added.
2026-09-27 23:38:28 +02:00
tim cc0f070661 Limit and configure how many jobs the Jobs panels show 2026-09-27 23:38:28 +02:00
tim a7a89b145c Grow the Add stories field to fit its URLs 2026-09-27 23:38:28 +02:00
tim ce19035689 Show a revised-chapters count on the story list 2026-09-27 23:38:28 +02:00
tim 1246816ab8 Open the epub import card and load a dropped file 2026-09-27 23:38:28 +02:00
tim 03ed1a69cb Stack the story and chapter lists on a phone instead of scrolling sideways
Each table had six columns, which squeezed down to unreadable widths on a
narrow screen and forced horizontal scrolling.

Below 600px, each row becomes a bordered card. The main value (the story
or chapter title) stays unlabelled at the top, and every other value gets
a small label in front of it, taken from its column heading.

The chapter table's "select all" checkbox lived in the table header,
which mobile now hides in favour of the per-row labels. It moves into
the sticky actions bar above the list instead, next to the buttons that
already act on the selection.
2026-09-27 23:38:28 +02:00
tim 407f67622b Update Chapters list to make modifying a chapter take less scrolling. 2026-09-27 23:38:28 +02:00
tim 5ef68fb240 Make the metadata fields readable on a phone
Each field was a row of a four-column table: name, lock, value and the
site's current value. On a narrow screen the columns squeezed the
inputs down to a few characters wide.

Each field is now a card with its name and lock on one line and the
input below at full width. The site's value shows only when it
differs, folded behind a line that opens on tap, instead of always
taking its own column. The lock is a labelled toggle instead of a
bare checkbox, and Save stays disabled until something changes.

On a wide screen, the label sits beside the field instead of above
it, and a single-line input no longer stretches to fill the card.
2026-09-27 23:38:28 +02:00
tim 1a10fe53f3 Add single-user local authentication 2026-09-27 23:38:28 +02:00
tim 2f4271314b Add the web interface
Plain HTML, CSS and JavaScript modules, with no build step. Hash routes
and relative URLs make it work under any path. The job list is polled:
every 2 seconds while a job runs, and less often otherwise.
2026-09-27 23:38:28 +02:00
tim 09d6b76a17 Add the JSON API and logins
Logins follow shelfmark: none, CWA accounts, or a reverse proxy header.
CWA accounts are checked against the OPDS feed, with a lockout after
repeated wrong passwords. An API key works in every mode, and changes
need an X-Requested-With header or the key. URL_BASE serves the app
under a path prefix.
2026-09-27 23:38:28 +02:00
tim 49cd18a09f Run download jobs and deliver books to CWA
Jobs download new chapters, recheck chapters, replace the book, update
metadata or rebuild from stored chapters. Locked metadata fields keep
their values. A scheduler queues updates. An update finds the library
book of a story through CWA, and fails with an error if it cannot, so
that it never makes a duplicate book.
2026-09-27 23:38:28 +02:00
tim 312dc3efc5 Find books through CWA's OPDS feed and use its ingest folder
Bindery mounts no CWA database. It searches the OPDS feed by title and
reads the story URL inside each candidate epub to find the library book.
Updates go to the ingest folder with a .cwa.json sidecar, so CWA replaces
the epub of the existing book.
2026-09-27 23:38:28 +02:00
tim c30622e3a0 Download stories and build epubs with FanFicFare
FanFicFare adapters fetch metadata and chapters, and its epub writer
builds each book from the store. Environment variables configure the
app, and a default personal.ini is copied to the config folder on first
start.
2026-09-27 23:38:28 +02:00
tim 6c2574cf0f Store chapters, merge chapter lists, diff and apply rules
The SQLite store keeps every chapter revision, so a chapter that the site
removes stays in the book. The merge puts new chapters after the chapter
that comes before them on the site. Diffs compare text, not HTML, and
replacement rules run on chapter HTML when a book is built.
2026-09-27 21:23:20 +02:00