this repo has no description
1
fork

Configure Feed

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

add nix flake

Signed-off-by: Marius Kimmina <mar.kimmina@gmail.com>

+261
+5
.gitignore
··· 1 1 leaflet-hugo-sync 2 + 3 + # Nix 4 + result 5 + result-* 6 + .direnv
+130
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 + 5 + ## Project Overview 6 + 7 + leaflet-hugo-sync is a Go CLI tool that syncs Leaflet/Bluesky blog posts to Hugo-compatible markdown files. It fetches blog entries from the AT Protocol (ATProto) network and converts them to markdown with downloaded media. 8 + 9 + ## Build & Run Commands 10 + 11 + ### Using Nix (recommended) 12 + 13 + ```bash 14 + # Build with Nix 15 + nix build 16 + 17 + # Run directly 18 + nix run . -- -config .leaflet-sync.yaml 19 + 20 + # Enter development shell 21 + nix develop 22 + 23 + # Within nix develop shell: 24 + go build -o leaflet-hugo-sync ./cmd/leaflet-hugo-sync 25 + go test ./... 26 + ``` 27 + 28 + ### Using Go directly 29 + 30 + ```bash 31 + # Build the binary 32 + go build -o leaflet-hugo-sync ./cmd/leaflet-hugo-sync 33 + 34 + # Run directly 35 + go run ./cmd/leaflet-hugo-sync/main.go -config .leaflet-sync.yaml 36 + 37 + # Run tests 38 + go test ./... 39 + 40 + # Run specific package tests 41 + go test ./internal/config 42 + go test ./internal/generator 43 + ``` 44 + 45 + ## Architecture 46 + 47 + ### Core Data Flow 48 + 49 + The application follows this pipeline: 50 + 51 + 1. **ATProto Resolution** (`internal/atproto/client.go`) 52 + - Resolves Bluesky handle → DID using public resolver (bsky.social) 53 + - Resolves DID → PDS endpoint via plc.directory 54 + - Creates client pointing to user's personal data server 55 + 56 + 2. **Record Fetching** (`internal/atproto/client.go:FetchEntries`) 57 + - Fetches all records from specified collection (e.g., `pub.leaflet.document`) 58 + - Handles pagination using cursors 59 + - Optionally filters by publication URI 60 + 61 + 3. **Content Conversion** (`internal/converter/markdown.go`) 62 + - Converts Leaflet's block-based format to markdown 63 + - Handles multiple block types: text, code, unorderedList, image, bskyPost 64 + - Processes rich text facets (links, mentions, inline code) 65 + - Returns markdown string + list of image references 66 + 67 + 4. **Media Download** (`internal/media/downloader.go`) 68 + - Downloads blob images from PDS via `com.atproto.sync.getBlob` XRPC endpoint 69 + - Determines file extension from Content-Type header 70 + - Caches downloaded images (skips if file exists) 71 + - Returns Hugo-compatible image paths 72 + 73 + 5. **Post Generation** (`internal/generator/hugo.go`) 74 + - Uses Go templates to generate frontmatter from config 75 + - Combines frontmatter + markdown content 76 + - Writes final `.md` files to configured output directory 77 + 78 + ### Key Types 79 + 80 + **ATProto Types** (`internal/atproto/types.go`): 81 + - `LeafletDocument`: Top-level document with title, publishedAt, pages 82 + - `Page`: Contains array of BlockWrappers 83 + - `BlockWrapper`: Wraps a block with deferred JSON unmarshaling 84 + - Block types: `TextBlock`, `CodeBlock`, `UnorderedListBlock`, `ImageBlock`, `BskyPostBlock` 85 + - `Facet`: Rich text annotation with byte-based ranges (links, mentions, inline code) 86 + 87 + **Configuration** (`internal/config/config.go`): 88 + - `Source`: Specifies handle, collection, optional publication_name 89 + - `Output`: Defines posts_dir, images_dir, image_path_prefix 90 + - `Template`: Go template strings for frontmatter and content 91 + 92 + ### Important Implementation Details 93 + 94 + **Facet Processing**: Facets use byte offsets, not rune offsets. The converter handles this by working with `[]byte` instead of string indices when applying rich text formatting. 95 + 96 + **Collection Migration**: The code defaults `com.whtwnd.blog.entry` to `pub.leaflet.document` as the old collection is deprecated (see main.go:104-110). 97 + 98 + **Blob Download**: Uses the pattern `{PDS_HOST}/xrpc/com.atproto.sync.getBlob?did={DID}&cid={CID}` to fetch images. 99 + 100 + **Publication Filtering**: When `publication_name` is configured, the tool first fetches `pub.leaflet.publication` records, finds the matching publication URI, then filters documents by that URI. 101 + 102 + ## Configuration File 103 + 104 + The tool requires a YAML config file (default: `.leaflet-sync.yaml`): 105 + 106 + ```yaml 107 + source: 108 + handle: "username.bsky.social" 109 + collection: "pub.leaflet.document" 110 + publication_name: "optional-publication-name" # Filter by specific publication 111 + 112 + output: 113 + posts_dir: "content/posts/leaflet" 114 + images_dir: "static/images/leaflet" 115 + image_path_prefix: "/images/leaflet" 116 + 117 + template: 118 + frontmatter: | 119 + --- 120 + title: "{{ .Title }}" 121 + date: {{ .CreatedAt }} 122 + --- 123 + ``` 124 + 125 + Template variables available: `.Title`, `.CreatedAt`, `.Slug`, `.Handle`, `.OriginalURL`, `.Content` 126 + 127 + ## Dependencies 128 + 129 + - `github.com/bluesky-social/indigo`: Official ATProto/Bluesky Go library for XRPC client and ATProto methods 130 + - `gopkg.in/yaml.v3`: YAML config parsing
+61
flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-utils": { 4 + "inputs": { 5 + "systems": "systems" 6 + }, 7 + "locked": { 8 + "lastModified": 1731533236, 9 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 + "owner": "numtide", 11 + "repo": "flake-utils", 12 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 + "type": "github" 14 + }, 15 + "original": { 16 + "owner": "numtide", 17 + "repo": "flake-utils", 18 + "type": "github" 19 + } 20 + }, 21 + "nixpkgs": { 22 + "locked": { 23 + "lastModified": 1767379071, 24 + "narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=", 25 + "owner": "NixOS", 26 + "repo": "nixpkgs", 27 + "rev": "fb7944c166a3b630f177938e478f0378e64ce108", 28 + "type": "github" 29 + }, 30 + "original": { 31 + "owner": "NixOS", 32 + "ref": "nixos-unstable", 33 + "repo": "nixpkgs", 34 + "type": "github" 35 + } 36 + }, 37 + "root": { 38 + "inputs": { 39 + "flake-utils": "flake-utils", 40 + "nixpkgs": "nixpkgs" 41 + } 42 + }, 43 + "systems": { 44 + "locked": { 45 + "lastModified": 1681028828, 46 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 + "owner": "nix-systems", 48 + "repo": "default", 49 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 + "type": "github" 51 + }, 52 + "original": { 53 + "owner": "nix-systems", 54 + "repo": "default", 55 + "type": "github" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+65
flake.nix
··· 1 + { 2 + description = "Sync Leaflet/Bluesky blog posts to Hugo-compatible markdown"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-utils.url = "github:numtide/flake-utils"; 7 + }; 8 + 9 + outputs = { self, nixpkgs, flake-utils }: 10 + flake-utils.lib.eachDefaultSystem (system: 11 + let 12 + pkgs = nixpkgs.legacyPackages.${system}; 13 + in 14 + { 15 + packages = { 16 + default = pkgs.buildGoModule { 17 + pname = "leaflet-hugo-sync"; 18 + version = "0.1.0"; 19 + 20 + src = ./.; 21 + 22 + vendorHash = "sha256-ruru71ASUBsvOByM58X0eoJTfEhtdijpt9d2g6DXnvI="; 23 + 24 + subPackages = [ "cmd/leaflet-hugo-sync" ]; 25 + 26 + ldflags = [ 27 + "-s" 28 + "-w" 29 + ]; 30 + 31 + meta = with pkgs.lib; { 32 + description = "Sync Leaflet (ATproto) blog posts to Hugo-compatible markdown"; 33 + homepage = "https://github.com/marius/leaflet-hugo-sync"; 34 + license = licenses.mit; 35 + mainProgram = "leaflet-hugo-sync"; 36 + }; 37 + }; 38 + 39 + leaflet-hugo-sync = self.packages.${system}.default; 40 + }; 41 + 42 + devShells.default = pkgs.mkShell { 43 + buildInputs = with pkgs; [ 44 + go 45 + gopls 46 + gotools 47 + go-tools 48 + delve 49 + ]; 50 + 51 + shellHook = '' 52 + echo "leaflet-hugo-sync development environment" 53 + echo "Go version: $(go version)" 54 + echo "Available commands:" 55 + echo " go build -o leaflet-hugo-sync ./cmd/leaflet-hugo-sync" 56 + ''; 57 + }; 58 + 59 + apps.default = { 60 + type = "app"; 61 + program = "${self.packages.${system}.default}/bin/leaflet-hugo-sync"; 62 + }; 63 + } 64 + ); 65 + }