2 Commits
Author SHA1 Message Date
thatguygriffandClaude Opus 5 76441bb91d Reformat the README, and put the Reddit rule back together
CI / Typecheck, test, build (pull_request) Successful in 15s
Publish / Build and push (pull_request) Successful in 20s
Publish / Move the working version on (pull_request) Skipped
A Markdown formatter over the file, plus a repair to what it did on the way
through.

The Reddit rewrite rule is the longest row in the table and the only one the
formatter took apart. The pipes in its alternation are written `\|`, which is
how a literal pipe goes inside a table cell and how the X and TikTok rows have
always written theirs; the formatter dropped the backslashes, at which point
four of them became column separators. The row became seven cells, the
separator row was widened to seven to match, and `(.*)` picked up an escape
and turned into `(.\*)`.

The visible damage is a mangled table. The real damage is that the rule is
there to be copied into StopTheMadness, and what was left could not be: split
across six cells with a regex that no longer matches a Reddit URL.

The row and the separator are restored. Every rule in the table now compiles
and rewrites correctly, checked by pulling them out of the file and running
them against real URLs for each platform, including the `redd.it` short code
and a `/s/` share link.

Worth knowing that the formatter will do this again. Nothing in the repository
configures one, so this was run by hand -- but if it becomes a habit, README.md
wants excluding, because this table cannot survive it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017nMQ2eDKnqALYhAibpTKTu
2026-08-27 13:23:47 -03:00
thatguygriffandClaude Opus 5 503d8a8dec Move the working version on after a release
package.json has sat at 0.1.0 since the first commit, through three releases,
because nothing read it. That is fine right up until something does — an image
label, a health endpoint, a bug report quoting a version — at which point the
tree claims to be a version that shipped long ago.

A new `bump` job takes the tag that was just published, works out the next
patch from it, and commits that to main. After 1.2.0 the tree says 1.2.1: not
a version that exists, which is the point. A build from main is then legible
as "after 1.2.0" rather than as 1.2.0 itself.

It sits in publish.yml rather than a workflow of its own so that it can say
`needs: build`. A version that failed to publish has not been released, and
moving past it would say that it had. Prereleases are skipped for the same
reason -- 1.2.3-rc1 is a candidate for a version that has not shipped, so
there is nothing yet to move past.

The bump goes through `npm version` rather than editing the file. The version
is in the lockfile too, in two places, and a tree where those disagree is worse
than one that is merely out of date.

Three smaller things. The patch arithmetic forces base ten, because a patch
number written 08 is otherwise read as octal and kills the job. The commit
carries `[skip ci]`, or pushing it starts another build of the image that was
just published. And the committer is a name that is not a person at a reserved
address that can never become one, so nothing here names the instance it runs
on.

Pushing to main needs a token that may write to the repository. The Actions
task token can where the instance allows it; where it does not, setting a
VERSION_BUMP_TOKEN secret overrides it. A push that is refused fails the job
with both of those as the suggestion rather than a bare 403.

