Squalk: an old-school forum engine built on Nostr (NIP-29 groups, NIP-7D threads)

Squalk: an old-school forum engine built on Nostr (NIP-29 groups, NIP-7D threads)

Squalk:基于 Nostr 构建的复古论坛引擎(支持 NIP-29 群组与 NIP-7D 讨论串)

Squalk is a forum built on Nostr that permits to manage simple or large communities; in fact you can choose to setup it in “simple” or “full” mode. Simple mode expose a single forum, while in Full mode you can have as many forum as you like. Each forum includes a chat feature in the right-hand sidebar, which is useful for quickly interacting with members. Squalk 是一个基于 Nostr 构建的论坛,允许管理小型或大型社区;实际上,你可以选择将其设置为“简单(simple)”或“完整(full)”模式。简单模式仅展示单个论坛,而在完整模式下,你可以拥有任意数量的论坛。每个论坛的右侧边栏都包含聊天功能,方便与成员进行快速互动。

Tech stack

技术栈

Squalk is built on Nostr and implement NIP-29 and NIP-7D. It needs a personal relay that supports NIP-29 to host the group(s) and a Blossom server for the uploads; Pyramid includes both and is the suggested solution. Squalk 基于 Nostr 构建,并实现了 NIP-29 和 NIP-7D 协议。它需要一个支持 NIP-29 的个人中继(Relay)来托管群组,以及一个用于上传文件的 Blossom 服务器;Pyramid 同时包含这两者,是推荐的解决方案。

Configuration

配置

Squalk is configured entirely through environment variables (all prefixed PUBLIC_, since they are read in the browser). Copy .env.example to .env and fill in the values; SvelteKit also reads .env.development (used by npm run dev) and .env.production (used by npm run build). Squalk 完全通过环境变量进行配置(所有变量均以 PUBLIC_ 为前缀,因为它们是在浏览器中读取的)。将 .env.example 复制为 .env 并填入相应值;SvelteKit 还会读取 .env.development(用于 npm run dev)和 .env.production(用于 npm run build)。

