Tools provides one central guestbook backend for the public Tools site and trusted external clients such as WordPress.
The public Tools guestbook is available at /guestbook with the tools, miazma, and terminal themes. E-mail addresses and visitor IP addresses are never included in public output.
Public signing on Tools is protected by the existing Cloudflare Turnstile service. If Turnstile is not configured, reading remains available but signing fails closed.
Signed-in users manage their own guestbooks at /guestbooks. A guestbook is owned through guestbooks.owner_user_id; the API key that happened to create an entry is audit metadata and is not the ownership authority.
Creating a guestbook automatically grants the owner the Tools permissions guestbook.manage and guestbook.moderate. Existing owners receive the same permissions when opening the guestbook manager. These user permissions never allow access to another user's guestbook because owner identity is checked separately.
Guestbook slugs remain globally unique, so /guestbook/{slug} always identifies one book without a user id in the URL.
Owners can edit their own name, slug, theme, active/hosted state, copy embed code, inspect their entries, and hide or restore entries belonging to their own guestbooks. Tools administrators retain global administration separately.
Each guestbook can also store optional site context: an HTTP/HTTPS site URL, a short language tag such as sv or en-gb, and a free-text description of the site, audience and expected guestbook content. This metadata belongs to the guestbook and is editable by the owner or a Tools administrator. It is context only: saving a description does not enable AI or automatic moderation. The context is intended to be reusable by later integrations such as WordPress and Moderator profiles, where a statement such as "visitors often rate songs with short comments such as 10/10" can help explain otherwise ambiguous content.
GET /api/guestbook/owned/books requires guestbook.write. It returns only guestbooks whose owner_user_id matches the Tools user that owns the API key. The response contains safe configuration metadata such as id, name, slug, theme, active/hosted state, entry count and optional site context. It also contains can_create, which is true only when the same token also has guestbook.moderate.
POST /api/guestbook/owned/books creates a guestbook for the token user and requires the same API key to have both guestbook.write and guestbook.moderate. The client may provide name, globally unique slug, theme, active/hosted state and site context. A remote client can never choose another owner id. Remote creation also provisions the normal Tools web guestbook permissions for the owner so the book can be managed later from /guestbooks.
This catalog is intended for trusted server-side clients such as Tornevall Tools for WordPress. Tokens must never be exposed to browser JavaScript.
GET /api/guestbook/entries is the global visible Tools feed and requires no token.
GET /api/guestbook/owned/entries requires guestbook.write and returns visible entries owned by the Tools user that owns the API key. It is no longer tied to the exact key id, so rotating a client token does not orphan earlier entries. guestbook_id or guestbook_slug can be supplied to restrict the feed to one guestbook owned by that same user. A foreign guestbook selector returns 404.
This endpoint contains public fields only and is intended for server-side clients such as WordPress that need to render their own guestbook without exposing the Tools token in the browser.
POST /api/guestbook/entries
Requires a Tools API key with guestbook.write. The token must remain server-side and belongs to a Tools user.
A client can select the target with guestbook_id or guestbook_slug. The selected guestbook must have owner_user_id equal to the token user's id. If the user owns exactly one active guestbook and no selector is supplied, Tools selects that book automatically. If multiple active books exist, the request must explicitly select one.
For backwards compatibility, a user with no owned guestbook can still create an unbound legacy client entry. Such historical/unbound rows remain attributed to the token user and are visible after token rotation. source_api_key_id continues to record which credential created the row but is not used as the long-term ownership boundary.
The moderation API requires guestbook.moderate.
GET /api/guestbook/admin/entries returns private moderation data only for entries owned by the same Tools user. guestbook_id or guestbook_slug can restrict the result to one owned guestbook. A token owned by another user cannot use a foreign guestbook selector.
PATCH /api/guestbook/admin/entries/{id}/visibility checks guestbook/user ownership rather than requiring the same historical API key. A client may also send guestbook_id or guestbook_slug; when a selector is supplied, the target entry must belong to that selected guestbook or Tools returns 404. This lets a WordPress installation remain bound to one book even when the same Tools user owns several. A replacement token for the same user can still moderate entries created with an older token.
Tools web administrators remain global administrators through /admin/guestbook.
/admin/guestbook contains a legacy-import panel for restored MySQL databases. Discovery reads both the generic books/messages engine and the explicitly supported standalone tables bbvote, connie, pv, and simhouse.
The normal operator flow is:
bookid/bookhost guestbooks and known standalone tables with row counts.Immediately before an import is submitted, the browser confirmation names both the legacy source and the exact selected destination guestbook. This lets the administrator verify the target one last time before Tools performs any writes.
The web importer no longer creates a destination guestbook automatically. An administrator must select an existing row in guestbooks, and imported entries are linked to it through guestbook_entries.guestbook_id. The legacy import does not overwrite the destination guestbook's own name, slug, ownership, theme, or source metadata.
When a source row cannot be imported, Preview shows a separate skipped-row list. It includes the legacy source, legacy id when available, the concrete skip reason, and available basic metadata. Typical reasons include an empty or undecodable payload, an invalid historical id, or a missing visitor name or message. The same skipped-row information remains available in the result after an actual import.
The historical Miazma guestbook is reconstructed from both relevant legacy sources: the generic messages rows for its book id and the standalone miazma table. messages.messagedata was written by the old engine using PHP serialization. When a message payload references an existing miazma.id, Tools merges those two source rows into one current guestbook entry and retains both source identifiers in legacy metadata. Unmatched messages rows and unmatched miazma rows are imported separately. This keeps the complete history without duplicating rows that represent the same old entry.
For repeated imports, deduplication is identity-based. Generic Miazma overlaps use the explicit historical Miazma id, while each standalone table uses its table name plus original primary key. Preview uses the same identity checks to show which entries would be inserted and which existing imported entries would be updated, without writing anything. If an existing legacy identity is imported into a different explicitly selected destination, the entry can be reattached to the selected guestbook.
Standalone tables are imported through a strict allowlisted adapter for each source. Administrators therefore cannot request an arbitrary database table. bbvote.disabled, connie.borttagen, and simhouse.removed are preserved as visible/hidden state. pv has no equivalent visibility field and is imported as visible history. Other legacy fields such as lastfield, done, smiley flags, and hashes are not treated as moderation signals, but useful values can be preserved as legacy metadata.
No importer executes SQL from the old database. The restored legacy database is read only; Tools writes through the current Laravel models only after an explicit Import action.
Preview and import operations are written to a dedicated structured guestbook audit log separate from the normal Laravel log. Audit metadata includes the action, result, legacy source, destination, and relevant counts. Message text, e-mail addresses, API keys, credentials, and secrets are not stored in import audit metadata.
The existing global public Tools guestbook feed remains backwards compatible. The guestbook identity is used for import, administration, filtering, hosting, and ownership.
The Tools admin interface can filter entries by DNSBL state, check stored source IPs, and explicitly report abuse. Guestbook/web abuse uses IP_ABUSE_NO_SMTP (64).
An explicit report publishes safe source TXT metadata such as entry id, source and source host. Visitor name, e-mail address and message text are never written into DNS TXT records. If bit 64 is already present, Tools can add the moderation TXT annotation without rewriting the existing A listing.
Tornevall Tools for WordPress acts as a server-side guestbook client. Public list, write, and moderation calls are scoped to the Tools user that owns the configured token and can explicitly select one of that user's guestbooks. The browser talks only to local WordPress REST; WordPress proxies the owner-scoped request to Tools from PHP.
A WordPress administrator can use the owned-book catalog to select an existing Tools guestbook. When its token has both guestbook scopes, it can also create a new owned guestbook with the WordPress site's URL, locale and description as initial site context.
Public signing on a WordPress site requires that site's Cloudflare Turnstile site key and secret key. WordPress validates the Turnstile token locally with Siteverify, including the returned hostname and guestbook action, before forwarding the entry to Tools.
The optional Tornevall DNSBL WordPress plugin supplies DNSBL capabilities. Without that plugin there are no DNSBL check/report controls in Tools for WordPress. Blacklist publication also requires that the DNSBL plugin's configured token is active and has add permission.