Because I am lazy

Everytime I publish a blog, I want to announce it across the internet. Although I feel I waste my time doing so. Therefore, I dedicate a blog post on how I managed to automate it.

Which for me means…

I want my GitHub account’s main page and mastodon account to be ✨automagically✨updated/tooted/announced with the info of any newly fresh blog post there is and will ever be.

Update GitHub repo’s README.md

Since I am using Hugo on this website, it conveniently comes with an Atom’s feed out of the box. With it, one can check the latest post’s metadata (e.g. title and URL).

Then, with the help of some golang magic:

  1. Using the library gofeed, retrieve and parse the feed’s metadata.
1    fp := gofeed.NewParser()
2    feed, err := fp.ParseURL("https://masagu.dev/index.xml")
  1. Since Items contain all blog post’s metadata, ordered from most recent, the first item matches the latest blog entry.
1    blogItem := feed.Items[0]
  1. From the original README.md, divided in two static parts: PRE-README.md and POST-README.md, content will be used to paste the retrieved info in the right order.
1    pre, err := ReadFileAsString("./PREREADME.md")
2    blog := "- Latest blog post :page_facing_up: [" + blogItem.Title + "](" + blogItem.Link + ")"
3	post, err := ReadFileAsString("./POSTREADME.md")
4	data := fmt.Sprintf("%s\n%s\n%s", pre, blog, post)
  1. The final parsed string will be then stored in a new README.md file. And that’s it, yay!

Call Mastodon’s API

Following Mastodon’s API specs, I ended up making a golang simple bot to toot the latest blog post information to Mastodon.

Output from the previous go script (title and URL) will serve as input for the new toot’s message. For that, I have used the flag library,

1var msgArg, visibilityArg string
2flag.StringVar(&msgArg, "message", "my test message", "message to toot")
3flag.StringVar(&visibilityArg, "visibility", "unlisted", "visibility flag")

so inputs can be easily given on cli

1go run main.go --message='my message' --visibility='public'

Mastodon’s API offer two auth token types: User authentication token and app authentication token. Write scopes aren’t allowed for app authenticated tokens obtained through grant_type:client_credentials. Write scope is required for calling POST /api/v1/statuses used to toot a message.

For that, Mastodon’s API oauth model offers either:

  • app-authorized short-lived token:
1	params := url.Values{
2		"client_id":     {c.Config.ClientID},
3		"client_secret": {c.Config.ClientSecret},
4		"grant_type":    {"authorization_code"},
5		"redirect_uri":  {"urn:ietf:wg:oauth:2.0:oob"},
6		"scope":         {"read write"},
7		"code":          {c.Config.ClientCode},
8	}
  • or, long term bearer token, used directly to call the API:
 1	request := MastodonClientRequest{
 2		RequestBody: bytes.NewBuffer(payload),
 3		Headers: http.Header{
 4			"Content-Type":  {"application/json"},
 5			"Authorization": {"Bearer " + c.Config.AccessToken},
 6		},
 7		Prefix: "Post Toot",
 8		Path:   "/api/v1/statuses",
 9		Method: http.MethodPost,
10	}

Orchestrate results on GitHub Workflows

On GitHub Workflows, a pipeline scheduled to run every once per week will execute both golang scripts. Steps of this job are:

  1. Test go scripts and oauth credentials
  2. Generate README.md with new blog post information
  3. If README contains new info, push it to the repository
  4. If file’s pushed, toot info on my Mastodon account. Important fact is to store Mastodon Credentials at a repository level, as GitHub Actions secrets.

Differences with the current README.md and the generated one will be checked, making use of git:

1  if ! git diff-index --quiet HEAD; then
2    git commit -am "Update README.md with new blogpost!"
3    git push --all -f https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git
4  fi

If the current README.md looks the same, the pipeline won’t commit anything to the repository, and will prevent from spectacularly failing with code 1.

Sources of the go scripts as well as the Github Workflows are located at my main repository https://www.github.com/mariasalcedo/mariasalcedo, if you want to check them out in detail.

Thanks for reading this post, I hope you enjoyed it! I look forward to your comments :)