summaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
from:
to:
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md31
1 files changed, 20 insertions, 11 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index f96e149..37d4651 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,7 +4,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
-Go service that extracts YAML frontmatter metadata from markdown files stored in cloud object storage (Backblaze B2 or AWS S3), then writes that metadata back as object attributes/metadata. Supports draft/prod workflow where draft files (`.draft.md`) are compared against prod (`.md`) and skipped if unchanged.
+Go service that reads markdown draft files from a local directory and runs each through an ordered list of **transcoders** that publish it to different targets:
+
+- **sayauz** — formats the draft (strips `...` separator lines, stamps a stable `publishedTime`, keeps frontmatter + `{Gallery}` blocks + `---` rules) and uploads the formatted `.md` to cloud object storage (B2/S3) with frontmatter as object metadata; then rebuilds the bucket `index.json`.
+- **telegram** — splits the body into sections on `...` lines and posts each *new* section to a Telegram channel as text messages + photo galleries (media groups). Posting is append-only, tracked per draft in a `*.telegram-state.json` sidecar.
## Commands
@@ -12,27 +15,33 @@ Go service that extracts YAML frontmatter metadata from markdown files stored in
# Build
go build -o metadata-extractor main.go
-# Run (requires B2_KEY_ID and B2_APPLICATION_KEY env vars, see .envrc)
+# Run (env vars in .envrc: S3_*/B2_* for storage, TELEGRAM_BOT_TOKEN/TELEGRAM_CHANNEL_ID)
go run main.go -c config/config.json
-# No tests or linter configured yet
+# Tests
+go test ./...
```
## Architecture
-Three-layer pipeline: **Config → Storage → Frontmatter parsing**
+Pipeline: **Config → scan local drafts → ParseDraft → each Transcoder → Finalize**
-- `main.go` — Entry point. Loads config, scans bucket, fans out goroutines (semaphore-bounded) to process each file: read → parse frontmatter → write metadata back as object attributes.
-- `config/config.go` — Loads JSON config with `os.ExpandEnv()` for credential injection. Validates via `validator/v10` struct tags. Storage type (`"b2"` or `"s3"`) selects which config struct and client to use.
-- `internal/storage/storage_interface.go` — `StorageClient` interface (`Scan`, `GetReader`, `WriteMetadata`, `CompareDraftAndProd`). Factory map registers all backends.
-- `internal/storage/b2.go` — Backblaze B2 implementation. Uses SHA1 for draft/prod change tracking. Writes metadata to B2 object `Info` map.
-- `internal/storage/s3.go` — AWS S3 implementation (AWS SDK v2). Uses ETag for draft/prod change tracking. Writes metadata as S3 user metadata. Supports custom `Endpoint` + `UsePathStyle` for S3-compatible stores (MinIO, etc).
-- `internal/frontmatter/parser.go` — Extracts content between `---` delimiters, unmarshals YAML into `Metadata` struct.
+- `main.go` — Entry point. Loads config, builds transcoders, walks `DraftDir` for `*DraftSuffix` files, fans out goroutines (semaphore-bounded) to parse each draft and run every transcoder in order, then calls `Finalize()` on each once.
+- `config/config.go` — Loads JSON config with `os.ExpandEnv()` for credential injection. Validates via `validator/v10`. `Transcoders` is an ordered list; each entry's `Type` (`"sayauz"`/`"telegram"`) selects the config struct via `TranscoderConfig.UnmarshalJSON`. `sayauz` embeds a `StorageConfig` (which itself dispatches `"b2"`/`"s3"`).
+- `internal/draft/parser.go` — `ParseDraft` reuses `frontmatter.ParseFrontmatter`, then splits the body into `Section`s on `...` lines, each an ordered list of text / `{Gallery}` `Block`s. Gallery photo lines are `file.jpg [| layoutToken]* [| caption]`; layout tokens (`2x`, `\d+x`) are stripped.
+- `internal/transcoder/transcoder.go` — `Transcoder` interface (`Name`, `Transcode`, `Finalize`) + factory map.
+- `internal/transcoder/sayauz.go` — `formatContent` does the draft→prod transform; uploads to `{Category}/{codename}.md` via the storage client. `publishedTime` is reused from the draft frontmatter or the existing object's metadata, else stamped `now()`.
+- `internal/transcoder/telegram.go` — Bot HTTP API via `net/http` (no SDK dep). `sendMessage`/`sendMediaGroup`; galleries >10 photos split into near-equal groups; per-draft state in `StateDir/{codename}.telegram-state.json`.
+- `internal/storage/storage_interface.go` — `StorageClient` interface (`Put`, `GetMetadata`, `BuildIndex`). Factory map registers all backends.
+- `internal/storage/b2.go` / `s3.go` — B2 (blazer) / S3 (AWS SDK v2) implementations. S3 url-escapes string metadata (and unescapes in `BuildIndex`); B2 stores plain. S3 supports custom `Endpoint` + `UsePathStyle` for S3-compatible stores. `BuildIndex` is S3-only (B2 returns an error).
+- `internal/frontmatter/parser.go` — Extracts content between `---` delimiters, unmarshals YAML into `Metadata`.
## Key Conventions
- Metadata keys use kebab-case (e.g., `short-description`, `action-date`) — stored in B2 `Info` map or S3 user metadata
- Geolocation format: `"{x} {y}"` or `"{x} {y} {areaError}"` — space-separated floats, validated in both storage backends
-- Change tracking: B2 uses SHA1 (`metadata-last-update-sha1`), S3 uses ETag (`metadata-last-update-etag`)
+- S3 key for a draft = `{Category}/{codename}.md`; `codename` = draft filename minus `DraftSuffix`
+- Section separator in drafts is a line that is exactly `...`; `---` is a kept horizontal rule
+- Telegram posting is append-only: only sections whose index is absent from the state file are posted
- Semantic commit messages: `feat:`, `fix:`, `refactor:`
- Structured logging via `log/slog`