Skip to content

Commit

Permalink
Post on Mastodon when a new blog post is published (#6)
Browse files Browse the repository at this point in the history
* test autotoot runs

* Add push trigger for autotoot branch

* fix i hope

* dumb

* xtra dumb

* 2xtra dumb

* test

* .

* .

* .

* .

* .

* perfected

* IMPROVEMENTS

* .

* .

* .

* WORKING PERFECTLY

* polish and test

* .

* TEST

* minor fix

* prepare
  • Loading branch information
tobyscott25 authored Nov 14, 2023
1 parent 5debde6 commit 659678c
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,26 @@ jobs:

- name: Invalidate CloudFront distribution cache
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*"

post-to-mastodon:
name: Post to Mastodon
needs: build-and-deploy
runs-on: ubuntu-22.04
if: ${{ contains(github.event.head_commit.modified, 'content/posts/') }}

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # fetch all history for all tags and branches
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21"
- run: go mod download
- name: Auto-toot
env:
MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
MASTODON_ORIGIN: ${{ secrets.MASTODON_ORIGIN }}
BLOG_ORIGIN: ${{ secrets.BLOG_ORIGIN }}
working-directory: ./auto-toot
run: go run main.go
11 changes: 11 additions & 0 deletions auto-toot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Auto-toot

This is a simple Go app that checks the latest commit for new files added within the `/content/posts` directory. If there are new files, it will parse the Hugo blog post and automatically publish a Mastodon toot with the description, hashtags and link to the post.

### Environment Variables

| Variable | Description | Example |
| ----------------------- | ---------------------------------------------------------- | --------------------------- |
| `MASTODON_ACCESS_TOKEN` | The access token for your Mastodon account. | `a1b2c3d4e5f6g7h8i9j0` |
| `MASTODON_ORIGIN` | The origin for your Mastodon instance. | `https://mas.to` |
| `BLOG_ORIGIN` | The origin used when generating the link to the blog post. | `https://www.tobyscott.dev` |
3 changes: 3 additions & 0 deletions auto-toot/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module auto-toot

go 1.21.1
24 changes: 24 additions & 0 deletions auto-toot/helpers/getNewFilesInLastCommit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package helpers

import (
"bytes"
"fmt"
"os/exec"
"strings"
)

// Returns a slice of strings representing the file paths of newly created files in the last commit.
func GetNewFilesInLastCommit() ([]string, error) {
// Use 'git diff' to get a list of added files in the last commit
cmd := exec.Command("git", "diff", "--diff-filter=A", "--name-only", "HEAD~1", "HEAD")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, fmt.Errorf("error running git diff: %v", err)
}

// Split the output by new lines to get individual file paths
files := strings.Split(strings.TrimSpace(out.String()), "\n")
return files, nil
}
98 changes: 98 additions & 0 deletions auto-toot/helpers/parseHugoPost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package helpers

import (
"bufio"
"fmt"
"net/url"
"os"
"regexp"
"strings"
)

type HugoPost struct {
Description string `json:"description"`
Tags []string `json:"tags"`
URL string `json:"url"`
}

func ParseHugoPost(filePath string, blogUrl string) (HugoPost, error) {

file, err := os.Open(filePath)
if err != nil {
fmt.Printf("Error opening file: %v\n", err)
return HugoPost{}, err
}
defer file.Close()

var fileContent strings.Builder
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fileContent.WriteString(scanner.Text() + "\n")
}
if err := scanner.Err(); err != nil {
fmt.Printf("Error reading file: %v\n", err)
return HugoPost{}, err
}

// Convert the builder to a string for regex processing
contentString := fileContent.String()

// Convert the file path to a blog post URL
postSlug := strings.TrimPrefix(filePath, "../content/posts/")
postSlug = strings.TrimSuffix(postSlug, ".md")
postSlug = strings.TrimSuffix(postSlug, "/index") // Remove '/index' if present

// Use url.QueryEscape to encode spaces and other characters in the post slug
postSlug = url.QueryEscape(postSlug)

blogPostURL := fmt.Sprintf(blogUrl+"/posts/%s", postSlug)

// Use regex to find the description block
descriptionRegex := regexp.MustCompile(`description: "(.*?)"`)
descriptionMatch := descriptionRegex.FindStringSubmatch(contentString)

