summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-09-28 15:48:51 +0800
committereltrac <hi@eltr.ac>2026-09-28 21:27:26 +0800
commitf55a1c09b6a0392aca6d898930cc35b951c6a103 (patch)
tree6ed5217e18777237b14b10b4a875fc717ea66151 /doc
parent0471b4017ccab10f92f140881aeafba9ceb0896f (diff)
doc: first draft of user manual; add philosophy; update contributors' guide
Diffstat (limited to 'doc')
-rw-r--r--doc/ARCHITECTURE.md7
-rw-r--r--doc/CONTRIBUTING.md84
-rw-r--r--doc/PHILOSOPHY.md66
-rw-r--r--doc/intro.md3
-rw-r--r--doc/manual/API.md9
-rw-r--r--doc/manual/configuration.md98
-rw-r--r--doc/manual/index.md30
-rw-r--r--doc/manual/installation.md91
-rw-r--r--doc/manual/migration.md17
9 files changed, 395 insertions, 10 deletions
diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md
new file mode 100644
index 0000000..364ae50
--- /dev/null
+++ b/doc/ARCHITECTURE.md
@@ -0,0 +1,7 @@
+# Architecture
+
+> [!note]
+> Before this document is finished, one can read [core.clj](https://codeberg.org/eltrac/weepinbell/src/branch/master/src/weepinbell/core.clj). It's basically an entry point where everything is loaded and started. Follow it's dependencies and read docstrings.
+
+TODO
+
diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md
new file mode 100644
index 0000000..010a3ca
--- /dev/null
+++ b/doc/CONTRIBUTING.md
@@ -0,0 +1,84 @@
+# Contributing to Weepinbell
+
+Contributions are welcome and greatly appreciated, as long as you follow the rules.
+
+## First step
+
+Go to [Issues](https://codeberg.org/eltrac/weepinbell/issues) and find ones labeled [contribution welcome](https://codeberg.org/eltrac/weepinbell/issues?q=&type=all&state=open&labels=2145577&milestone=0&assignee=0&poster=0), or [good first issue](https://codeberg.org/eltrac/weepinbell/issues?q=&type=all&state=open&labels=2145589&milestone=0&assignee=0&poster=0) if you're contributing for the first time.
+
+Once you find an issue you think you'll be able to help with, read the conversation and ask if anyone else has already been working on it. If not, you can fork the repository and work on your fork!
+
+Don't create a PR if it hasn't been discussed and approved in an issue yet!
+
+## Style Guide
+
+### Code style
+
+1. Functions should be small and does one thing only.
+2. A file should have less than 100 lines of code.
+3. Test-Driven Development is not enforced, but committing tests alongside your code is encouraged.
+4. Use utility functions and `->`/`->>` macros to reduce layers of indentation.
+5. Format your code using [cljfmt](https://github.com/weavejester/cljfmt). (run `lein cljfmt fix`)
+
+### :git: Commits and Pull Requests
+
+1. Use [Scoped Commits](https://scopedcommits.com/).
+2. **Squash** your changes to a few large, standalone commits, and **rebase** `master` before you create a PR. This keeps the history clean.
+3. Make sure you've run `lein test` and manually verified your changes before submitting PR.
+4. Keep description simple and concise. Highlight affected scopes, things other contributors should look out for.
+
+## Set up environment
+
+Install Clojure and [Leinigen](https://leiningen.org/).
+
+To install dependencies:
+
+```
+lein deps
+```
+
+To start REPL:
+
+```
+lein repl
+```
+
+To start a dev server:
+
+```
+lein ring server
+```
+
+To run project:
+
+```
+lein run
+```
+
+To compile:
+
+```
+lein uberjar
+```
+
+Make sure to run our test suite and solve bugs captured by tests before you commit and create a PR. To run tests:
+
+```
+lein test
+```
+
+## Generated Code Policy
+
+We do not accept generated code, for the same reasons as Clojure:
+
+> Clojure’s code is written and reviewed by humans. Code generated by a large language model or similar technology, such as Anthropic’s Claude, GitHub/Microsoft’s Copilot, OpenAI’s ChatGPT, Facebook/Meta’s Code Llama et al, is not compliant with the covenants and representations of Clojure’s Contributor’s Agreement, and is thus not acceptable as code for Clojure.
+>
+> An exception may be made for human written generators whose source code is contributed along with the patch.
+
+*from [Clojure's Contributor Agreement](https://clojure.org/dev/contributor_agreement#_no_generated_code)*
+
+Also, refer to Codeberg's [Terms of Use](https://codeberg.org/Codeberg/org/commit/96fac426a32d1ba91ff879366d59bf1af54080c2):
+
+> 7. You must not share projects that mostly consist of code written by "generative AI"-tools (including services such as *Claude*, *OpenAI Codex*). Such projects having an unclear copyright status (see requirements § 2 (1) 1 and § 2 (1) 3) and furthermore have little safeguards to ensure that they do not include harmful code (c.f. § 2 (1) 5).
+
+Generated code, especially by an LLM, is not the prompter's to license. Such code may not be compatible with our GNU Affero General Public License. LLM usage for research, validation and code review is tolerated.
diff --git a/doc/PHILOSOPHY.md b/doc/PHILOSOPHY.md
new file mode 100644
index 0000000..8c06a29
--- /dev/null
+++ b/doc/PHILOSOPHY.md
@@ -0,0 +1,66 @@
+# Philosophy
+
+This document explains what weepinbell is, what it's not, and the design principles it follows. This document is for anyone who wants to get involved in the development of weepinbell and users who are simply curious.
+
+## What It Is
+
+A tool for hosting [webmention](https://www.w3.org/TR/webmention/) service that receives and sends[^1] webmentions. It aims to be an all-in-one webmention toolkit that's easy to self-host.
+
+It should be capable of:
+
+1. Receiving webmentions for one or many webmasters[^2]
+2. Parsing metadata (microformats) of webmentions
+3. Storing and querying webmentions
+4. Sending webmentions
+5. Notifiying users about webmentions they receive or send
+
+## What It's Not
+
+1. Full website interaction layer
+2. Commenting system
+3. Commercial user tracking system
+4. Enterprise-level system
+5. LLM-powered application
+6. Complex system that handles large amount of concurrent requests
+
+## Principles
+
+### Documentation first, code later
+
+1. A feature should be discussed in an issue before it's implemented in a PR.
+2. When writing a function, write docstring first.
+3. When designing a new component, write namespace's docstring first.
+
+### Minimal dependencies
+
+1. Never depend on things you don't need. For instance, if only one or two features of a library is used, we should not depend on that library, but should seek alternatives that's more focused, implement those features within our project, or copy-paste code we need exactly into our codebase.
+2. If a requirement is not of great significance and implementing it will require introducing tons of dependencies, it's best to reconsider. Read [#19](https://codeberg.org/eltrac/weepinbell/issues/19) and see how and why we abandoned auto-generating API documentation.
+
+### Make things pluggable
+
+Whenever possible, pass something as a function argument instead of requiring it in that function's namespace, because:
+
+1. It makes the function **testable**. It's easy to pass mock objects as arguments. It's difficult to redefine a namespace's dependencies.
+2. It makes it easier to replace concrete implementation. If a component depends on SQL directly, it's hard to switch to KV storage. If a component uses something like `((:get-data storage) data-id)`, all we need to do is replace that `storage`.
+3. It's lighter than implementing interfaces or protocols. We don't need to create an object or a record. We only need a regular symbol or a collection of symbols.
+4. It can still be type-safe if we check it using spec or schema.
+5. It makes the system easier to maintain.
+
+### Keep everything small and focused
+
+Every part of the system should be small. A file should have no more than 100 lines of code.[^3] A function should be easy to understand and should not require knowledge of more than one layer of abstraction. The key to keep things small is to keep them clear. Everything should have a clear purpose and a clear way to achieve that purpose.
+
+## Why Clojure
+
+1. Clojure is a functional language with laziness, which makes systems easier to maintain.
+2. Clojure runs on JVM, a stable platform.
+3. Clojure's semantics is pretty dense and it's easy to write less code and achieve more.
+4. *It's a Lisp and I like it.*
+
+## Why the name "weepinbell"
+
+Webmention is basically notification to someone when someone else mentioned them, which is like ringing a bell. And it's a name of a Pokemon. No particular reaon about why it has to be a pokemon.
+
+[^1]: Sender feature is planned but not yet implemented. See [#24](https://codeberg.org/eltrac/weepinbell/issues/24).
+[^2]: Webmasters refers to those who own a website.
+[^3]: Trust me. 100 LoC is more than enough for Clojure.
diff --git a/doc/intro.md b/doc/intro.md
deleted file mode 100644
index 9143296..0000000
--- a/doc/intro.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Introduction to weepinbell
-
-TODO: write [great documentation](https://jacobian.org/writing/what-to-write/)
diff --git a/doc/manual/API.md b/doc/manual/API.md
index f227264..cecfe7a 100644
--- a/doc/manual/API.md
+++ b/doc/manual/API.md
@@ -1,8 +1,3 @@
----
-weepinbell_version: v0.6.0
-modified: 2026-09-28
----
-
# HTTP API Referrence
## Notations used in this page
@@ -21,12 +16,12 @@ Receiver endpoint is where people send their webmentions to. It's a route that a
| Method | Path |
| ------ | ----------------------------------------------------- |
-| `POST` | `/endpoint` (Configurable, see [./configuration.md]) |
+| `POST` | `/endpoint` (Configurable, see [configuration](./configuration.md)) |
| Post Params | Description | Default |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `source` | Where webmention is coming from. The page that mentioned `target`. | None |
-| `target` | Where webmention is sent to. The page that was mentioned by `source`. Target's domain should be in the allow list of receiver. (see [./configuration.md]) | None |
+| `target` | Where webmention is sent to. The page that was mentioned by `source`. Target's domain should be in the allow list of receiver. (see [configuration](./configuration.md)) | None |
The endpoint responds with status code `201` and a `Location` header pointing to the webmention. It'll take some time for weepinbell to verify, process and store webmention. Senders should check the `Location` URL a few minutes after.
diff --git a/doc/manual/configuration.md b/doc/manual/configuration.md
new file mode 100644
index 0000000..23787f5
--- /dev/null
+++ b/doc/manual/configuration.md
@@ -0,0 +1,98 @@
+# Configuration
+
+## 0. Create a `.edn` file
+
+> [!note]
+> If you're not familiar with this format, see [edn](https://github.com/edn-format/edn). In short, it's Clojure's equivalent of JSON, but more extensible (for example, you can use functions as values).
+
+Configure weepinbell by creating `config.edn` at the app's working directory[^1].
+
+Copy-paste the following [default configuration](https://codeberg.org/eltrac/weepinbell/src/branch/doc/resources/config.edn) to your file and start editing.
+
+```edn
+{:port "3000"
+ :base-url "http://localhost:3000"
+ :endpoint "/endpoint"
+ :allowed-target-domains ["example.com"]
+ :data-path "data/webmention.edn"
+ :webhook {:on-receive []
+ :on-create []
+ :on-update []
+ :on-delete []}
+ :webhook-request-fn nil
+ :feed-length 8}
+```
+
+Port can also be configured with enviroment variable `PORT`. Config overrides enviroment variable.
+
+## 1. First options to modify
+
+For weepinbell to receive webmentions for your site, add your site's domain to `:allowed-target-domains`.
+
+```edn
+ :allowed-target-domains ["your.site.domain" "can-have.more-than.one"]
+```
+
+For weepinbell to return the right URLs to webmentions and proxied photos, set `:base-url` to where you're hosting weepinbell, **without trailing slash**.
+
+```edn
+ :base-url "https://example.com"
+```
+
+> [!warning]
+> You have to **restart** weepinbell for configuration to take effects.
+
+## 2. Tweak some features
+
+### Webhook
+
+Weepinbell can be configured to send `POST` requests to certain URLs when certain events ocurred, like when a webmention is received (but not processed yet), created, updated or deleted.
+
+```edn
+ :webhook {:on-receive []
+ :on-create ["https://send-to-me-when-created.com"]
+ :on-update ["https://can-have-multiple-URLs-under-one.event"
+ "https://like-this.net"]
+ :on-delete []}
+```
+
+If an event key's value is empty, request will not be sent when that event occurs.
+
+Webhook requests can carry customized payload. You need a *payload generator function*. The function should take two arguments, `hook` and `data`.
+
+- `hook`: a Clojure keyword, which can only be one of the four keywords: `:on-receive`, `:on-create`, `:on-update` and `:on-delete`.
+- `data`: a wrapped webmention map, much like what you'll get in `/api/mention/{id}` (see [API Referrence](./API.md)), but a Clojure map. Note that when `hook` is `:on-receive`, `data` only contains `webmention` but no `microformats` or `extra` fields.
+
+The return value should be a map, which will be merged to HTTP request options. Example:
+
+```edn
+{:body "I'm in request body"
+ :headers {"Content-Type" "text/plain"}
+ :post-params {"title" "Webmention received"}}
+```
+
+Set `:webhook-request-fn` to your generator function:
+
+```edn
+ :webhook-request-fn (fn [hook data] ...)
+```
+
+It can also be a keyword, which will use a preset generator. Currently available generator presets are `:ntfy` and `:bark`. (for push notification apps [ntfy](https://ntfy.sh/) and [Bark](https://bark.day.app/))
+
+```edn
+ :webhook-request-fn :ntfy ;; or :bark
+```
+
+See examples [here](https://codeberg.org/eltrac/weepinbell/src/commit/0471b4017ccab10f92f140881aeafba9ceb0896f/src/weepinbell/webhook_presets.clj#L21-L33). Feel free to [contribute](../CONTRIBUTING.md) more presets!
+
+### RSS/Atom Feed
+
+Weepinbell generates [Atom](https://validator.w3.org/feed/docs/atom.html) feed for each domain.
+
+Currently, the only option available is length of feed. If set to `n`, Atom feed lists only the newest `n` webmentions. Cannot use `0` and cannot list all available webmentions.
+
+```edn
+ :feed-length 8
+```
+
+[^1]: Working directory is where the starting command is run. For example, if you `cd` into `/home/yourname/weepinbell` and run `java -jar weepinbell-xxx.jar`, then your configuration file should be `/home/yourname/weepinbell/config.edn`. If you're using Docker or systemd, you can configure working directory in `docker-compose.yaml` and `.service` files.
diff --git a/doc/manual/index.md b/doc/manual/index.md
new file mode 100644
index 0000000..0a92f0c
--- /dev/null
+++ b/doc/manual/index.md
@@ -0,0 +1,30 @@
+# User Manual
+
+## Index
+
+1. [Installation](./installation.md)
+2. [Configuration](./configuration.md)
+3. [API Referrence](./API.md)
+
+## Ask a question
+
+Feel free to email me: `hi@eltr.ac`. Ask me anything you are not sure about. Opening an issue to ask questions is also OK, but I might be slow to respond.
+
+## Report a bug
+
+I don't care for issue templates. When [submitting a new issue](https://codeberg.org/eltrac/weepinbell/issues/new), all you need to do is try to get these information as clear as possible.
+
+1. Versions
+ - Weepinbell version
+ - JDK version
+ - Operating system version
+ - Browser version (if relevant)
+2. Your configuration file (hide your secrets and keys)
+3. What you did (How can we reproduce this bug?)
+4. Logs
+ - `docker logs` or `journalctl` or whatever you're using
+
+## Suggest a Feature
+
+
+
diff --git a/doc/manual/installation.md b/doc/manual/installation.md
new file mode 100644
index 0000000..3ab457d
--- /dev/null
+++ b/doc/manual/installation.md
@@ -0,0 +1,91 @@
+# Installation
+
+## 0. Requirements
+
+- JDK (Java), >= 17
+- [Leiningen](https://codeberg.org/leiningen/leiningen) (optional, only if you want to build from source)
+
+## 1. Get the `.jar`
+
+### Download pre-built jar files
+
+You can download the latest version in [Releases](https://codeberg.org/eltrac/weepinbell/releases) page.
+
+```shell
+# or via command line
+wget https://codeberg.org/eltrac/weepinbell/releases/download/vx.x.x/weepinbell-vx.x.x-standalone.jar
+```
+
+> [!note]
+>
+> **What's the difference between a `standalone` jar and a regular jar?**
+>
+> You may have noticed that `standalone` jars are larger, that's because it has all the dependencies bundled in and can be run directly. Unless you want to manage dependencies manually, you should always use the `standalone` jar.
+
+### Build from source
+
+Make sure you have [Leinigen](https://leiningen.org/) installed.
+
+```shell
+git clone https://codeberg.org/eltrac/weepinbell
+cd weepinbell
+lein uberjar
+```
+
+And grab `target/weepinbell-v0.x.x-standalone.jar`.
+
+## 2. Run the app
+
+### The command
+
+```shell
+java -jar weepinbell-v0.x.x-standalone.jar
+```
+
+When you see this line of log, you're good to go:
+
+```
+2026-09-28T08:20:45.680Z MACHINE INFO [weepinbell.core:43] - weepinbell started, listening on 3000
+```
+
+### Docker
+
+> [!warning]
+> The `Dockerfile` is not actively tested.
+
+[Dockerfile](Dockerfile) and [docker-compose.yml](docker-compose.yml) are included in the repository.
+
+```shell
+git clone https://codeberg.org/eltrac/weepinbell
+cd weepinbell
+docker compose up -d
+```
+
+Podman should work as well, but it's not tested.
+
+## 3. Set up web server
+
+It's recommended to put weepinbell behind a reverse proxy, using something like [nginx](https://nginx.org/) and [caddy](https://caddyserver.com/). Here's an example `Caddyfile`:
+
+```Caddyfile
+example.com
+
+reverse_proxy :3000
+```
+
+Weepinbell listens port `:3000` by default. See [configuration](./configuration.md) on how to change it.
+
+## 4. Point your site to weepinbell
+
+Add this to your `<head>`:
+
+```html
+<link rel="webmention" href="https://example.com/endpoint">
+```
+
+Senders will look for this `<link>` tag and know where to send webmentions to.
+
+---
+
+Now weepinbell is installed on your site. Before it can actually receive webmentions for you, you need to [configure it](./configuration.md).
+
diff --git a/doc/manual/migration.md b/doc/manual/migration.md
new file mode 100644
index 0000000..8ef7d38
--- /dev/null
+++ b/doc/manual/migration.md
@@ -0,0 +1,17 @@
+# Migration
+
+## Migrating from `webmention.io`
+
+You can migrate from [webmention.io](https://webmention.io/) with [this Babashka script](script/migrate-from-webmentionio.clj). Make sure you have [Babashka](https://babashka.org/) installed and:
+
+```shell
+./script/migrate-from-webmentionio.clj <token> <new_endpoint>
+```
+
+Get `<token>` in [webmention.io settings](https://webmention.io/settings). `<new_endpoint>` is your current Weepinbell endpoint (default: `localhost:3000/endpoint`).
+
+This script basically just resends all webmentions to Weepinbell. To avoid being mistaken as attacks, webmentions are resent every 0.5 seconds, so it might take a long time.
+
+> [!note]
+> We're developing a Batch API to simplify the process. See [#38](https://codeberg.org/eltrac/weepinbell/issues/38). Contributions are greatly appreciated!
+