package.json goes to 1.2.1 here, which is where the job would have left it had
it existed when 1.2.0 went out.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017nMQ2eDKnqALYhAibpTKTu
2026-08-27 13:19:05 -03:00
5 changed files with 120 additions and 12 deletions
+97
View File
@@ -141,3 +141,100 @@ jobs:
- name: Log out - name: Log out
if: always() && github.event_name != 'pull_request' if: always() && github.event_name != 'pull_request'
run: docker logout "${{ vars.REGISTRY }}" || true run: docker logout "${{ vars.REGISTRY }}" || true
# Once a release is out, the version in package.json has already shipped.
# Moving it on to the next patch means the working tree is never sitting on
# a number that is published and immutable, and that a build from main is
# always identifiable as "after 1.2.0" rather than "1.2.0, but not really".
#
# `needs: build` is the point of putting this here rather than in a workflow
# of its own: a version that failed to publish has not been released, and
# bumping past it would say it had.
bump:
name: Move the working version on
needs: build
# Tags only, and only final ones. A prerelease has not shipped the version
# it is a candidate for, so there is nothing yet to move past.
if: github.ref_type == 'tag' && !contains(github.ref_name, '-')
runs-on: ubuntu-latest
container:
image: node:22
steps:
# The tag names a commit in main's history, but the bump belongs on the
# branch, so this checks out main rather than the tag.
- uses: actions/checkout@v4
with:
ref: main
# The task token can push only if the instance allows Actions to
# write to the repository. Where it does not, set VERSION_BUMP_TOKEN
# to a personal access token with write access and it is used
# instead.
token: ${{ secrets.VERSION_BUMP_TOKEN || secrets.GITEA_TOKEN }}
- name: Work out the next patch version
id: next
run: |
set -euo pipefail
version="${{ github.ref_name }}"
version="${version#v}"
major="${version%%.*}"
rest="${version#*.}"
minor="${rest%%.*}"
patch="${rest##*.}"
# `10#` forces base ten: a patch number written 08 would otherwise be
# read as octal and fail to parse.
next="${major}.${minor}.$((10#${patch} + 1))"
echo "next=${next}" >> "$GITHUB_OUTPUT"
echo "Released ${version}; the working version becomes ${next}"
- name: Bump package.json
id: bump
env:
NEXT: ${{ steps.next.outputs.next }}
run: |
set -euo pipefail
current="$(node -p "require('./package.json').version")"
if [ "${current}" = "${NEXT}" ]; then
echo "package.json is already ${NEXT}; nothing to do."
echo "changed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# npm rather than editing the file: the version is in the lockfile
# too, in more than one place, and they have to agree.
npm version "${NEXT}" --no-git-tag-version --allow-same-version >/dev/null
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "package.json ${current} -> ${NEXT}"
- name: Commit it to main
if: steps.bump.outputs.changed == 'true'
env:
NEXT: ${{ steps.next.outputs.next }}
run: |
set -euo pipefail
# A name that is not a person, and a reserved address that can never
# resolve to one. Nothing here names the instance it runs on.
git config user.name 'Release bot'
git config user.email '[email protected]'
git add package.json package-lock.json
# `[skip ci]` because this commit is a number and nothing else:
# without it the push to main starts another build of the very image
# that was just published.
git commit -m "Set the working version to ${NEXT} [skip ci]"
if ! git push origin HEAD:main; then
echo >&2
echo "Could not push the version bump to main. Either the Actions" >&2
echo "token has no write access to this repository, or main is" >&2
echo "protected against direct pushes. Set VERSION_BUMP_TOKEN to a" >&2
echo "token that may push to main, or allow that token past the" >&2
echo "branch protection." >&2
exit 1
fi
+11
View File
@@ -201,6 +201,17 @@ The registry comes from the `REGISTRY` repository variable, the image name from
`IMAGE_NAME` or the repository name, and credentials from `REGISTRY_USER` and the `IMAGE_NAME` or the repository name, and credentials from `REGISTRY_USER` and the
`REGISTRY_TOKEN` secret. Nothing about any particular deployment is committed here. `REGISTRY_TOKEN` secret. Nothing about any particular deployment is committed here.
A release also moves `package.json` on to the next patch version, committed to main by
the `bump` job — so the number in the tree is never one that has already shipped and
been made immutable. It lives in `publish.yml` rather than a workflow of its own so it
can say `needs: build`: a version that failed to publish has not been released, and
bumping past it would claim otherwise. Prereleases are skipped, being candidates for a
version that has not shipped. The bump goes through `npm version` rather than an edit in
place, because the version is in the lockfile too, in more than one place, and the two
have to agree. The commit carries `[skip ci]`, or pushing it would rebuild the image
that was just published. Pushing to main needs a token with write access —
`VERSION_BUMP_TOKEN` overrides the task token where that one cannot.
Two things any deployment has to get right, both learned the hard way: Two things any deployment has to get right, both learned the hard way:
- **Chromium needs more than the default 64Mi `/dev/shm`** or it crashes. Mount a - **Chromium needs more than the default 64Mi `/dev/shm`** or it crashes. Mount a
+9 -9
View File
@@ -29,7 +29,7 @@ Replace `antisocial.example.com` with wherever you are running it.
| TikTok | `/^https:\/\/(?:www\.\|vm\.\|vt\.)?tiktok\.com\/(.*)$/` | `https://antisocial.example.com/tiktok/$1` | | TikTok | `/^https:\/\/(?:www\.\|vm\.\|vt\.)?tiktok\.com\/(.*)$/` | `https://antisocial.example.com/tiktok/$1` |
| Bluesky | `/^https:\/\/bsky\.app\/(.*)$/` | `https://antisocial.example.com/bsky/$1` | | Bluesky | `/^https:\/\/bsky\.app\/(.*)$/` | `https://antisocial.example.com/bsky/$1` |
| Reddit | `/^https:\/\/(?:www\.\|old\.\|new\.\|np\.\|m\.)?reddit\.com\/(.*)$/` | `https://antisocial.example.com/reddit/$1` | | Reddit | `/^https:\/\/(?:www\.\|old\.\|new\.\|np\.\|m\.)?reddit\.com\/(.*)$/` | `https://antisocial.example.com/reddit/$1` |
| Reddit | `/^https:\/\/redd\.it\/(.*)$/` | `https://antisocial.example.com/reddit/$1` | | Reddit | `/^https:\/\/redd\.it\/(.*)$/` | `https://antisocial.example.com/reddit/$1` |
So `https://x.com/user/status/123` becomes So `https://x.com/user/status/123` becomes
`https://antisocial.example.com/x/user/status/123`. `https://antisocial.example.com/x/user/status/123`.
@@ -59,13 +59,13 @@ Each adapter layers its extraction, most structured first:
3. **The rendered DOM** — whatever is actually on screen is real. 3. **The rendered DOM** — whatever is actually on screen is real.
4. **Open Graph tags** — the floor, and enough to show something. 4. **Open Graph tags** — the floor, and enough to show something.
| Platform | Loads | Reads | | Platform | Loads | Reads |
| --------- | ---------------------------- | -------------------------------------------------------- | | --------- | ---------------------------- | --------------------------------------------------------------- |
| Bluesky | the public AT Protocol API | `getPostThread`; falls back to the post page | | Bluesky | the public AT Protocol API | `getPostThread`; falls back to the post page |
| X | `platform.twitter.com` embed | the `cdn.syndication.twimg.com/tweet-result` response | | X | `platform.twitter.com` embed | the `cdn.syndication.twimg.com/tweet-result` response |
| Instagram | `/embed/captioned/` | `shortcode_media`, then the rendered `<video>`/`<img>` | | Instagram | `/embed/captioned/` | `shortcode_media`, then the rendered `<video>`/`<img>` |
| TikTok | the post page | `__UNIVERSAL_DATA_FOR_REHYDRATION__` | | TikTok | the post page | `__UNIVERSAL_DATA_FOR_REHYDRATION__` |
| Threads | the post page | the Relay payloads in `<script type="application/json">` | | Threads | the post page | the Relay payloads in `<script type="application/json">` |
| Reddit | the post's own `.json` | the post and the first page of comments; falls back to the page | | Reddit | the post's own `.json` | the post and the first page of comments; falls back to the page |
On Bluesky and Threads people write in chains, so where the linked post is part On Bluesky and Threads people write in chains, so where the linked post is part
@@ -93,7 +93,7 @@ Reddit posts come with it: every comment the first page carried, nested the way
written. Each comment is a `<details>` element, so folding one takes its whole subtree written. Each comment is a `<details>` element, so folding one takes its whole subtree
with it, works without JavaScript and works from the keyboard; a collapsed comment says with it, works without JavaScript and works from the keyboard; a collapsed comment says
how many replies it is hiding. "Collapse all" is the one piece that needs the script, how many replies it is hiding. "Collapse all" is the one piece that needs the script,
which is why it only appears once the script has run. What was behind a *load more* is which is why it only appears once the script has run. What was behind a _load more_ is
not fetched — that is a second page and often a third — but it is counted and said out not fetched — that is a second page and often a third — but it is counted and said out
loud rather than quietly dropped. loud rather than quietly dropped.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "antisocial", "name": "antisocial",
"version": "0.1.0", "version": "1.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "antisocial", "name": "antisocial",
"version": "0.1.0", "version": "1.2.1",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@fastify/static": "10.1.3", "@fastify/static": "10.1.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "antisocial", "name": "antisocial",
"version": "0.1.0", "version": "1.2.1",
"private": true, "private": true,
"description": "Reads social posts back to you without the app.", "description": "Reads social posts back to you without the app.",
"license": "UNLICENSED", "license": "UNLICENSED",