Repository navigation
feat(sharing): post a teaser for each new article to Substack and X - #9
Merged
Merged
Conversation
First publication records one short teaser (title, description, link) that always fits a single X post, and queues it for every channel enabled at that moment. A supervised worker posts it; edits, republishing, restarts, and upgrades never share an article again. Channels and their credentials are managed by an Owner in the admin portal and stored in the application database, so changes need no host configuration and no restart. Saved credentials are never rendered. Substack has no publishing API: the channel uses its editor's private interface with the Owner's session cookie and publishes to the web without email. X posts are signed with OAuth 1.0a. A delivery is claimed before any provider request and leaves that state only with a recorded outcome. Uncertain outcomes fail closed instead of posting twice, refused credentials hold the queue until new ones are saved, and an offline restore pauses both channels.
tee8z
marked this pull request as ready for review
October 3, 2026 16:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Publishing an article now also announces it on the Owner's other channels. First publication records one short teaser (title, description, link) and posts it to Substack and X when those channels are enabled. The teaser always fits a single 280-character X post, so the same text can be pasted anywhere else from Admin → Sharing.
substack.sidsession cookie: it creates a draft, then publishes it withsend: false, so the post appears on the web and Substack emails nobody. This interface is unofficial and can change without notice; unexpected answers fail closed.POST /2/tweets, signed with OAuth 1.0a from the app's API key and secret and the account's access token and secret. X bills each post to the app's credit.Validation:
cargo clippy -p maincopy-server --all-targets --all-features -- --deny warningsand the full server suite (767 tests) pass. The 28 new tests cover teaser sizing (wide characters, bare domains, clipping), credential parsing, both clients against a local HTTP peer, the OAuth signature against X's published example, the admin page, first-publication queueing, the delivery ledger, and restore. Neither channel has been exercised against a live account yet; the Substack request shapes follow community observations of its web interface and need one real acceptance run before release.Upgrade: migration 0014 adds five
sharing_*tables. Take a verified pre-upgrade backup; an older binary cannot open the newer migration ledger. Sharing credentials are stored in the database and therefore enter its backups.hmacandsha1become direct dependencies at versions already in the lockfile.Not included: a link from the article page to its Substack post. Pages are content-addressed snapshots, so adding one after delivery needs a separate design.