mastodon-markdown-archive/files/files.go

286 lines
5.1 KiB
Go
Raw Normal View History

2024-05-13 08:47:26 +00:00
package files
import (
2024-05-19 13:00:55 +00:00
"bytes"
2024-05-13 08:47:26 +00:00
"embed"
"fmt"
"io"
"mime"
"net/http"
"os"
"path/filepath"
2024-05-19 13:00:55 +00:00
"strings"
2024-05-13 08:47:26 +00:00
"text/template"
"git.garrido.io/gabriel/mastodon-markdown-archive/client"
md "github.com/JohannesKaufmann/html-to-markdown"
)
//go:embed templates/post.tmpl
var templates embed.FS
type FileWriter struct {
dir string
}
type TemplateContext struct {
2024-05-18 13:22:23 +00:00
Post *client.Post
2024-05-13 08:47:26 +00:00
}
2024-05-19 13:00:55 +00:00
type FilenameDate struct {
Year int
Month string
Day string
}
type FilenameTemplateContext struct {
Post *client.Post
Date FilenameDate
}
2024-05-13 08:47:26 +00:00
type PostFile struct {
Dir string
Name string
File *os.File
}
func New(dir string) (FileWriter, error) {
var fileWriter FileWriter
_, err := os.Stat(dir)
if os.IsNotExist(err) {
os.Mkdir(dir, os.ModePerm)
}
absDir, err := filepath.Abs(dir)
if err != nil {
return fileWriter, err
}
return FileWriter{
2024-05-18 14:08:07 +00:00
dir: absDir,
2024-05-13 08:47:26 +00:00
}, nil
}
2024-05-19 13:00:55 +00:00
func (f FileWriter) Write(post *client.Post, templateFile, filenameTemplate string) error {
2024-05-13 08:47:26 +00:00
hasMedia := len(post.AllMedia()) > 0
2024-05-19 13:00:55 +00:00
postFile, err := f.createFile(post, hasMedia, filenameTemplate)
2024-05-13 08:47:26 +00:00
if err != nil {
return err
}
defer postFile.File.Close()
if len(post.MediaAttachments) > 0 {
err = downloadAttachments(post.MediaAttachments, postFile.Dir)
if err != nil {
return err
}
}
for _, descendant := range post.Descendants() {
if len(descendant.MediaAttachments) > 0 {
err = downloadAttachments(descendant.MediaAttachments, postFile.Dir)
if err != nil {
return err
}
}
}
tmpl, err := resolveTemplate(templateFile)
context := TemplateContext{
Post: post,
}
err = tmpl.Execute(postFile.File, context)
if err != nil {
return err
}
return nil
}
2024-05-19 13:00:55 +00:00
func formatFilename(post *client.Post, filenameTemplate string) (string, error) {
tmplString := "{{.Post.Id}}"
if filenameTemplate != "" {
tmplString = filenameTemplate
}
tmpl := template.Must(template.New("filename").Parse(tmplString))
year, month, day := post.CreatedAt.Date()
filenameData := FilenameTemplateContext{
Post: post,
Date: FilenameDate{
Year: year,
Month: fmt.Sprintf("%02d", int(month)),
Day: fmt.Sprintf("%02d", day),
},
}
var nameBuffer bytes.Buffer
if err := tmpl.Execute(&nameBuffer, filenameData); err != nil {
return "", err
}
return nameBuffer.String(), nil
}
func (f FileWriter) createFile(post *client.Post, shouldBundle bool, filenameTemplate string) (PostFile, error) {
2024-05-13 08:47:26 +00:00
var postFile PostFile
2024-05-19 13:00:55 +00:00
outputFilename, err := formatFilename(post, filenameTemplate)
extension := filepath.Ext(outputFilename)
if extension == "" {
extension = ".md"
} else {
outputFilename = strings.TrimSuffix(outputFilename, extension)
}
if err != nil {
return postFile, err
}
2024-05-13 08:47:26 +00:00
if shouldBundle {
2024-05-19 13:00:55 +00:00
dir := filepath.Join(f.dir, outputFilename)
2024-05-13 08:47:26 +00:00
_, err := os.Stat(dir)
if os.IsNotExist(err) {
os.Mkdir(dir, os.ModePerm)
}
2024-05-19 13:00:55 +00:00
name := filepath.Join(dir, fmt.Sprintf("index%s", extension))
2024-05-13 08:47:26 +00:00
file, err := os.Create(name)
if err != nil {
return postFile, err
}
postFile = PostFile{
Name: name,
Dir: dir,
File: file,
}
return postFile, nil
}
2024-05-19 13:00:55 +00:00
name := filepath.Join(f.dir, fmt.Sprintf("%s%s", outputFilename, extension))
2024-05-13 08:47:26 +00:00
file, err := os.Create(name)
if err != nil {
return postFile, err
}
postFile = PostFile{
Name: name,
Dir: f.dir,
File: file,
}
return postFile, nil
}
func downloadAttachments(attachments []client.MediaAttachment, dir string) error {
for i := 0; i < len(attachments); i++ {
media := &attachments[i]
if media.Type != "image" {
continue
}
imageFilename, err := downloadAttachment(dir, media.Id, media.URL)
if err != nil {
return err
}
media.Path = imageFilename
}
return nil
}
func downloadAttachment(dir string, id string, url string) (string, error) {
var filename string
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Accept", "image/*")
res, err := client.Do(req)
if err != nil {
return filename, err
}
defer res.Body.Close()
contentType := res.Header.Get("Content-Type")
extensions, err := mime.ExtensionsByType(contentType)
if err != nil {
return filename, err
}
var extension string
urlExtension := filepath.Ext(url)
for _, i := range extensions {
if i == urlExtension {
extension = i
break
}
}
if extension == "" {
return filename, fmt.Errorf("could not match extension for media")
}
filename = fmt.Sprintf("%s%s", id, extension)
file, err := os.Create(filepath.Join(dir, filename))
if err != nil {
return filename, err
}
defer file.Close()
_, err = io.Copy(file, res.Body)
if err != nil {
return filename, err
}
return filename, nil
}
func resolveTemplate(templateFile string) (*template.Template, error) {
converter := md.NewConverter("", true, nil)
funcs := template.FuncMap{
"tomd": converter.ConvertString,
}
if templateFile == "" {
tmpl, err := template.New("post.tmpl").Funcs(funcs).ParseFS(templates, "templates/*.tmpl")
if err != nil {
return tmpl, err
}
return tmpl, nil
}
tmpl, err := template.New(filepath.Base(templateFile)).Funcs(funcs).ParseGlob(templateFile)
if err != nil {
return tmpl, err
}
return tmpl, nil
}