VariableRequiredDefaultDescription
变量必填默认值描述
PUBLIC_RELAY_URLyesWebSocket URL of the NIP-29 relay hosting the group(s), e.g. wss://relay.example.com.
PUBLIC_RELAY_URL托管群组的 NIP-29 中继的 WebSocket URL,例如 wss://relay.example.com。
PUBLIC_MODEnosimplesimple (a single forum) or full (multiple rooms). The admin can later upgrade simple → full at runtime.
PUBLIC_MODEsimplesimple(单个论坛)或 full(多个房间)。管理员后续可在运行时将 simple 升级为 full。
PUBLIC_GROUP_IDin simple modeThe single forum’s group id. Required when PUBLIC_MODE=simple; ignored in full mode, where rooms are selected at runtime.
PUBLIC_GROUP_ID简单模式下必填单个论坛的群组 ID。当 PUBLIC_MODE=simple 时必填;在 full 模式下会被忽略,此时房间在运行时选择。
PUBLIC_TITLEnogroup nameTitle shown in the top bar. When empty it falls back to the group’s name.
PUBLIC_TITLE群组名称顶部栏显示的标题。留空时将回退显示为群组名称。
PUBLIC_JOINCODEnonoyes to show an invite-code field when a join request is rejected (for code-gated relays).
PUBLIC_JOINCODEno设置为 yes 以便在加入请求被拒绝时显示邀请码字段(适用于有代码门槛的中继)。
PUBLIC_SSRnonoyes to render pages on the server (crawlable HTML, real 404s). The build then targets Node (node build) instead of a static bundle; see Deploying.
PUBLIC_SSRno设置为 yes 以在服务器端渲染页面(可抓取的 HTML,真实的 404 错误)。构建目标将变为 Node(node build)而非静态包;详见部署说明。
PUBLIC_SSR_WARMnoyesWith PUBLIC_SSR=yes, each client-side navigation also asks the server to fetch and cache that page, so a later refresh, shared link or crawler hit is served warm. Costs one extra relay query per navigation on the server; set to no to disable.
PUBLIC_SSR_WARMyes当 PUBLIC_SSR=yes 时,每次客户端导航也会请求服务器获取并缓存该页面,以便后续刷新、分享链接或爬虫访问时能快速响应。每次导航会额外消耗一次服务器端中继查询;设置为 no 可禁用。
PUBLIC_SSR_CACHE_FRESHno300Seconds a server-rendered snapshot is served as is. Also the edge cache’s s-maxage.
PUBLIC_SSR_CACHE_FRESH300服务器渲染快照保持不变的秒数。同时也是边缘缓存的 s-maxage。
PUBLIC_SSR_CACHE_STALEno21600Seconds after which a snapshot is no longer served while being refreshed in the background (until then a stale page is answered instantly and updated for the next visitor). Also the edge cache’s stale-while-revalidate.
PUBLIC_SSR_CACHE_STALE21600快照不再直接提供服务并开始在后台刷新的秒数(在此之前,过期的页面会立即响应,并为下一位访问者更新)。同时也是边缘缓存的 stale-while-revalidate。
PUBLIC_SEARCHnonoyes to show a search box at the top of the homepage. Requires a relay with NIP-50 search support.
PUBLIC_SEARCHno设置为 yes 以在首页顶部显示搜索框。需要支持 NIP-50 搜索的中继。
PUBLIC_LABELSnoComma-separated discussion labels offered when composing, e.g. bug,feature,question.
PUBLIC_LABELS发布时提供的逗号分隔的讨论标签,例如 bug,feature,question。
PUBLIC_BLOSSOM_URLnoBlossom server URL used for media uploads, e.g. https://blossom.primal.net. Uploads are disabled when unset.
PUBLIC_BLOSSOM_URL用于媒体上传的 Blossom 服务器 URL,例如 https://blossom.primal.net。未设置时禁用上传。
PUBLIC_ACCENT_COLORno#e32a6dOverride the accent (primary) color. Quote the value (“#00ff00”) — an unquoted leading # is read as a comment. The hover shade is derived automatically.
PUBLIC_ACCENT_COLOR#e32a6d覆盖强调(主)颜色。请使用引号包裹值(如 “#00ff00”)——未加引号的开头 # 会被视为注释。悬停颜色会自动生成。
PUBLIC_SECONDARY_COLORno#ffaf25Override the secondary color. Same quoting rule and derived hover shade as above.
PUBLIC_SECONDARY_COLOR#ffaf25覆盖次要颜色。规则与上述相同,包含引号规则和自动生成的悬停颜色。

Customizing content

自定义内容

Squalk fills its sidebar links and personalizes the homepage and contacts page from NIP-23 long-form events (kind 30023) published to the same relay that hosts the group(s). Only events authored by a forum admin (a pubkey listed in a group’s NIP-29 39001 admin event) are surfaced — the relay query is open, so the admin set is the trust gate. Content is plain markdown. The sample .md files in the repo root (about.md, guidelines.md, homepage.md, contacts.md) are starting points you can adapt and publish. Squalk 通过发布到托管群组的同一中继上的 NIP-23 长文事件(kind 30023)来填充侧边栏链接,并个性化首页和联系页面。只有由论坛管理员(在群组的 NIP-29 39001 管理事件中列出的公钥)发布的事件才会被显示——中继查询是开放的,因此管理员列表是信任门槛。内容为纯 Markdown。仓库根目录下的示例 .md 文件(about.md, guidelines.md, homepage.md, contacts.md)是你可以修改并发布的起点。

资源(侧边栏链接)

Resources appear in the left sidebar and are served at /resource/. Publish a kind 30023 event with: 资源显示在左侧边栏,并通过 /resource/ 访问。发布一个 kind 30023 事件,包含以下标签:

