2022-08-03 14:55:48 +00:00
|
|
|
package owl
|
2022-07-21 17:02:37 +00:00
|
|
|
|
|
|
|
import (
|
2022-07-21 17:44:07 +00:00
|
|
|
"bytes"
|
2022-08-23 15:59:17 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/base64"
|
2022-09-01 19:34:33 +00:00
|
|
|
"fmt"
|
2022-07-21 17:02:37 +00:00
|
|
|
"io/ioutil"
|
2022-08-23 15:59:17 +00:00
|
|
|
"os"
|
2022-07-21 17:02:37 +00:00
|
|
|
"path"
|
2022-09-01 19:53:06 +00:00
|
|
|
"sort"
|
2022-07-21 17:44:07 +00:00
|
|
|
|
|
|
|
"github.com/yuin/goldmark"
|
2022-07-27 19:26:37 +00:00
|
|
|
"github.com/yuin/goldmark/extension"
|
2022-07-21 17:44:07 +00:00
|
|
|
"github.com/yuin/goldmark/parser"
|
2022-08-21 09:31:48 +00:00
|
|
|
"github.com/yuin/goldmark/renderer/html"
|
2022-08-13 17:07:10 +00:00
|
|
|
"gopkg.in/yaml.v2"
|
2022-07-21 17:02:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Post struct {
|
2022-08-22 19:15:36 +00:00
|
|
|
user *User
|
|
|
|
id string
|
|
|
|
title string
|
|
|
|
metaLoaded bool
|
|
|
|
meta PostMeta
|
2022-07-21 17:02:37 +00:00
|
|
|
}
|
|
|
|
|
2022-08-13 17:07:10 +00:00
|
|
|
type PostMeta struct {
|
|
|
|
Title string `yaml:"title"`
|
|
|
|
Aliases []string `yaml:"aliases"`
|
|
|
|
Date string `yaml:"date"`
|
2022-08-20 20:35:51 +00:00
|
|
|
Draft bool `yaml:"draft"`
|
2022-08-13 17:07:10 +00:00
|
|
|
}
|
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
type PostStatus struct {
|
|
|
|
Webmentions []WebmentionOut
|
|
|
|
}
|
|
|
|
|
2022-08-03 17:41:13 +00:00
|
|
|
func (post Post) Id() string {
|
|
|
|
return post.id
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:02:37 +00:00
|
|
|
func (post Post) Dir() string {
|
|
|
|
return path.Join(post.user.Dir(), "public", post.id)
|
|
|
|
}
|
|
|
|
|
2022-08-01 17:50:29 +00:00
|
|
|
func (post Post) MediaDir() string {
|
|
|
|
return path.Join(post.Dir(), "media")
|
|
|
|
}
|
|
|
|
|
2022-08-23 15:59:17 +00:00
|
|
|
func (post Post) WebmentionDir() string {
|
|
|
|
return path.Join(post.Dir(), "webmention")
|
|
|
|
}
|
|
|
|
|
2022-07-24 13:34:52 +00:00
|
|
|
func (post Post) UrlPath() string {
|
2022-08-03 17:41:13 +00:00
|
|
|
return post.user.UrlPath() + "posts/" + post.id + "/"
|
|
|
|
}
|
|
|
|
|
2022-08-13 16:47:27 +00:00
|
|
|
func (post Post) FullUrl() string {
|
|
|
|
return post.user.FullUrl() + "posts/" + post.id + "/"
|
|
|
|
}
|
|
|
|
|
2022-08-03 17:41:13 +00:00
|
|
|
func (post Post) UrlMediaPath(filename string) string {
|
|
|
|
return post.UrlPath() + "media/" + filename
|
2022-07-23 15:19:47 +00:00
|
|
|
}
|
|
|
|
|
2022-07-21 17:44:07 +00:00
|
|
|
func (post Post) Title() string {
|
|
|
|
return post.title
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:02:37 +00:00
|
|
|
func (post Post) ContentFile() string {
|
|
|
|
return path.Join(post.Dir(), "index.md")
|
|
|
|
}
|
|
|
|
|
2022-08-22 19:15:36 +00:00
|
|
|
func (post *Post) Meta() PostMeta {
|
|
|
|
if !post.metaLoaded {
|
|
|
|
post.LoadMeta()
|
|
|
|
}
|
|
|
|
return post.meta
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:02:37 +00:00
|
|
|
func (post Post) Content() []byte {
|
|
|
|
// read file
|
|
|
|
data, _ := ioutil.ReadFile(post.ContentFile())
|
|
|
|
return data
|
|
|
|
}
|
2022-07-21 17:44:07 +00:00
|
|
|
|
2022-08-22 19:15:36 +00:00
|
|
|
func (post Post) RenderedContent() bytes.Buffer {
|
2022-07-21 17:44:07 +00:00
|
|
|
data := post.Content()
|
2022-08-13 17:07:10 +00:00
|
|
|
|
2022-08-22 19:15:36 +00:00
|
|
|
// trim yaml block
|
|
|
|
// TODO this can be done nicer
|
2022-08-13 17:07:10 +00:00
|
|
|
trimmedData := bytes.TrimSpace(data)
|
|
|
|
// check first line is ---
|
|
|
|
if string(trimmedData[0:4]) == "---\n" {
|
|
|
|
trimmedData = trimmedData[4:]
|
|
|
|
// find --- end
|
|
|
|
end := bytes.Index(trimmedData, []byte("\n---\n"))
|
|
|
|
if end != -1 {
|
|
|
|
data = trimmedData[end+5:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-21 09:31:48 +00:00
|
|
|
options := goldmark.WithRendererOptions()
|
|
|
|
if post.user.repo.AllowRawHtml() {
|
|
|
|
options = goldmark.WithRendererOptions(
|
|
|
|
html.WithUnsafe(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:44:07 +00:00
|
|
|
markdown := goldmark.New(
|
2022-08-21 09:31:48 +00:00
|
|
|
options,
|
2022-07-21 17:44:07 +00:00
|
|
|
goldmark.WithExtensions(
|
2022-08-13 17:07:10 +00:00
|
|
|
// meta.Meta,
|
2022-07-27 19:26:37 +00:00
|
|
|
extension.GFM,
|
2022-07-21 17:44:07 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
var buf bytes.Buffer
|
|
|
|
context := parser.NewContext()
|
|
|
|
if err := markdown.Convert(data, &buf, parser.WithContext(context)); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2022-08-22 19:15:36 +00:00
|
|
|
return buf
|
2022-07-21 17:44:07 +00:00
|
|
|
|
|
|
|
}
|
2022-08-06 17:38:13 +00:00
|
|
|
|
|
|
|
func (post Post) Aliases() []string {
|
2022-08-22 19:15:36 +00:00
|
|
|
return post.Meta().Aliases
|
|
|
|
}
|
|
|
|
|
|
|
|
func (post *Post) LoadMeta() error {
|
|
|
|
data := post.Content()
|
|
|
|
|
|
|
|
// get yaml metadata block
|
|
|
|
meta := PostMeta{}
|
|
|
|
trimmedData := bytes.TrimSpace(data)
|
|
|
|
// check first line is ---
|
|
|
|
if string(trimmedData[0:4]) == "---\n" {
|
|
|
|
trimmedData = trimmedData[4:]
|
|
|
|
// find --- end
|
|
|
|
end := bytes.Index(trimmedData, []byte("\n---\n"))
|
|
|
|
if end != -1 {
|
|
|
|
metaData := trimmedData[:end]
|
|
|
|
err := yaml.Unmarshal(metaData, &meta)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
post.meta = meta
|
|
|
|
return nil
|
2022-08-06 17:38:13 +00:00
|
|
|
}
|
2022-08-23 15:59:17 +00:00
|
|
|
|
2022-09-01 19:34:33 +00:00
|
|
|
func (post *Post) WebmentionFile(source string) string {
|
2022-08-31 18:43:01 +00:00
|
|
|
|
2022-08-23 15:59:17 +00:00
|
|
|
hash := sha256.Sum256([]byte(source))
|
|
|
|
hashStr := base64.URLEncoding.EncodeToString(hash[:])
|
2022-09-01 19:34:33 +00:00
|
|
|
return path.Join(post.WebmentionDir(), hashStr+".yml")
|
|
|
|
}
|
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
func (post *Post) PersistWebmention(webmention WebmentionIn) error {
|
2022-09-01 19:34:33 +00:00
|
|
|
// ensure dir exists
|
|
|
|
os.MkdirAll(post.WebmentionDir(), 0755)
|
|
|
|
|
|
|
|
// write to file
|
|
|
|
fileName := post.WebmentionFile(webmention.Source)
|
|
|
|
data, err := yaml.Marshal(webmention)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return os.WriteFile(fileName, data, 0644)
|
|
|
|
}
|
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
func (post *Post) Webmention(source string) (WebmentionIn, error) {
|
2022-09-01 19:34:33 +00:00
|
|
|
// ensure dir exists
|
|
|
|
os.MkdirAll(post.WebmentionDir(), 0755)
|
|
|
|
|
|
|
|
// Check if file exists
|
|
|
|
fileName := post.WebmentionFile(source)
|
|
|
|
if !fileExists(fileName) {
|
|
|
|
// return error if file doesn't exist
|
2022-09-04 13:03:16 +00:00
|
|
|
return WebmentionIn{}, fmt.Errorf("Webmention file not found: %s", source)
|
2022-09-01 19:34:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := os.ReadFile(fileName)
|
|
|
|
if err != nil {
|
2022-09-04 13:03:16 +00:00
|
|
|
return WebmentionIn{}, err
|
2022-09-01 19:34:33 +00:00
|
|
|
}
|
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
mention := WebmentionIn{}
|
2022-09-01 19:34:33 +00:00
|
|
|
err = yaml.Unmarshal(data, &mention)
|
|
|
|
if err != nil {
|
2022-09-04 13:03:16 +00:00
|
|
|
return WebmentionIn{}, err
|
2022-09-01 19:34:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return mention, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (post *Post) AddWebmention(source string) error {
|
2022-08-24 20:11:39 +00:00
|
|
|
// Check if file already exists
|
2022-09-01 19:34:33 +00:00
|
|
|
_, err := post.Webmention(source)
|
|
|
|
if err != nil {
|
2022-09-04 13:03:16 +00:00
|
|
|
webmention := WebmentionIn{
|
2022-09-01 19:34:33 +00:00
|
|
|
Source: source,
|
|
|
|
}
|
|
|
|
defer post.EnrichWebmention(source)
|
|
|
|
return post.PersistWebmention(webmention)
|
2022-08-24 20:11:39 +00:00
|
|
|
}
|
2022-09-01 19:34:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (post *Post) EnrichWebmention(source string) error {
|
2022-08-31 18:01:58 +00:00
|
|
|
html, err := post.user.repo.Retriever.Get(source)
|
|
|
|
if err == nil {
|
2022-09-01 19:34:33 +00:00
|
|
|
webmention, err := post.Webmention(source)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-08-31 18:01:58 +00:00
|
|
|
entry, err := post.user.repo.Parser.ParseHEntry(html)
|
|
|
|
if err == nil {
|
2022-09-01 19:34:33 +00:00
|
|
|
webmention.Title = entry.Title
|
|
|
|
return post.PersistWebmention(webmention)
|
2022-08-31 18:01:58 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-01 19:34:33 +00:00
|
|
|
return err
|
2022-08-23 15:59:17 +00:00
|
|
|
}
|
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
func (post *Post) Webmentions() []WebmentionIn {
|
2022-08-31 18:43:01 +00:00
|
|
|
// ensure dir exists
|
|
|
|
os.MkdirAll(post.WebmentionDir(), 0755)
|
2022-09-01 19:34:33 +00:00
|
|
|
files := listDir(post.WebmentionDir())
|
2022-09-04 13:03:16 +00:00
|
|
|
webmentions := []WebmentionIn{}
|
2022-09-01 19:34:33 +00:00
|
|
|
for _, file := range files {
|
|
|
|
data, err := os.ReadFile(path.Join(post.WebmentionDir(), file))
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
2022-09-04 13:03:16 +00:00
|
|
|
mention := WebmentionIn{}
|
2022-09-01 19:34:33 +00:00
|
|
|
err = yaml.Unmarshal(data, &mention)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
webmentions = append(webmentions, mention)
|
|
|
|
}
|
2022-08-31 18:43:01 +00:00
|
|
|
|
2022-09-01 19:34:33 +00:00
|
|
|
return webmentions
|
2022-08-23 15:59:17 +00:00
|
|
|
}
|
2022-09-01 19:53:06 +00:00
|
|
|
|
2022-09-04 13:03:16 +00:00
|
|
|
func (post *Post) ApprovedWebmentions() []WebmentionIn {
|
2022-09-01 19:53:06 +00:00
|
|
|
webmentions := post.Webmentions()
|
2022-09-04 13:03:16 +00:00
|
|
|
approved := []WebmentionIn{}
|
2022-09-01 19:53:06 +00:00
|
|
|
for _, webmention := range webmentions {
|
|
|
|
if webmention.ApprovalStatus == "approved" {
|
|
|
|
approved = append(approved, webmention)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort by retrieved date
|
|
|
|
sort.Slice(approved, func(i, j int) bool {
|
|
|
|
return approved[i].RetrievedAt.After(approved[j].RetrievedAt)
|
|
|
|
})
|
|
|
|
return approved
|
|
|
|
}
|