README.md (7261 bytes)
1 # sbm-webpublish 2 3 sbm-webpublish publishes an [sbm](https://github.com/equwal/sbm) bookmark 4 file as a list of links on a web site. The links page of 5 [recentlywritten.com](https://recentlywritten.com/links.html) uses it. 6 7 It is one small Go program. At run time it needs only the Go standard 8 library. The tests use rapid for property tests. The program keeps the 9 links in one plain sbm file, one line for each link: 10 11 URL<tab>description<tab>tag tag tag<tab>feed 12 13 so `bm`, `cut`, `awk` and `grep` can read the same file. The feed is the 14 address of the RSS or Atom feed of the site. It is optional: a line without 15 a feed has the three fields of `bm`. `bm` reads only the first three 16 fields. `bm --merge` does not copy the feed. 17 18 ## Get the links 19 20 The program makes each output from the file at each request. The outputs 21 contain only http and https links. 22 23 | Path | Format | 24 |---|---| 25 | `/links/links.txt` | The links as sbm lines, oldest first. The fourth field is the feed of the site, if there is one. | 26 | `/links/atom.xml` | An Atom feed, newest first. Each tag of a link is a category. | 27 | `/links/list.html` | The links as an HTML fragment, under each of their tags, with an "rss" link after each link that has a feed. A page of the site includes it with nginx SSI. | 28 29 Each output sends `Last-Modified` and answers `If-Modified-Since` with 30 `304 Not Modified`. 31 32 To follow the links in [sfeed](https://codemadness.org/sfeed.html), add 33 a line to `feeds()` in your sfeedrc: 34 35 feed 'recentlywritten links' 'https://recentlywritten.com/links/atom.xml' 36 37 The plain text works with the usual tools: 38 39 curl -s https://recentlywritten.com/links/links.txt | cut -f1 40 curl -s https://recentlywritten.com/links/links.txt | bm --merge 41 curl -s https://recentlywritten.com/links/links.txt | dmenu -l 20 | cut -f1 | xargs xdg-open 42 43 To follow the sites of the links, make a `feed` line for sfeedrc from each 44 link that has a feed. The name of each feed comes from its URL: 45 46 curl -s https://recentlywritten.com/links/links.txt | awk -F'\t' '$4 { 47 n = $1; sub(/^[a-z]+:\/\//, "", n); gsub(/[^A-Za-z0-9._-]/, "-", n) 48 printf "\tfeed '\''%s'\'' '\''%s'\''\n", n, $4 }' 49 50 ## Dates 51 52 A feed needs the time of each link, and an sbm line has no time. So the 53 admin page writes a date line above the links that it adds: 54 55 # 2026-09-25T08:00:00Z 56 https://suckless.org/ software that sucks less code unix 57 58 `bm` ignores each line that starts with `#`, so the file stays an sbm file. 59 In the feed, a link gets the time of the last date line above it. A link 60 below no date line gets the time of the file. If you add links to the file 61 with an editor, write a date line above them. 62 63 ## The admin page 64 65 `/links/admin` lets the owner add a link with its feed, set the feed of a 66 link, delete a link and import a file. It uses HTTP basic auth. Any user 67 name works. The password is the value of `ADMIN_PASSWORD`. The server 68 refuses a form POST that comes from another site. 69 70 The import takes an sbm file or the Netscape HTML that browsers export. 71 The folders of a browser bookmark become its tags. The import skips a URL 72 that is a link already, but a line with a feed gives its feed to a link 73 that has none. Two URLs are the same link when they differ only in scheme, 74 a leading `www.`, trailing slashes or the case of the host, as in `bm`. 75 76 ## Add links in a shell 77 78 On the server, `sbm-webpublish merge` adds the links of its input, as the 79 import does: 80 81 bm --list | ssh root@example.com 'LINKS_FILE=/var/lib/sbm-webpublish/links.sbm sbm-webpublish merge' 82 83 `sbm-webpublish feed URL FEED` sets the feed of one link, as the admin 84 page does. An empty FEED (`''`) removes the feed. 85 86 The commands do not lock the file against the service, so do not use the 87 admin page at the same time. 88 89 ## Run 90 91 ADMIN_PASSWORD=secret go run . 92 93 Then open http://127.0.0.1:8082/links/admin. 94 95 | Variable | Default | Meaning | 96 |---|---|---| 97 | `ADMIN_PASSWORD` | none, required | The password of the admin page. | 98 | `LINKS_FILE` | `links.sbm` | The sbm file of the links. | 99 | `ADDR` | `127.0.0.1:8082` | The address to listen on. | 100 101 The feed has no settings. It gets its address and name from the request. 102 103 ## Put it on a site 104 105 1. Install the program on a server with systemd and nginx. The second 106 argument is the name of the site file in `/etc/nginx/sites-enabled`: 107 108 sh deploy/deploy.sh root@example.com example 109 110 The script builds the program, installs `deploy/sbm-webpublish.service`, 111 makes an admin password when there is none, and installs 112 `deploy/nginx-location.conf` as `/etc/nginx/snippets/sbm-webpublish.conf`. 113 2. The script adds `include snippets/sbm-webpublish.conf;` to each HTTPS 114 server block of the site file, runs `nginx -t` and reloads nginx. If 115 `nginx -t` fails, it puts the old site file back. The snippet sends 116 `/links/` to the program and turns on SSI for `/links.html`. 117 3. Put this line in the page of the links: 118 119 <!--# include virtual="/links/list.html" --> 120 121 Put `<link rel="alternate" type="application/atom+xml" href="links/atom.xml">` 122 in its head, so that feed readers and `sfeed_web` find the feed. See 123 `pages/links.md` in 124 [recentlywritten](https://github.com/equwal/recentlywritten) for an 125 example. 126 127 ## Test 128 129 go test ./... 130 131 ## More projects 132 133 - [SubRead](https://subread.space/): read along with an audiobook, in the browser. 134 Also [for Android](https://github.com/equwal/subread-android/releases/latest), 135 [for YouTube](https://github.com/equwal/subread-extension/releases/latest) 136 and [for KOReader](https://github.com/equwal/subread.koplugin). 137 - [SubRead Overlay](https://github.com/equwal/subread-overlay/releases/latest): subtitle lines over any Android media player. 138 - [SubRead Dictionary](https://github.com/equwal/subread-dictionary/releases/latest): a pop-up dictionary for Android that reads Yomitan dictionaries. 139 - [SubRead Anki](https://github.com/equwal/subread-anki): one tap makes an Anki card from any Android app. 140 - [Subrep](https://github.com/equwal/subrep-android/releases/latest): live captions of the sound of your phone. 141 - [Book Simulator](https://booksimulator.com/): a reading room for Aozora Bunko and Project Gutenberg books. 142 - [honjimaku.com](https://honjimaku.com/): subtitles for Japanese audiobooks. 143 - [sbm Sync](https://sbmsync.com/): your bookmarks, the same on every device, 144 with [sbm](https://github.com/equwal/sbm) for dmenu, 145 [sbm for Android](https://github.com/equwal/sbm-android/releases/latest) 146 and the [sbm add-on](https://github.com/equwal/sbm-extension/releases/latest) for Firefox and Chrome. 147 - [Rebind](https://github.com/equwal/rebind/releases): remap the hardware buttons of e-ink readers and Android, 148 with [Ink Recents](https://github.com/equwal/ink-recents/releases/latest), 149 [Ink Dim](https://github.com/equwal/ink-dim/releases/latest) 150 and [Ink Update](https://github.com/equwal/ink-update/releases/latest). 151 - [dickt.store](https://dickt.store/): language-learning tools, flashcards and web toys. 152 - [hentaibun.online](https://hentaibun.online/): learn kanbun and kobun. 153 - [Recently Written](https://recentlywritten.com/): the blog, and a list of [all projects](https://recentlywritten.com/projects.html). 154 155 ## License 156 157 AGPL-3.0, as sbm-sync.