Crates.io | gen-bsky |
lib.rs | gen-bsky |
version | 0.1.0 |
created_at | 2025-07-31 11:45:06.641239+00 |
updated_at | 2025-09-19 13:40:37.672316+00 |
description | A Library to generate and post a bluesky post |
homepage | |
repository | https://github.com/jerus-org/pcu |
max_upload_size | |
id | 1774948 |
size | 387,494 |
A library to generate a bluesky feed post from the front matter metadata in markdown source file for a web document.
Automatically creates and publishes Bluesky posts for your markdown blog articles using front matter metadata. This tool maximizes character usage by generating short URLs, leaving more space for compelling post content.
The process separates drafting from publishing to integrate seamlessly with your website build and deployment pipeline:
When processing your markdown blog files:
When your website goes live:
Bluesky posts are automatically generated from blog post metadata including:
Bluesky posts are limited to 300 characters (graphemes). If the generated post exceeds this limit, an error will be logged as a warning and processing will continue without posting to Bluesky.
When the default title, description, and tags produce a post that's too long,
you can override the content by adding a [bluesky]
section to your front matter:
[bluesky]
description = "Custom shorter description for Bluesky"
tags = ["rust", "web"]
Note: The post title and link URL cannot be customized and will always use the original blog post values.
+++
title = "Used as the header in the Bluesky blog post"
description = """
The descriptions of the blog post can be as necessary for the \
publication of the website and as long as you need it to be. \
The goals of publication on the website should be primary \
ones driving the composition of this element. The description \
will be used for the text on the Bluesky blog post and if it \
is very long result in a Bluesky post that exceeds the size \
allowed by the protocol."""
[taxonomies]
tags = ["Tags",
"Should be",
"Generated as",
"Appropriate to the ",
"Requirements of ",
"The post on",
"The website",
"And can be",
"As extensive as",
"Required.",
"They will be",
"Converted to",
"Hashtags"
"And contribute to"
"The size of the post."
]
[bluesky]
description = """\
This description will be preferred allowing an edited version \
of the description to ensure the Bluesky post can be kept \
within the limits of the protocol."""
tags = ["Likewise",
"this tags section",
"will be preferred"]
+++
By generating compact referrer URLs (like https://www.example.com/s/A4t5rb.html
instead of https://www.example.com/blog/gen-bsky-release-version-1.3.0/
), you gain
valuable characters for:
The following example demonstrates the complete drafting workflow—from building the post structure to generating both the short URL referrer and the final Bluesky post content.
# use std::path::PathBuf;
#
# use url::Url;
# use toml::value::Datetime;
#
# use gen_bsky::{Draft, DraftError};
#
# #[tokio::main]
# async fn main() -> Result<(), DraftError> {
let base_url = Url::parse("https://www.example.com/")?;
let paths = vec!["content/blog".to_string()];
let date = Datetime {
date: Some(toml::value::Date{
year: 2025,
month: 8,
day: 4}),
time: None,
offset: None};
let allow_draft = false;
let mut posts = get_post_drafts(
base_url,
paths,
date,
allow_draft).await?;
posts.write_referrers(None)?;
posts.write_bluesky_posts(None).await?;
Ok(())
}
async fn get_post_drafts(
base_url: Url,
paths: Vec<String>,
date: Datetime,
allow_draft: bool) -> Result<Draft, DraftError>
{
let post_store = PathBuf::new().join("bluesky_post_store");
let referrer_store = PathBuf::new().join("static").join("s");
let mut builder = Draft::builder(base_url, None);
// Add the paths specified at the command line.
for path in paths.iter() {
builder.add_path_or_file(path)?;
}
// Set the filters for blog posts
builder
.with_minimum_date(date)?
.with_allow_draft(allow_draft);
// Create the `Draft` structure to write the files
let mut drafter = builder.build().await?;
drafter.write_referrers(Some(referrer_store))?
.write_bluesky_posts(Some(post_store))?;
}
The post files generated in the previous example are processed through the following workflow:
# use gen_bsky::{Post, PostError};
#
# #[tokio::main]
# async fn main() -> Result<(), PostError> {
let id = "bluesky_identifier";
let pw = "bluesky_password";
let store = "bluesky_post_store";
let mut poster = Post::new(id, pw)?;
let deleted = poster
.load(store)?
.post_to_bluesky()
.await?
.delete_posted_posts()?
.count_deleted();
println!("{deleted} post sent to bluesky and deleted from the {store}");
Ok(())
# }
Licensed under either of
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.