TagRequiredPurpose
标签必填用途
[“t”, “squalk-resource”]yesmarks the event as a resource
[“t”, “squalk-resource”]将事件标记为资源
[“d”, ""]yesthe d/identifier tag — also the URL slug (/resource/)
[“d”, ""]d/identifier 标签 — 同时也是 URL 别名 (/resource/)
[“title”, ""] recommendedlabel shown in the sidebar (falls back to the slug)
[“title”, ""] 推荐侧边栏显示的标签(若为空则回退显示为别名)
[“position”, ""]optionalordering hint, ascending
[“position”, ""]可选排序提示,升序排列

The content field is the markdown body. Ordering: resources with a position come first, sorted ascending; ties and unpositioned resources fall back to alphabetical order by title. Because events are addressable, re-publishing with the same d slug updates the resource (newest wins). content 字段为 markdown 正文。排序规则:带有 position 的资源优先,按升序排列;平局或未设置 position 的资源按标题字母顺序排列。由于事件是可寻址的,使用相同的 d 别名重新发布即可更新资源(以最新版本为准)。

Partials (homepage & contacts)

局部页面(首页与联系页面)

Partials inject custom markdown into fixed slots. There are exactly two slots: home (rendered at the top of the homepage) and contacts (the contacts page). Publish a kind 30023 event with: 局部页面将自定义 markdown 注入到固定槽位中。共有两个槽位:home(渲染在首页顶部)和 contacts(联系页面)。发布一个 kind 30023 事件,包含以下标签:

TagRequiredPurpose
标签必填用途
[“t”, “squalk-partial”]yesmarks the event as a partial
[“t”, “squalk-partial”]将事件标记为局部页面
[“d”, “home”] or [“d”, “contacts”]yesthe slot to fill (any other value is ignored)
[“d”, “home”] 或 [“d”, “contacts”]要填充的槽位(其他值将被忽略)
[“title”, ""] optionalnot displayed in the slot, but useful for clients
[“title”, ""] 可选不会在槽位中显示,但对客户端有用

The newest admin-authored event for a slot wins. The home partial renders above the room list / discussions feed; a leading image URL on its own line (see homepage.md) is rendered as a banner image. 每个槽位以管理员发布的最新事件为准。home 局部页面渲染在房间列表/讨论流上方;如果内容首行是一个独立的图片 URL(参考 homepage.md),它会被渲染为横幅图片。

Developing

开发

Once you’ve created a project and installed dependencies with npm install (or pnpm install or yarn), start a development server: 创建项目并使用 npm install(或 pnpm install / yarn)安装依赖后,启动开发服务器:

npm run dev
# or start the server and open the app in a new browser tab
# 或启动服务器并在新浏览器标签页中打开应用
npm run dev -- --open

Building

构建

Two deployment targets share the same code base, selected by PUBLIC_SSR: 两个部署目标共享同一代码库,通过 PUBLIC_SSR 选择:

  • Static (default, PUBLIC_SSR=no)npm run build (or just build) writes a single-page bundle to build/; serve it from any web server with index.html as the fallback for unknown paths. Everything is fetched by the browser.
  • 静态(默认,PUBLIC_SSR=no)npm run build(或直接 build)将单页应用包写入 build/ 目录;使用任何 Web 服务器托管,并将 index.html 设置为未知路径的回退页面。所有内容均由浏览器获取。
  • Server-rendered (PUBLIC_SSR=yes) — just build-ssr writes a Node app to build/. Pages arrive as crawlable HTML (threads, rooms, resources, contacts, with description/Open Graph tags, JSON-LD, a live robots.txt and sitemap.xml, and real 404s), then the browser takes over exactly as in the static build. The server reads the relay anonymously, so it only ever renders public content; members see their…
  • 服务器渲染(PUBLIC_SSR=yes)build-ssr 将 Node 应用写入 build/ 目录。页面以可抓取的 HTML 形式呈现(包含讨论串、房间、资源、联系页面,带有描述/Open Graph 标签、JSON-LD、动态 robots.txt 和 sitemap.xml,以及真实的 404 错误),随后浏览器接管页面,过程与静态构建完全一致。服务器以匿名方式读取中继,因此它仅渲染公开内容;成员将看到他们的…