Compare commits
2
Commits
1.2.0
..
24db9be5af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24db9be5af
|
||
|
|
503d8a8dec
|
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -21,15 +21,43 @@ readable in your history.
|
|||||||
|
|
||||||
Replace `antisocial.example.com` with wherever you are running it.
|
Replace `antisocial.example.com` with wherever you are running it.
|
||||||
|
|
||||||
| Platform | Find | Replace |
|
Each rule is two fields. Both are on their own line below, and neither needs any
|
||||||
| --------- | ------------------------------------------------------------- | ------------------------------------------- |
|
escaping — copy them straight out of this file.
|
||||||
| X | `/^https:\/\/(?:www\.\|mobile\.)?(?:x\|twitter)\.com\/(.*)$/` | `https://antisocial.example.com/x/$1` |
|
|
||||||
| Threads | `/^https:\/\/(?:www\.)?threads\.(?:net\|com)\/(.*)$/` | `https://antisocial.example.com/threads/$1` |
|
```text
|
||||||
| Instagram | `/^https:\/\/(?:www\.)?instagram\.com\/(.*)$/` | `https://antisocial.example.com/ig/$1` |
|
# X
|
||||||
| TikTok | `/^https:\/\/(?:www\.\|vm\.\|vt\.)?tiktok\.com\/(.*)$/` | `https://antisocial.example.com/tiktok/$1` |
|
/^https:\/\/(?:www\.|mobile\.)?(?:x|twitter)\.com\/(.*)$/
|
||||||
| Bluesky | `/^https:\/\/bsky\.app\/(.*)$/` | `https://antisocial.example.com/bsky/$1` |
|
https://antisocial.example.com/x/$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` |
|
# Threads
|
||||||
|
/^https:\/\/(?:www\.)?threads\.(?:net|com)\/(.*)$/
|
||||||
|
https://antisocial.example.com/threads/$1
|
||||||
|
|
||||||
|
# Instagram
|
||||||
|
/^https:\/\/(?:www\.)?instagram\.com\/(.*)$/
|
||||||
|
https://antisocial.example.com/ig/$1
|
||||||
|
|
||||||
|
# TikTok
|
||||||
|
/^https:\/\/(?:www\.|vm\.|vt\.)?tiktok\.com\/(.*)$/
|
||||||
|
https://antisocial.example.com/tiktok/$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 short links
|
||||||
|
/^https:\/\/redd\.it\/(.*)$/
|
||||||
|
https://antisocial.example.com/reddit/$1
|
||||||
|
```
|
||||||
|
|
||||||
|
A code block rather than a table, because a table cell cannot hold a bare `|` — it has
|
||||||
|
to be written `\|`, which renders correctly and copies wrongly. The alternation in these
|
||||||
|
rules is full of them, and a regex whose pipes arrive as literal pipes matches nothing
|
||||||
|
and says nothing about why.
|
||||||
|
|
||||||
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`.
|
||||||
@@ -42,7 +70,8 @@ where it came from Reddit. A Reddit `/r/<sub>/s/<code>` share link is followed t
|
|||||||
post it points at, and that permalink — not the opaque share code — is what the copy
|
post it points at, and that permalink — not the opaque share code — is what the copy
|
||||||
button hands back.
|
button hands back.
|
||||||
|
|
||||||
`/` serves this table with the live hostnames, if you'd rather read it there.
|
`/` serves these rules with the live hostname already filled in, if you'd rather copy
|
||||||
|
them from there.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -59,13 +88,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 +122,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.
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user