description := ""
if len(descriptionMatch) > 1 {
description = descriptionMatch[1]
}

// Use regex to find the tags block (taking new lines into account)
tagsRegex := regexp.MustCompile(`(?s)tags:\s+\[(.*?)\]`)
tagsMatch := tagsRegex.FindStringSubmatch(contentString)

// Process tags
var tags []string
if len(tagsMatch) > 1 {
// Remove the square brackets and split the string by comma
tagsStr := strings.Trim(tagsMatch[1], "[]")
tagsStr = strings.Replace(tagsStr, " \"", "\"", -1) // Remove 4-space indentations
tagsStr = strings.Replace(tagsStr, "\"", "", -1) // Remove quotes
tagsStr = strings.Replace(tagsStr, "\n", "", -1) // Remove newlines
tagsStr = strings.Replace(tagsStr, " ", "", -1) // Remove spaces

// Remove trailing comma
if strings.HasSuffix(tagsStr, ",") {
tagsStr = tagsStr[:len(tagsStr)-1]
}
tags = strings.Split(tagsStr, ",")

}

post := HugoPost{
Description: description,
Tags: tags,
URL: blogPostURL,
}

return post, nil
}

func (p HugoPost) GetHashtagString() string {
var hashtags []string
for _, tag := range p.Tags {
hashtags = append(hashtags, fmt.Sprintf("#%s", tag))
}
hashtagsStr := strings.Join(hashtags, " ")
hashtagsStr = strings.TrimSpace(hashtagsStr) // Trim the trailing space
return hashtagsStr
}
63 changes: 63 additions & 0 deletions auto-toot/helpers/sendToot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package helpers

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)

// Structure to parse the response from Mastodon API
type MastodonStatusResponse struct {
URL string `json:"url"`
}

func SendToot(mastodonURL string, accessToken string, status string) error {
data := url.Values{}
data.Set("status", status)

// Create a new request
req, err := http.NewRequest("POST", mastodonURL+"/api/v1/statuses", strings.NewReader(data.Encode()))
if err != nil {
fmt.Printf("Error creating request: %v\n", err)
return err
}

// Set headers
req.Header.Set("Authorization", "Bearer "+accessToken)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Error posting to Mastodon: %v\n", err)
return err
}
defer resp.Body.Close()

// Check the response
if resp.StatusCode != http.StatusOK {
fmt.Printf("Error response from Mastodon: %v\n", resp.Status)
return err
}

// Read response body
respBody, error := io.ReadAll(resp.Body)
if error != nil {
fmt.Println(error)
}

// Parse the JSON response
var mastodonResp MastodonStatusResponse
if err := json.Unmarshal(respBody, &mastodonResp); err != nil {
fmt.Printf("Error parsing JSON response: %v\n", err)
return err
}

fmt.Printf("Successfully posted to Mastodon: %s \n", mastodonResp.URL)

return nil
}
45 changes: 45 additions & 0 deletions auto-toot/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"auto-toot/helpers"
"fmt"
"os"
"strings"
)

func main() {

mastodonOrigin := os.Getenv("MASTODON_ORIGIN")
blogOrigin := os.Getenv("BLOG_ORIGIN")
accessToken := os.Getenv("MASTODON_ACCESS_TOKEN")

newFiles, err := helpers.GetNewFilesInLastCommit()
if err != nil {
fmt.Printf("Error getting new files: %v\n", err)
return
}

for _, file := range newFiles {

if strings.HasPrefix(file, "content/posts/") {

filePath := "../" + file

hugoPostDetails, err := helpers.ParseHugoPost(filePath, blogOrigin)
if err != nil {
fmt.Printf("Error parsing Hugo post: %v\n", err)
return
}

hashtagString := hugoPostDetails.GetHashtagString()
status := fmt.Sprintf("%s\n\n%s\n\n%s", hugoPostDetails.Description, hugoPostDetails.URL, hashtagString)

helpers.SendToot(mastodonOrigin, accessToken, status)
if err != nil {
fmt.Printf("Error posting about %s to Mastodon: %v\n", filePath, err)
} else {
fmt.Printf("Successfully posted about %s to Mastodon.\n", filePath)
}
}
}
}
5 changes: 5 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
go 1.21.1

use (
./auto-toot
)

0 comments on commit 659678c

Please sign in to comment.