Documentation
¶
Overview ¶
Package synd provides a personal syndication engine: publish posts to a canonical static site and syndicate copies to social platforms including Bluesky, Mastodon, and Threads.
Class: experiment UseWhen: Blog/social publishing.
Index ¶
- Constants
- Variables
- func BlueskyPostURL(handle, atURI string) string
- func CloudflareDeploy(cfg CloudflareConfig, siteDir string) error
- func GitPublish(repoDir, message string) (bool, error)
- func MarshalData(v any) json.RawMessage
- func MastodonPostURL(instance, username, statusID string) string
- func TestGit(t interface{ ... }, dir string, args ...string)
- func ThreadsPostURL(username, mediaID string) string
- type AlreadyDeleted
- type BlueskyClient
- func (c *BlueskyClient) Authenticate(ctx context.Context) error
- func (c *BlueskyClient) Post(ctx context.Context, text string) (uri string, cid string, err error)
- func (c *BlueskyClient) PostWithImage(ctx context.Context, text, imagePath, altText string) (uri string, cid string, err error)
- func (c *BlueskyClient) PostWithLink(ctx context.Context, text, linkURL, linkText string) (uri string, cid string, err error)
- type BlueskyConfig
- type CloudflareConfig
- type Engagement
- type Link
- type MastodonClient
- func (c *MastodonClient) Post(ctx context.Context, text string) (id string, statusURL string, err error)
- func (c *MastodonClient) PostWithImage(ctx context.Context, text, imagePath, altText string) (id string, statusURL string, err error)
- func (c *MastodonClient) PostWithLink(ctx context.Context, text, linkURL string) (id string, statusURL string, err error)
- func (c *MastodonClient) VerifyCredentials(ctx context.Context) error
- type MastodonConfig
- type MissingBody
- type NotApproved
- type NotDraft
- type Platform
- type Post
- type PostApproved
- type PostCandidate
- type PostCreated
- type PostDeleted
- type PostEngagementUpdated
- type PostKind
- type PostOption
- type PostProjection
- func (p *PostProjection) ApprovedPosts() []Post
- func (p *PostProjection) Drafts() []Post
- func (p *PostProjection) EngagementFor(postID string) []Engagement
- func (p *PostProjection) Get(id string) *Post
- func (p *PostProjection) Handle(_ context.Context, event fact.Event) error
- func (p *PostProjection) List() []Post
- func (p *PostProjection) PublishedPosts() []Post
- func (p *PostProjection) Syndications(postID string) []SyndicationRecord
- func (p *PostProjection) UnsyncedPosts(platform Platform) []Post
- type PostPublished
- type PostRevised
- type PostStatus
- type PostStore
- func (s *PostStore) Approve(ctx context.Context, postID, approvedBy string) error
- func (s *PostStore) Create(ctx context.Context, kind PostKind, body string, opts ...PostOption) (*Post, error)
- func (s *PostStore) Delete(ctx context.Context, postID, deletedBy string) error
- func (s *PostStore) Get(id string) *Post
- func (s *PostStore) List() []Post
- func (s *PostStore) Projection() *PostProjection
- func (s *PostStore) Projector() fact.Projector
- func (s *PostStore) Publish(ctx context.Context, postID, url string) error
- func (s *PostStore) Revise(ctx context.Context, postID, body, title, abstract string, tags []string, ...) error
- func (s *PostStore) SetEventStore(es fact.EventStore)
- func (s *PostStore) Syndicate(ctx context.Context, postID string, platform Platform, ...) error
- func (s *PostStore) UpdateEngagement(ctx context.Context, postID string, platform Platform, ...) error
- type PostSyndicated
- type SiteBuilder
- type SiteConfig
- type SyndicationRecord
- type ThreadsClient
- func (c *ThreadsClient) Post(ctx context.Context, text string) (string, error)
- func (c *ThreadsClient) PostWithImage(ctx context.Context, text, imageURL string) (string, error)
- func (c *ThreadsClient) PostWithLink(ctx context.Context, text, linkURL string) (string, error)
- func (c *ThreadsClient) VerifyCredentials(ctx context.Context) error
- type ThreadsConfig
Constants ¶
const ( EventPostCreated = "post.created" EventPostRevised = "post.revised" EventPostApproved = "post.approved" EventPostPublished = "post.published" EventPostSyndicated = "post.syndicated" EventPostDeleted = "post.deleted" EventPostEngagementUpdate = "post.engagement_updated" )
Event types for post lifecycle.
Variables ¶
var CanApprove = rule.AllOf( rule.New(PostCandidate.IsDraft), )
CanApprove requires the post to be a draft.
var CanDelete = rule.AllOf( rule.New(PostCandidate.IsNotDeleted), )
CanDelete allows any non-deleted post to be deleted.
var CanPublish = rule.AllOf( rule.New(PostCandidate.IsApproved), )
CanPublish requires the post to be approved.
var CanRevise = rule.AllOf( rule.New(PostCandidate.IsDraft), )
CanRevise requires the post to be a draft.
Functions ¶
func BlueskyPostURL ¶
BlueskyPostURL converts an AT URI to a web URL. at://did:plc:abc/app.bsky.feed.post/xyz → https://bsky.app/profile/handle/post/xyz
func CloudflareDeploy ¶ added in v0.2.0
func CloudflareDeploy(cfg CloudflareConfig, siteDir string) error
CloudflareDeploy uploads the contents of siteDir to a Cloudflare Pages project using the Direct Upload API. It hashes all files with BLAKE3, checks which are missing, uploads them, and creates a deployment.
func GitPublish ¶
GitPublish commits and pushes changes in a site repo directory. Returns true if changes were committed, false if there was nothing to commit.
func MarshalData ¶
func MarshalData(v any) json.RawMessage
MarshalData serialises an event payload to JSON.
func MastodonPostURL ¶
MastodonPostURL constructs the web URL for a Mastodon status.
func ThreadsPostURL ¶
ThreadsPostURL constructs the web URL for a Threads post.
Types ¶
type AlreadyDeleted ¶ added in v0.4.0
type AlreadyDeleted struct{}
type BlueskyClient ¶
type BlueskyClient struct {
// contains filtered or unexported fields
}
BlueskyClient posts to Bluesky via the AT Protocol.
func NewBlueskyClient ¶
func NewBlueskyClient(config BlueskyConfig) *BlueskyClient
NewBlueskyClient creates a client with the given config.
func (*BlueskyClient) Authenticate ¶
func (c *BlueskyClient) Authenticate(ctx context.Context) error
Authenticate creates a session with the PDS.
func (*BlueskyClient) PostWithImage ¶
func (c *BlueskyClient) PostWithImage(ctx context.Context, text, imagePath, altText string) (uri string, cid string, err error)
PostWithImage creates a post with an attached image.
func (*BlueskyClient) PostWithLink ¶
func (c *BlueskyClient) PostWithLink(ctx context.Context, text, linkURL, linkText string) (uri string, cid string, err error)
PostWithLink creates a post with a clickable link appended to the text.
type BlueskyConfig ¶
type BlueskyConfig struct {
Handle string // e.g. "baskins.bsky.social"
Password string // app password
PDS string // PDS host, defaults to https://bsky.social
}
BlueskyConfig holds credentials for the Bluesky AT Protocol API.
type CloudflareConfig ¶ added in v0.2.0
CloudflareConfig holds credentials for Cloudflare Pages direct upload.
type Engagement ¶
type Engagement struct {
PostID string `json:"post_id"`
Platform string `json:"platform"`
Likes int `json:"likes"`
Reposts int `json:"reposts"`
Replies int `json:"replies"`
Views int `json:"views,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
}
Engagement holds metrics for a single post on a single platform.
type Link ¶ added in v0.2.1
type Link struct {
Text string
URL string
Start int // byte offset in plain text
End int // byte offset in plain text
}
Link represents a hyperlink extracted from markdown text.
func ExtractMarkdownLinks ¶ added in v0.2.1
ExtractMarkdownLinks converts markdown link syntax to plain text and returns the positions of each link in the resulting text. Inline code spans are preserved as-is (backtick-wrapped content is not treated as links).
type MastodonClient ¶
type MastodonClient struct {
// contains filtered or unexported fields
}
MastodonClient posts to Mastodon via the REST API.
func NewMastodonClient ¶
func NewMastodonClient(config MastodonConfig) *MastodonClient
NewMastodonClient creates a client with the given config.
func (*MastodonClient) Post ¶
func (c *MastodonClient) Post(ctx context.Context, text string) (id string, statusURL string, err error)
Post creates a status on Mastodon. Returns the status ID and URL.
func (*MastodonClient) PostWithImage ¶
func (c *MastodonClient) PostWithImage(ctx context.Context, text, imagePath, altText string) (id string, statusURL string, err error)
PostWithImage uploads an image and creates a status with it attached.
func (*MastodonClient) PostWithLink ¶
func (c *MastodonClient) PostWithLink(ctx context.Context, text, linkURL string) (id string, statusURL string, err error)
PostWithLink creates a status with a URL appended.
func (*MastodonClient) VerifyCredentials ¶
func (c *MastodonClient) VerifyCredentials(ctx context.Context) error
VerifyCredentials checks the access token and retrieves the account username.
type MastodonConfig ¶
type MastodonConfig struct {
Instance string // e.g. "https://aus.social"
AccessToken string // OAuth access token
}
MastodonConfig holds credentials for the Mastodon API.
type MissingBody ¶ added in v0.4.0
type MissingBody struct{}
type NotApproved ¶ added in v0.4.0
type NotApproved struct{ Status PostStatus }
type NotDraft ¶ added in v0.4.0
type NotDraft struct{ Status PostStatus }
type Post ¶
type Post struct {
ID string `json:"id"`
Kind PostKind `json:"kind"`
Status PostStatus `json:"status"`
Title string `json:"title,omitempty"`
Abstract string `json:"abstract,omitempty"`
Body string `json:"body"`
ImagePath string `json:"image_path,omitempty"`
Tags []string `json:"tags,omitempty"`
// ImportedFrom records the source platform for archived posts.
// Empty for posts authored locally.
ImportedFrom string `json:"imported_from,omitempty"`
// ApprovalToken is a one-time token for the approval gate URL.
ApprovalToken string `json:"approval_token,omitempty"`
CreatedAt time.Time `json:"created_at"`
ApprovedAt time.Time `json:"approved_at,omitempty"`
ApprovedBy string `json:"approved_by,omitempty"`
PublishedAt time.Time `json:"published_at,omitempty"`
}
Post is the canonical representation of a piece of content.
type PostApproved ¶
type PostApproved struct {
PostID string `json:"post_id"`
ApprovedAt time.Time `json:"approved_at"`
ApprovedBy string `json:"approved_by"`
}
PostApproved is emitted when a human approves a draft for publishing.
type PostCandidate ¶ added in v0.4.0
type PostCandidate struct {
Post Post
}
PostCandidate holds the data needed to evaluate post lifecycle rules.
func (PostCandidate) HasBody ¶ added in v0.4.0
func (c PostCandidate) HasBody() rule.Verdict
func (PostCandidate) IsApproved ¶ added in v0.4.0
func (c PostCandidate) IsApproved() rule.Verdict
func (PostCandidate) IsDraft ¶ added in v0.4.0
func (c PostCandidate) IsDraft() rule.Verdict
func (PostCandidate) IsNotDeleted ¶ added in v0.4.0
func (c PostCandidate) IsNotDeleted() rule.Verdict
type PostCreated ¶
type PostCreated struct {
ID string `json:"id"`
Kind PostKind `json:"kind"`
Title string `json:"title,omitempty"`
Abstract string `json:"abstract,omitempty"`
Body string `json:"body"`
ImagePath string `json:"image_path,omitempty"`
Tags []string `json:"tags,omitempty"`
ImportedFrom string `json:"imported_from,omitempty"`
ApprovalToken string `json:"approval_token,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
PostCreated is emitted when a new post is authored.
type PostDeleted ¶
type PostDeleted struct {
PostID string `json:"post_id"`
DeletedAt time.Time `json:"deleted_at"`
DeletedBy string `json:"deleted_by"`
}
PostDeleted is emitted when a post is removed from the site.
type PostEngagementUpdated ¶
type PostEngagementUpdated struct {
PostID string `json:"post_id"`
Platform Platform `json:"platform"`
Likes int `json:"likes"`
Reposts int `json:"reposts"`
Replies int `json:"replies"`
Views int `json:"views,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
}
PostEngagementUpdated is emitted when metrics are polled from a platform.
type PostOption ¶
type PostOption func(*postConfig)
PostOption configures optional fields when creating a post.
func WithAbstract ¶
func WithAbstract(a string) PostOption
func WithApprovalToken ¶
func WithApprovalToken(t string) PostOption
func WithImagePath ¶
func WithImagePath(p string) PostOption
func WithImportedFrom ¶
func WithImportedFrom(p string) PostOption
func WithTags ¶
func WithTags(t ...string) PostOption
func WithTitle ¶
func WithTitle(t string) PostOption
type PostProjection ¶
type PostProjection struct {
// contains filtered or unexported fields
}
PostProjection is a read model built from post events.
func (*PostProjection) ApprovedPosts ¶
func (p *PostProjection) ApprovedPosts() []Post
ApprovedPosts returns posts that are approved but not yet published.
func (*PostProjection) Drafts ¶
func (p *PostProjection) Drafts() []Post
Drafts returns posts with status == draft.
func (*PostProjection) EngagementFor ¶
func (p *PostProjection) EngagementFor(postID string) []Engagement
EngagementFor returns engagement metrics for a post across all platforms.
func (*PostProjection) Get ¶
func (p *PostProjection) Get(id string) *Post
Get returns a post by ID, or nil if not found.
func (*PostProjection) List ¶
func (p *PostProjection) List() []Post
List returns all posts sorted by creation time, newest first.
func (*PostProjection) PublishedPosts ¶ added in v0.2.0
func (p *PostProjection) PublishedPosts() []Post
PublishedPosts returns posts with status == published, newest first.
func (*PostProjection) Syndications ¶
func (p *PostProjection) Syndications(postID string) []SyndicationRecord
Syndications returns the syndication records for a post.
func (*PostProjection) UnsyncedPosts ¶
func (p *PostProjection) UnsyncedPosts(platform Platform) []Post
UnsyncedPosts returns posts that haven't been syndicated to the given platform.
type PostPublished ¶
type PostPublished struct {
ID string `json:"id"`
URL string `json:"url"`
PublishedAt time.Time `json:"published_at"`
}
PostPublished is emitted when the static site is rebuilt and pushed.
type PostRevised ¶
type PostRevised struct {
PostID string `json:"post_id"`
Title string `json:"title,omitempty"`
Abstract string `json:"abstract,omitempty"`
Body string `json:"body"`
Tags []string `json:"tags,omitempty"`
RevisedAt time.Time `json:"revised_at"`
RevisedBy string `json:"revised_by,omitempty"`
}
PostRevised is emitted when a draft post is edited.
type PostStatus ¶
type PostStatus string
PostStatus tracks where a post is in its lifecycle.
const ( StatusDraft PostStatus = "draft" StatusApproved PostStatus = "approved" StatusPublished PostStatus = "published" StatusDeleted PostStatus = "deleted" )
type PostStore ¶
type PostStore struct {
// contains filtered or unexported fields
}
PostStore manages posts through an event-sourced append-only log.
func NewPostStore ¶
func NewPostStore(events fact.EventStore) *PostStore
NewPostStore creates a post store backed by the given event store. The projection is registered as a projector so it stays in sync.
func (*PostStore) Create ¶
func (s *PostStore) Create(ctx context.Context, kind PostKind, body string, opts ...PostOption) (*Post, error)
Create persists a new post as a PostCreated event.
func (*PostStore) Delete ¶
Delete marks a post as deleted so it is excluded from listings and site builds.
func (*PostStore) Projection ¶
func (s *PostStore) Projection() *PostProjection
Projection returns the read model for direct query access.
func (*PostStore) Projector ¶
Projector returns the projector for registration with the event store.
func (*PostStore) Revise ¶
func (s *PostStore) Revise(ctx context.Context, postID, body, title, abstract string, tags []string, revisedBy string) error
Revise updates a draft post's content.
func (*PostStore) SetEventStore ¶
func (s *PostStore) SetEventStore(es fact.EventStore)
SetEventStore replaces the backing event store. Used when the store is constructed before the event store is available.
type PostSyndicated ¶
type PostSyndicated struct {
PostID string `json:"post_id"`
Platform Platform `json:"platform"`
RemoteID string `json:"remote_id"`
RemoteURL string `json:"remote_url,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
PostSyndicated is emitted when a copy is sent to an external platform.
type SiteBuilder ¶
type SiteBuilder struct {
// contains filtered or unexported fields
}
SiteBuilder generates a static site from posts.
func NewSiteBuilder ¶
func NewSiteBuilder(config SiteConfig) *SiteBuilder
NewSiteBuilder creates a builder with the given config.
type SiteConfig ¶
SiteConfig holds settings for static site generation.
type SyndicationRecord ¶
type SyndicationRecord struct {
PostID string `json:"post_id"`
Platform string `json:"platform"`
RemoteID string `json:"remote_id"`
RemoteURL string `json:"remote_url,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
SyndicationRecord tracks a post's presence on an external platform.
type ThreadsClient ¶
type ThreadsClient struct {
// contains filtered or unexported fields
}
ThreadsClient posts to Threads via Meta's Graph API.
func NewThreadsClient ¶
func NewThreadsClient(config ThreadsConfig) *ThreadsClient
NewThreadsClient creates a client with the given config.
func (*ThreadsClient) PostWithImage ¶
PostWithImage creates a post with an attached image.
func (*ThreadsClient) PostWithLink ¶
PostWithLink creates a text post with a link appended.
func (*ThreadsClient) VerifyCredentials ¶
func (c *ThreadsClient) VerifyCredentials(ctx context.Context) error
VerifyCredentials fetches the authenticated user's ID.
type ThreadsConfig ¶
type ThreadsConfig struct {
AccessToken string // long-lived OAuth token
}
ThreadsConfig holds credentials for the Threads API.