Files

184 lines
8.1 KiB
Markdown

# MMM-NextcloudCookbook
A MagicMirror module that displays recipes from a read-only Nextcloud public
share. It includes a phone-friendly control page for choosing a recipe,
adjusting its number of portions, marking ingredients and steps complete, and
entering or leaving recipe mode. Completion marks are shared with the mirror
and retained until a different recipe is selected, so a recipe can be resumed
after exiting recipe mode.
Create a public share for the folder configured as your Nextcloud Cookbook
recipe directory. Read-only permission is sufficient. Copy the public share
link; it should look like `https://cloud.example.test/s/SHARE_TOKEN`.
## MagicMirror configuration
```js
{
module: "MMM-NextcloudCookbook",
position: "bottom_right",
config: {
shareUrl: "https://cloud.example.test/s/SHARE_TOKEN",
controlUrl: "http://mirror.example.test:8080/MMM-NextcloudCookbook/control",
layout: "stacked",
theme: "light"
}
}
```
`layout` controls the full-screen recipe arrangement. Its default value,
`"stacked"`, displays the ingredients above the instructions. Set it to
`"side-by-side"` to display ingredients and instructions in two columns. Both
layouts automatically scale the ingredient and instruction sections to make
the best use of the available screen while keeping all recipe content visible.
The recipe title, timing metadata, yield, and QR controller remain at fixed
sizes.
`theme` defaults to `"dark"`. Set it to `"light"` for a light starting theme;
the controller and recipe overlay also offer a toggle, and the chosen theme is
saved locally. After exiting recipe mode, use **Resume last recipe** in the
controller to restore its portions and completion marks.
Ingredients and instructions can be marked complete from either the phone
controller or a touchscreen on the mirror; both views remain synchronized.
When portions change, the display shows both the selected yield and the
recipe's original yield. Ingredients without a leading quantity are marked in
yellow because they cannot be scaled; unsupported or imprecise numeric scaling
is marked in red.
The configured `position` controls where the QR controller appears during the
normal MagicMirror layout. When a recipe is activated, the module displays a
fixed full-screen overlay regardless of that region. Exiting recipe mode
removes the overlay and returns the QR controller to its configured position.
`controlUrl` is the complete address that should open when someone scans the QR
code. Use the MagicMirror host name or IP address and port that your phone can
reach. The module generates the QR image itself; it does not use an external QR
code service.
`controlUrl` is optional. When omitted, the module attempts to construct it
from the address used to load MagicMirror. Set it explicitly when MagicMirror
runs as an app or service using an address such as `localhost`, or when phones
reach it through a different host name, port, or reverse proxy.
When `controlUrl` is omitted, the module appends its default control path,
`/MMM-NextcloudCookbook/control`, to the detected MagicMirror address. Most
installations do not need to configure `controlPath` separately.
For a password-protected public share, add `sharePassword`:
```js
config: {
shareUrl: "https://cloud.example.test/s/SHARE_TOKEN",
sharePassword: "PUBLIC_SHARE_PASSWORD"
}
```
Do not treat values in MagicMirror's `config.js` as private: someone who can
load the MagicMirror display may be able to inspect them. A read-only public
share URL is normally appropriate because its token is already limited to that
share. Do not put a Nextcloud account password or app password in this
configuration.
To keep even the share token out of the configuration delivered with the
MagicMirror display, set `SECRET_NEXTCLOUD_COOKBOOK_SHARE_URL` and, if needed,
`SECRET_NEXTCLOUD_COOKBOOK_SHARE_PASSWORD` in the MagicMirror process
environment and omit the corresponding config values. Environment values take
precedence over module configuration and are read only by `node_helper.js`. Read
more here: https://docs.magicmirror.builders/configuration/secrets.html
Open the controller at:
```text
http://mirror.example.test:8080/MMM-NextcloudCookbook/control
```
The share must contain Nextcloud Cookbook recipe folders with a `recipe.json`
file in each folder. The module uses Nextcloud's token-scoped public DAV API;
protected shares authenticate as Nextcloud's `anonymous` public-share user.
## Data-source decision: public WebDAV instead of the Cookbook API
Nextcloud Cookbook provides a REST API, but its external API requires
Nextcloud user credentials on every request. A read-only public-share token
cannot authenticate to that API.
This module intentionally reads the recipe files through Nextcloud's public
WebDAV interface because its job is limited to listing and displaying recipes.
That gives the mirror a narrower security boundary:
- access is scoped to the shared recipe folder;
- the share can be read-only;
- the mirror receives no general Nextcloud account credential; and
- the module cannot create, update, or delete recipes.
Cookbook stores recipes as ordinary `recipe.json` files, so WebDAV provides the
data required by this read-only display without relying on Cookbook's database
index. Index synchronization and reindexing matter when an external tool writes
recipe files; this module does not write them.
The Cookbook API would become preferable if the module later needs
Cookbook-native search, categories, keywords, imports, or recipe editing. In
that case, use a dedicated Nextcloud service user with read-only access to the
recipe folder and a dedicated app password rather than credentials for a
person's normal account.
References:
- [Cookbook API documentation](https://nextcloud.github.io/cookbook/dev/api/0.1.0/index.html)
- [Cookbook user documentation](https://nextcloud.github.io/cookbook/user/)
## Future architecture: recipe sources and consumers
The current module both retrieves recipes and presents the full-screen recipe
mode. A future version should separate those responsibilities using
MagicMirror's notification system as a shared recipe data stream:
```text
MMM-NextcloudCookbook ─┐
MMM-Paprika ───────────┼── recipe data notifications ──> MMM-RecipeMode
MMM-Mealime ───────────┘
```
In that model:
- `MMM-NextcloudCookbook` is a source adapter. It reads recipes from Nextcloud
Cookbook and publishes normalized recipe records.
- `MMM-RecipeMode` is a consumer. It provides recipe selection, the phone
control page, portion scaling, QR code, and full-screen cooking display.
- Other source modules can publish recipes from Paprika, Mealime, local files,
or other services without duplicating the presentation code.
- Additional consumers can use the same recipe data for meal planning, search,
shopping lists, dashboards, or other displays.
The shared notification contract should eventually define source-qualified
stable recipe IDs, basic list metadata, complete recipe records, refresh and
error states, and optional source capabilities. The normalized record should
retain schema.org Recipe fields where practical. Exact notification names and
payload schemas should be designed when a second source or consumer is
implemented, rather than treating the current module's private HTTP API as the
cross-module contract.
This separation is a future feature. The current combined behavior should
remain functional until `MMM-RecipeMode` can replace its presentation and
control responsibilities.
### Future presentation option: preserve selected modules
Recipe mode currently hides every other MagicMirror module. A future
`MMM-RecipeMode` configuration should accept an allowlist of modules to leave
visible while cooking. For example, a user could preserve a calendar at the top
of the display while the recipe occupies the remaining area. The option should
support module names and, where multiple instances exist, specific instance
identifiers. Hiding all other modules should remain the default.
Or maybe it should just be up to the mirror admin to specify a pane (most of the screen) which swaps content to recipe mode or not.
## Development
```bash
npm install
npm test
```