Publish snippets and Markdown posts through authenticated uploads

This commit is contained in:
2026-09-19 15:34:17 +03:30
parent 17f1a8ef39
commit e42646e09e
14 changed files with 553 additions and 15 deletions
+106
View File
@@ -155,6 +155,112 @@ The token is visible in the shortcut editor; remove it before sharing the shortc
Rotate it by replacing the token file and recreating the upload container with
`podman compose up -d --force-recreate upload`.
## Snippets shortcut
The same service and token also publish to `https://theread.me/snippets/`.
Text stays literal (including line breaks and Persian text); standalone HTTP/HTTPS
URL lines become clickable links. Images use the same optimization and metadata
removal as photo uploads. Each submission is prepended below the introduction,
with a separator from the previous snippet. Existing snippets are preserved.
Create **Publish snippet**:
1. Enable **Show in Share Sheet**, accepting **Text**, **URLs**, and **Images**.
2. **Repeat with Each** item in **Shortcut Input**. Use **Get Type** on the repeat
item. If it is **Image**, add the item to a variable called **Snippet Images**.
Otherwise use **Get Text from Input** on the item and add its result to
**Snippet Text**. These variables accumulate the shared items during this run.
3. **Combine Text** from **Snippet Text**, separated by **New Lines**. For an
image-only share, leave the text empty. Don't fetch the contents of shared URLs:
send the link itself.
4. **Ask for Input** (Text): “Note (optional)”. Keep its output separate from the
shared text. You can also run the shortcut without shared input and use a text
prompt as **Snippet Text** to write a new snippet.
5. **Get Contents of URL**: `https://theread.me/api/snippets`, method **POST**,
request body **Form**:
- Header `Authorization`: `Bearer YOUR_TOKEN` (same token as photos).
- `text`: **Text**, the combined shared text.
- `caption`: **Text**, the optional note.
- `photos`: **File**, **Snippet Images**, when sharing images. For text-only
requests, omit this field. Use an **If** on whether there are images to choose
between the form with `photos` and the text-only form.
- Delete any blank header rows. Do not manually set `Content-Type`.
6. **Show Result** using the actual response. A response with `error` means the
upload failed; don't show a fixed success message. `queued` means accepted;
publishing happens afterwards. The returned `status_url` can be polled with the
same authorization header; `published` confirms deployment.
For the simplest initial shortcut, accept just Text and URLs and omit the image
branch and `photos` field; add the image branch when that works on your phone.
`POST /api/snippets` accepts up to 20,000 characters of `text`, an optional
4,000-character `caption`, and up to 20 images in repeated `photos` parts, within
the existing 100 MiB request limit. At least text or an image is required.
It returns `id`, `count` (image count), `url`, `status_url`, and `status`.
`GET /api/snippets/<id>` checks publication, and
`POST /api/snippets/<id>/retry` retries failed Git publication. Identical text,
note, and original images in the same order are deduplicated.
Keep `<!-- uploaded-snippets -->` in `snippets.md`: it marks the insertion point.
The publisher fails safely if that marker is removed. GitHub Actions includes
rendered snippet IDs in `photo-publication.json`, alongside photo IDs.
## Markdown blog-post shortcut
Create **Publish blog post** and enable **Show in Share Sheet**, accepting **Files**.
Share one UTF-8 `.md` or `.markdown` file, up to 1 MiB. In **Get Contents of URL**:
- URL: `https://theread.me/api/posts`; method **POST**; request body **Form**.
- Header: `Authorization` = `Bearer YOUR_TOKEN` (no empty header rows).
- Form field `file`: type **File**, value **Shortcut Input**.
- Let Shortcuts set `Content-Type` automatically.
Show the actual response, and poll `https://theread.me` plus its `status_url`
with the same header. Matching happens in the publisher: `202` only means the
file was accepted, not that a new post or update was published. The status
response supplies the final `url`, `post_id`, and `action` (`created` or `updated`).
`failed` includes an error; `published` confirms the deployed revision. An older
upload can become `superseded` when a newer revision replaces it.
No front matter is required. The first `# Heading` becomes the title, or the
filename is used if there is no heading. Optional YAML front matter supports
`title`, `subtitle`, `lang`, `description`, `categories`, `tags`, `toc`, `math`,
`date`, and `permalink` (a path such as `/my-essay/`). Markdown formatting is
preserved; Liquid template evaluation is disabled for imported post bodies.
The original file is retained privately in the service volume.
### Automatic updates by content
The publisher compares the body against existing Markdown posts, ignoring front
matter and whitespace changes. It updates a unique match with at least 92% word
sequence similarity, at least 200 characters in both texts, and a lead of at
least 8 percentage points over the next plausible match. An exact body match of
at least 40 characters also qualifies. These scores are a conservative heuristic,
not a probability. Renaming the file does not prevent matching; reusing a filename
for unrelated writing does not overwrite its previous post.
If several posts match closely, the upload fails without changing Git. If no
post meets the threshold, it creates a new post. Large rewrites and very short
posts may therefore need an explicit ID. For a known imported post, add the
returned `post_id` to its front matter to target it directly:
```yaml
---
title: My revised essay
post_id: THE_ID_FROM_THE_UPLOAD_STATUS
---
```
For an older Git-authored post, first add a unique `post_id` to that existing
post's front matter and use the same ID in the uploaded file. Updates preserve
the original publication date and public URL, and remain reversible through Git.
Identical file retries are deduplicated and do not roll back a newer revision.
The upload takes the Markdown file only. Relative links to images or attachments
in an export folder will not upload those files; use existing public image URLs.
Export Markdown from the source app before sharing; proprietary note files,
PDFs, ZIP bundles, and rich-text files are not Markdown uploads.
## API, failures, and backups
All API routes require the bearer token: