summaryrefslogtreecommitdiff
path: root/internal/transcoder/sayauz.go
diff options
from:
to:
context:
space:
mode:
authorGravatar Saya Andy <saya.andy@posteo.com> 2026-08-01 19:58:57 +0700
committerGravatar Saya Andy <saya.andy@posteo.com> 2026-08-01 19:58:57 +0700
commit684d0b6a57d2eb79730ade63baccdc6e59bebc29 (patch)
tree956c89c81965b8c974c8497e3719d0b16d332512 /internal/transcoder/sayauz.go
parent129e48683f6fc0615b606a737f2afcd50538bb2e (diff)
downloadarticlator-684d0b6a57d2eb79730ade63baccdc6e59bebc29.tar.gz
articlator-684d0b6a57d2eb79730ade63baccdc6e59bebc29.zip
feat: repurpose metadata parser as article transcoder for saya.uz andmain
telegram
Diffstat (limited to 'internal/transcoder/sayauz.go')
-rw-r--r--internal/transcoder/sayauz.go138
1 files changed, 138 insertions, 0 deletions
diff --git a/internal/transcoder/sayauz.go b/internal/transcoder/sayauz.go
new file mode 100644
index 0000000..ee888e5
--- /dev/null
+++ b/internal/transcoder/sayauz.go
@@ -0,0 +1,138 @@
+package transcoder
+
+import (
+ "fmt"
+ "log/slog"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/SayaAndy/saya-today-article-metadata-add/config"
+ "github.com/SayaAndy/saya-today-article-metadata-add/internal/draft"
+ "github.com/SayaAndy/saya-today-article-metadata-add/internal/storage"
+)
+
+type SayauzTranscoder struct {
+ category string
+ storage storage.StorageClient
+}
+
+func NewSayauzTranscoder(cfg any) (Transcoder, error) {
+ sayauzCfg, ok := cfg.(*config.SayauzConfig)
+ if !ok {
+ return nil, fmt.Errorf("invalid config for sayauz transcoder")
+ }
+
+ newClient, ok := storage.NewStorageClientMap[sayauzCfg.Storage.Type]
+ if !ok {
+ return nil, fmt.Errorf("unsupported storage type %q for sayauz transcoder", sayauzCfg.Storage.Type)
+ }
+ storageClient, err := newClient(&sayauzCfg.Storage)
+ if err != nil {
+ return nil, fmt.Errorf("init storage client: %w", err)
+ }
+
+ return &SayauzTranscoder{
+ category: sayauzCfg.Category,
+ storage: storageClient,
+ }, nil
+}
+
+func (t *SayauzTranscoder) Name() string { return "sayauz" }
+
+func (t *SayauzTranscoder) Transcode(doc *draft.Document) error {
+ if doc.Metadata == nil {
+ return fmt.Errorf("draft %q has no frontmatter metadata", doc.SourcePath)
+ }
+
+ prodKey := t.category + "/" + doc.Codename + ".md"
+
+ // publishedTime is the first-publish time: reuse an existing value (from the
+ // draft frontmatter, else from the already-published object), otherwise stamp now.
+ inject := doc.Metadata.PublishedTime.IsZero()
+ if inject {
+ published := time.Time{}
+ if meta, err := t.storage.GetMetadata(prodKey); err == nil {
+ if v := meta["published-time"]; v != "" {
+ if parsed, err := time.Parse(time.RFC3339, v); err == nil {
+ published = parsed
+ }
+ }
+ } else {
+ slog.Warn("could not read existing metadata for publishedTime, stamping now",
+ slog.String("key", prodKey), slog.String("error", err.Error()))
+ }
+ if published.IsZero() {
+ published = time.Now()
+ }
+ doc.Metadata.PublishedTime = published
+ }
+
+ formatted := formatContent(doc.RawContent, doc.Metadata.PublishedTime, inject)
+
+ localPath := filepath.Join(filepath.Dir(doc.SourcePath), doc.Codename+".md")
+ if err := os.WriteFile(localPath, formatted, 0o644); err != nil {
+ return fmt.Errorf("write formatted file %q: %w", localPath, err)
+ }
+
+ if err := t.storage.Put(prodKey, formatted, doc.Metadata); err != nil {
+ return fmt.Errorf("upload %q: %w", prodKey, err)
+ }
+
+ slog.Info("sayauz published page",
+ slog.String("key", prodKey),
+ slog.String("local", localPath))
+ return nil
+}
+
+func (t *SayauzTranscoder) Finalize() error {
+ return t.storage.BuildIndex()
+}
+
+// formatContent renders a draft into its published form: standalone "..."
+// separator lines become blank lines (trailing ones are dropped), and when
+// inject is true a "publishedTime:" line is added after "actionDate:" in the
+// frontmatter. Frontmatter, galleries and "---" rules are otherwise untouched.
+func formatContent(raw []byte, published time.Time, inject bool) []byte {
+ lines := strings.Split(string(raw), "\n")
+ out := make([]string, 0, len(lines)+1)
+
+ inFrontmatter := false
+ frontmatterDone := false
+
+ for i, line := range lines {
+ trimmed := strings.TrimSpace(line)
+
+ if !frontmatterDone {
+ if i == 0 && trimmed == "---" {
+ inFrontmatter = true
+ out = append(out, line)
+ continue
+ }
+ if inFrontmatter {
+ out = append(out, line)
+ switch {
+ case trimmed == "---":
+ inFrontmatter = false
+ frontmatterDone = true
+ case inject && strings.HasPrefix(trimmed, "actionDate:"):
+ out = append(out, "publishedTime: "+published.Format(time.RFC3339))
+ }
+ continue
+ }
+ }
+
+ if trimmed == "..." {
+ out = append(out, "")
+ continue
+ }
+ out = append(out, line)
+ }
+
+ for len(out) > 0 && out[len(out)-1] == "" {
+ out = out[:len(out)-1]
+ }
+
+ return []byte(strings.Join(out, "\n"))
+}