A pastebin clone for atproto
1

Configure Feed

Select the types of activity you want to include in your feed.

5 1 0

Clone this repository

https://git.vm.fail/wilb.me/atmob.in https://git.vm.fail/did:plc:sszfm4noj2munuwsq737qqry
ssh://git@knot1.tangled.sh:2222/wilb.me/atmob.in ssh://git@knot1.tangled.sh:2222/did:plc:sszfm4noj2munuwsq737qqry

For self-hosted knots, clone URLs may differ based on your setup.


README.md

atmob paste#

A pastebin / GitHub-Gists clone built on the AT Protocol. Every paste is a public record in the author's own repository — no central database, portable across PDS hosts, and readable by any atproto client.

  • Live: atmob.in
  • Lexicons: in.atmob.paste.*
  • Frontend: a dependency-vendored static SPA (no build server), deployable to any static host

What it does#

  • Multi-file pastes ("bundles"). A bundle is an ordered list of files. Each file is either small text stored inline, or a reference to a standalone paste record — which itself is either inline text (document) or an uploaded blob (any binary: images, archives, …).
  • Sign in with atproto OAuth. Users authorize against their own PDS; the app only requests write access to its own in.atmob.paste.* collections plus blob upload — not "access to everything."
  • Stars & comments. Standard atproto social records that live in the interacting user's repo and point back at the bundle via a strong reference.
  • Public viewing, no login required. Any bundle is viewable by its at:// URI; the app resolves the author's PDS and renders it.
  • Markdown rendering. Bundle descriptions, comments, and any .md file in a paste render as formatted markdown (files have a rendered/source toggle). Output is sanitized with DOMPurify, so untrusted content can't inject scripts.

Repository layout#

lexicons/in/atmob/paste/    Lexicon schemas (the source of truth for the data model)
  defs.json                 shared vocab: visibility, language
  document.json             single inline-text paste
  blob.json                 single blob-backed paste (binary/large files)
  bundle.json               the multi-file gist; inline files + references
  comment.json              a comment (in commenter's repo → strongRef to subject)
  star.json                 a star (in star-er's repo → strongRef to subject)
web/                        the static frontend (see web/README.md to deploy)
  index.html                markup + styles
  app.js                    all app logic (ES module)
  vendor/atproto.js         self-contained bundle of the @atproto/* libs
  vendor/build.sh           regenerates vendor/atproto.js
  client-metadata.json      OAuth client identity (served publicly)

The data model#

Three "content" record types and one that ties them together, plus two social records. All use tid record keys and carry a timezoned createdAt.

Lexicon Holds Notes
in.atmob.paste.document UTF-8 text, inline For snippets ≤ ~64 KB
in.atmob.paste.blob a blob (uploadBlob) For large or binary files
in.atmob.paste.bundle an array of files Each entry is an #inlineFile (text held in the bundle) or a #reference (strongRef to a document/blob)
in.atmob.paste.comment text subject strong-refs the target
in.atmob.paste.star (nothing but the ref) subject strong-refs the target; unstar = delete

Why inline and blob?#

atproto records should stay small — a few dozen KB, ~1 MiB hard cap — with larger content living in blobs. So the model offers a gradient:

  • a one-line snippet → a single #inlineFile in a bundle (one record total)
  • a multi-file config gist → several #inlineFile entries (still one record)
  • a large source file or an image → a blob/document record, #referenced
  • anything that needs its own shareable URL → a standalone document/blob

The bundle's files array is an open union, so new entry variants can be added later without breaking existing readers.

A note on "private" pastes#

Every record in a public PDS is fetchable by anyone with the URI. The visibility field (public | unlisted) is therefore a display/indexing hint, not access control. Genuinely private pastes would require client-side encryption — deliberately out of scope here.

Discovery relies on network infrastructure#

A record in your PDS is public, but finding records scattered across the network needs infrastructure a static page can't provide itself:

  • Stars & comments live in other people's repositories. The frontend reads counts and threads from Constellation, a network-wide backlink index. Your writes hit your PDS immediately; the aggregated count/thread catches up within seconds (the UI renders your own action optimistically in the meantime).

A global list of all pastes is intentionally not implemented: Constellation is backlinks-only and cannot enumerate a collection, and a live Jetstream feed only covers records seen while connected. Durable global discovery would require running an app-view — a firehose consumer that stores records in a database.

Running it#

The frontend is a static site with no build step at runtime — the atproto libraries are pre-bundled into web/vendor/atproto.js. See web/README.md for deploy and local-development instructions.

Quick local start:

cd web
python3 -m http.server 8080   # then open http://127.0.0.1:8080

To publish your lexicons so other atproto software can resolve them, create a _lexicon DNS TXT record on paste.atmob.in pointing at your DID and write the schemas as com.atproto.lexicon.schema records — see the Lexicon resolution spec.

Built with#