2020-10-06 17:07:48 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-04-22 13:49:22 +00:00
|
|
|
"cmp"
|
2023-12-19 11:15:30 +00:00
|
|
|
"crypto/sha256"
|
2020-10-06 17:07:48 +00:00
|
|
|
"errors"
|
2022-06-15 12:44:12 +00:00
|
|
|
"fmt"
|
2023-12-19 11:15:30 +00:00
|
|
|
"io"
|
2021-06-23 17:20:50 +00:00
|
|
|
"mime"
|
2023-12-19 11:15:30 +00:00
|
|
|
"mime/multipart"
|
2020-10-06 17:07:48 +00:00
|
|
|
"net/http"
|
2023-12-19 11:15:30 +00:00
|
|
|
urlpkg "net/url"
|
|
|
|
"path/filepath"
|
|
|
|
"reflect"
|
2020-10-14 16:23:56 +00:00
|
|
|
"regexp"
|
2020-10-06 17:07:48 +00:00
|
|
|
"strings"
|
|
|
|
|
2022-03-16 07:28:03 +00:00
|
|
|
"github.com/samber/lo"
|
2020-10-06 17:07:48 +00:00
|
|
|
"github.com/spf13/cast"
|
2023-12-19 11:15:30 +00:00
|
|
|
"go.goblog.app/app/pkgs/bodylimit"
|
|
|
|
"go.hacdias.com/indielib/micropub"
|
2020-10-06 17:07:48 +00:00
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (a *goBlog) getMicropubImplementation() *micropubImplementation {
|
|
|
|
if a.mpImpl == nil {
|
|
|
|
a.mpImpl = µpubImplementation{a: a}
|
|
|
|
}
|
|
|
|
return a.mpImpl
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
micropubPath = "/micropub"
|
|
|
|
micropubMediaSubPath = "/media"
|
|
|
|
)
|
|
|
|
|
|
|
|
type micropubImplementation struct {
|
|
|
|
a *goBlog
|
|
|
|
h http.Handler
|
|
|
|
mh http.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) getHandler() http.Handler {
|
|
|
|
if s.h == nil {
|
|
|
|
s.h = micropub.NewHandler(
|
|
|
|
s,
|
|
|
|
micropub.WithMediaEndpoint(s.a.getFullAddress(micropubPath+micropubMediaSubPath)),
|
|
|
|
micropub.WithGetCategories(s.getCategories),
|
|
|
|
micropub.WithGetChannels(s.getChannels),
|
|
|
|
micropub.WithGetVisibility(s.getVisibility),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return s.h
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) getCategories() []string {
|
|
|
|
allCategories := []string{}
|
|
|
|
for blog := range s.a.cfg.Blogs {
|
|
|
|
values, _ := s.a.db.allTaxonomyValues(blog, s.a.cfg.Micropub.CategoryParam)
|
|
|
|
allCategories = append(allCategories, values...)
|
2022-02-26 19:38:52 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return lo.Uniq(allCategories)
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) getChannels() []micropub.Channel {
|
|
|
|
allChannels := []micropub.Channel{}
|
|
|
|
for b, bc := range s.a.cfg.Blogs {
|
|
|
|
allChannels = append(allChannels, micropub.Channel{
|
|
|
|
Name: fmt.Sprintf("%s: %s", b, bc.Title),
|
|
|
|
UID: b,
|
2022-09-23 12:15:29 +00:00
|
|
|
})
|
|
|
|
for s, sc := range bc.Sections {
|
2023-12-19 11:15:30 +00:00
|
|
|
allChannels = append(allChannels, micropub.Channel{
|
|
|
|
Name: fmt.Sprintf("%s/%s: %s", b, s, sc.Name),
|
|
|
|
UID: fmt.Sprintf("%s/%s", b, s),
|
2022-09-23 12:15:29 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return allChannels
|
2022-09-23 12:15:29 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) getVisibility() []string {
|
|
|
|
return []string{string(visibilityPrivate), string(visibilityUnlisted), string(visibilityPublic)}
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) getMediaHandler() http.Handler {
|
|
|
|
if s.mh == nil {
|
|
|
|
s.mh = micropub.NewMediaHandler(
|
|
|
|
s.UploadMedia,
|
|
|
|
s.HasScope,
|
|
|
|
micropub.WithMaxMemory(0),
|
|
|
|
micropub.WithMaxMediaSize(30*bodylimit.MB),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return s.mh
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) HasScope(r *http.Request, scope string) bool {
|
|
|
|
return strings.Contains(r.Context().Value(indieAuthScope).(string), scope)
|
|
|
|
}
|
2020-10-14 16:23:56 +00:00
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) Source(urlStr string) (map[string]any, error) {
|
|
|
|
url, err := urlpkg.Parse(urlStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
|
|
|
}
|
|
|
|
p, err := s.a.getPost(url.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
|
|
|
}
|
|
|
|
return s.a.postToMfMap(p), nil
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) SourceMany(limit, offset int) ([]map[string]any, error) {
|
|
|
|
posts, err := s.a.getPosts(&postsRequestConfig{
|
|
|
|
limit: limit,
|
|
|
|
offset: offset,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
|
|
|
}
|
|
|
|
list := []map[string]any{}
|
|
|
|
for _, p := range posts {
|
|
|
|
list = append(list, s.a.postToMfMap(p))
|
|
|
|
}
|
|
|
|
return list, nil
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) Create(req *micropub.Request) (string, error) {
|
|
|
|
if req.Type != "h-entry" {
|
|
|
|
return "", fmt.Errorf("%w: only h-entry supported", micropub.ErrNotImplemented)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
entry := &post{}
|
2021-06-23 17:20:50 +00:00
|
|
|
entry.Parameters = map[string][]string{}
|
2023-12-19 11:15:30 +00:00
|
|
|
allValues := lo.Assign(req.Properties, req.Commands)
|
|
|
|
// Parameters with special care
|
|
|
|
for photoNo, photo := range allValues["photo"] {
|
|
|
|
pp := s.a.cfg.Micropub.PhotoParam
|
|
|
|
pdp := s.a.cfg.Micropub.PhotoDescriptionParam
|
|
|
|
if photoLink, isPhotoLink := photo.(string); isPhotoLink {
|
|
|
|
entry.Parameters[pp] = append(entry.Parameters[pp], photoLink)
|
|
|
|
if len(allValues["photo-alt"]) > photoNo && allValues["photo-alt"][photoNo] != nil {
|
|
|
|
entry.Parameters[pdp] = append(entry.Parameters[pdp], cast.ToString(allValues["photo-alt"][photoNo]))
|
|
|
|
} else {
|
|
|
|
entry.Parameters[pdp] = append(entry.Parameters[pdp], "")
|
|
|
|
}
|
|
|
|
} else if photoObject, isPhotoObject := photo.(map[string]any); isPhotoObject {
|
|
|
|
entry.Parameters[pp] = append(entry.Parameters[pp], cast.ToString(photoObject["value"]))
|
|
|
|
entry.Parameters[pdp] = append(entry.Parameters[pdp], cast.ToString(photoObject["alt"]))
|
|
|
|
}
|
2020-12-08 18:36:19 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
delete(allValues, "photo")
|
|
|
|
delete(allValues, "photo-alt")
|
|
|
|
delete(allValues, "file") // Micropublish.net fix
|
|
|
|
// Rest of parameters
|
|
|
|
for key, values := range allValues {
|
|
|
|
values := cast.ToStringSlice(values)
|
|
|
|
if len(values) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch key {
|
|
|
|
case "content":
|
|
|
|
entry.Content = values[0]
|
|
|
|
case "published":
|
|
|
|
entry.Published = values[0]
|
|
|
|
case "updated":
|
|
|
|
entry.Updated = values[0]
|
|
|
|
case "slug":
|
|
|
|
entry.Slug = values[0]
|
|
|
|
case "channel":
|
|
|
|
entry.setChannel(values[0])
|
|
|
|
case "post-status":
|
|
|
|
entry.Status = micropubStatus(values[0])
|
|
|
|
case "visibility":
|
|
|
|
entry.Visibility = micropubVisibility(values[0])
|
|
|
|
default:
|
|
|
|
entry.Parameters[s.mapToParameterName(key)] = values
|
|
|
|
}
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
if err := s.a.extractParamsFromContent(entry); err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-12 17:54:22 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
if err := s.a.createPost(entry); err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-12 17:54:22 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return s.a.fullPostURL(entry), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) Update(req *micropub.Request) (string, error) {
|
|
|
|
// Get post
|
|
|
|
url, err := urlpkg.Parse(req.URL)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2021-06-23 17:20:50 +00:00
|
|
|
}
|
2024-04-22 13:49:22 +00:00
|
|
|
postPath := cmp.Or(url.Path, "/")
|
2023-12-19 11:15:30 +00:00
|
|
|
entry, err := s.a.getPost(postPath)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2022-06-15 12:44:12 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
// Check if post is marked as deleted
|
|
|
|
if entry.Deleted() {
|
|
|
|
return "", fmt.Errorf("%w: post is marked as deleted, undelete it first", micropub.ErrBadRequest)
|
2021-07-14 16:50:24 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
// Update post
|
|
|
|
oldPath := entry.Path
|
|
|
|
oldStatus := entry.Status
|
|
|
|
oldVisibility := entry.Visibility
|
|
|
|
if entry.Parameters == nil {
|
|
|
|
entry.Parameters = map[string][]string{}
|
|
|
|
}
|
|
|
|
// Update properties
|
|
|
|
properties := s.a.postMfProperties(entry, false)
|
|
|
|
properties, err = micropubUpdateMfProperties(properties, req.Updates)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed to update properties: %w", err)
|
2021-07-14 16:50:24 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
s.updatePostPropertiesFromMf(entry, properties)
|
|
|
|
err = s.a.extractParamsFromContent(entry)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
err = s.a.replacePost(entry, oldPath, oldStatus, oldVisibility)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return s.a.fullPostURL(entry), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) Delete(urlStr string) error {
|
|
|
|
url, err := urlpkg.Parse(urlStr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
if err := s.a.deletePost(url.Path); err != nil {
|
|
|
|
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) Undelete(urlStr string) error {
|
|
|
|
url, err := urlpkg.Parse(urlStr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
if err := s.a.undeletePost(url.Path); err != nil {
|
|
|
|
return fmt.Errorf("%w: %w", micropub.ErrBadRequest, err)
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) UploadMedia(file multipart.File, header *multipart.FileHeader) (string, error) {
|
|
|
|
// Generate sha256 hash for file
|
|
|
|
hash := sha256.New()
|
|
|
|
_, err := io.Copy(hash, file)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: failed to get file hash", micropub.ErrBadRequest)
|
|
|
|
}
|
|
|
|
// Get file extension
|
|
|
|
fileExtension := filepath.Ext(header.Filename)
|
|
|
|
if fileExtension == "" {
|
|
|
|
// Find correct file extension if original filename does not contain one
|
|
|
|
mimeType := header.Header.Get(contentType)
|
|
|
|
if len(mimeType) > 0 {
|
|
|
|
allExtensions, _ := mime.ExtensionsByType(mimeType)
|
|
|
|
if len(allExtensions) > 0 {
|
|
|
|
fileExtension = allExtensions[0]
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
// Generate the file name
|
|
|
|
fileName := fmt.Sprintf("%x%s", hash.Sum(nil), fileExtension)
|
|
|
|
// Save file
|
|
|
|
_, err = file.Seek(0, io.SeekStart)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: failed to read multipart file", micropub.ErrBadRequest)
|
|
|
|
}
|
|
|
|
location, err := s.a.saveMediaFile(fileName, file)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%w: failed to save original file", micropub.ErrBadRequest)
|
|
|
|
}
|
|
|
|
// Try to compress file (only when not in private mode)
|
|
|
|
if !s.a.isPrivate() {
|
|
|
|
compressedLocation, compressionErr := s.a.compressMediaFile(location)
|
|
|
|
if compressionErr != nil {
|
|
|
|
return "", fmt.Errorf("%w: failed to compress file: %w", micropub.ErrBadRequest, compressionErr)
|
|
|
|
}
|
|
|
|
// Overwrite location
|
|
|
|
if compressedLocation != "" {
|
|
|
|
location = compressedLocation
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return location, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *micropubImplementation) mapToParameterName(key string) string {
|
|
|
|
switch key {
|
|
|
|
case "name":
|
|
|
|
return "title"
|
|
|
|
case "category":
|
|
|
|
return s.a.cfg.Micropub.CategoryParam
|
|
|
|
case "in-reply-to":
|
|
|
|
return s.a.cfg.Micropub.ReplyParam
|
|
|
|
case "like-of":
|
|
|
|
return s.a.cfg.Micropub.LikeParam
|
|
|
|
case "bookmark-of":
|
|
|
|
return s.a.cfg.Micropub.BookmarkParam
|
|
|
|
case "audio":
|
|
|
|
return s.a.cfg.Micropub.AudioParam
|
|
|
|
case "location":
|
|
|
|
return s.a.cfg.Micropub.LocationParam
|
|
|
|
default:
|
|
|
|
return key
|
|
|
|
}
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2022-06-15 12:44:12 +00:00
|
|
|
func (a *goBlog) extractParamsFromContent(p *post) error {
|
2023-12-19 11:15:30 +00:00
|
|
|
// Ensure parameters map is initialized
|
2021-08-04 20:48:50 +00:00
|
|
|
if p.Parameters == nil {
|
|
|
|
p.Parameters = map[string][]string{}
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
|
|
|
// Normalize line endings in content
|
2020-10-15 15:32:46 +00:00
|
|
|
p.Content = regexp.MustCompile("\r\n").ReplaceAllString(p.Content, "\n")
|
2023-12-19 11:15:30 +00:00
|
|
|
|
|
|
|
// Check for frontmatter
|
2022-02-25 15:29:42 +00:00
|
|
|
if split := strings.Split(p.Content, "---\n"); len(split) >= 3 && strings.TrimSpace(split[0]) == "" {
|
2023-12-19 11:15:30 +00:00
|
|
|
// Extract frontmatter
|
2020-10-06 17:07:48 +00:00
|
|
|
fm := split[1]
|
2022-03-16 07:28:03 +00:00
|
|
|
meta := map[string]any{}
|
2023-12-19 11:15:30 +00:00
|
|
|
if err := yaml.Unmarshal([]byte(fm), &meta); err != nil {
|
2020-10-06 17:07:48 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
|
|
|
// Copy frontmatter to parameters
|
2020-10-06 17:07:48 +00:00
|
|
|
for key, value := range meta {
|
2022-03-16 07:28:03 +00:00
|
|
|
if a, ok := value.([]any); ok {
|
2023-12-19 17:20:55 +00:00
|
|
|
p.Parameters[key] = []string{}
|
2020-10-06 17:07:48 +00:00
|
|
|
for _, ae := range a {
|
2020-10-15 15:32:46 +00:00
|
|
|
p.Parameters[key] = append(p.Parameters[key], cast.ToString(ae))
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-12-19 17:20:55 +00:00
|
|
|
p.Parameters[key] = []string{cast.ToString(value)}
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
2020-10-06 17:07:48 +00:00
|
|
|
// Remove frontmatter from content
|
2020-10-15 15:32:46 +00:00
|
|
|
p.Content = strings.Join(split[2:], "---\n")
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
|
|
|
// Extract specific parameters
|
|
|
|
extractParam := func(paramName string, field any) {
|
|
|
|
if values, ok := p.Parameters[paramName]; len(values) == 1 && ok {
|
|
|
|
if stringPointer, ok := field.(*string); ok {
|
|
|
|
*stringPointer = values[0]
|
|
|
|
} else if stringFunc, ok := field.(func(string)); ok {
|
|
|
|
stringFunc(values[0])
|
|
|
|
}
|
|
|
|
delete(p.Parameters, paramName)
|
|
|
|
}
|
2021-07-12 14:19:28 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
|
|
|
extractParam("blog", &p.Blog)
|
|
|
|
extractParam("path", &p.Path)
|
|
|
|
extractParam("section", &p.Section)
|
|
|
|
extractParam("slug", &p.Slug)
|
|
|
|
extractParam("published", &p.Published)
|
|
|
|
extractParam("updated", &p.Updated)
|
|
|
|
extractParam("status", func(status string) { p.Status = postStatus(status) })
|
|
|
|
extractParam("visibility", func(visibility string) { p.Visibility = postVisibility(visibility) })
|
|
|
|
extractParam("priority", func(priority string) { p.Priority = cast.ToInt(priority) })
|
|
|
|
|
2020-10-14 16:23:56 +00:00
|
|
|
// Add images not in content
|
2023-12-19 11:15:30 +00:00
|
|
|
images, imageAlts := p.Parameters[a.cfg.Micropub.PhotoParam], p.Parameters[a.cfg.Micropub.PhotoDescriptionParam]
|
2020-10-14 16:23:56 +00:00
|
|
|
useAlts := len(images) == len(imageAlts)
|
|
|
|
for i, image := range images {
|
2020-10-15 15:32:46 +00:00
|
|
|
if !strings.Contains(p.Content, image) {
|
2023-12-19 11:15:30 +00:00
|
|
|
if useAlts && imageAlts[i] != "" {
|
|
|
|
p.Content += fmt.Sprintf("\n\n![%s](%s \"%s\")", imageAlts[i], image, imageAlts[i])
|
2020-10-14 16:23:56 +00:00
|
|
|
} else {
|
2023-12-19 11:15:30 +00:00
|
|
|
p.Content += fmt.Sprintf("\n\n![](%s)", image)
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-06 17:07:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
return nil
|
2021-06-23 17:20:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func micropubStatus(status string) postStatus {
|
|
|
|
switch status {
|
|
|
|
case "draft":
|
|
|
|
return statusDraft
|
|
|
|
default:
|
|
|
|
return statusPublished
|
2021-06-23 17:20:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func micropubVisibility(visibility string) postVisibility {
|
|
|
|
switch visibility {
|
|
|
|
case "unlisted":
|
|
|
|
return visibilityUnlisted
|
|
|
|
case "private":
|
|
|
|
return visibilityPrivate
|
|
|
|
default:
|
|
|
|
return visibilityPublic
|
2022-02-26 19:38:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func micropubUpdateMfProperties(properties map[string][]any, req micropub.RequestUpdate) (map[string][]any, error) {
|
|
|
|
if req.Replace != nil {
|
|
|
|
for key, value := range req.Replace {
|
|
|
|
properties[key] = value
|
|
|
|
}
|
2021-06-23 17:20:50 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
if req.Add != nil {
|
|
|
|
for key, value := range req.Add {
|
|
|
|
if _, ok := properties[key]; !ok {
|
|
|
|
properties[key] = []any{}
|
|
|
|
}
|
|
|
|
properties[key] = append(properties[key], value...)
|
|
|
|
}
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
if req.Delete != nil {
|
|
|
|
if reflect.TypeOf(req.Delete).Kind() == reflect.Slice {
|
|
|
|
toDelete, ok := req.Delete.([]any)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("invalid delete array")
|
|
|
|
}
|
|
|
|
for _, key := range toDelete {
|
|
|
|
delete(properties, cast.ToString(key))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
toDelete, ok := req.Delete.(map[string]any)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("invalid delete object: expected map[string]any, got: %s", reflect.TypeOf(req.Delete))
|
|
|
|
}
|
|
|
|
for key, v := range toDelete {
|
|
|
|
value, ok := v.([]any)
|
|
|
|
if !ok {
|
|
|
|
// Wrong type, ignore
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, ok := properties[key]; !ok {
|
|
|
|
// Parameter not present, ignore delete
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
properties[key] = lo.Filter(properties[key], func(ss any, _ int) bool {
|
|
|
|
for _, s := range value {
|
|
|
|
if s == ss {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-01-03 12:55:44 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return properties, nil
|
2022-01-03 12:55:44 +00:00
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
func (s *micropubImplementation) updatePostPropertiesFromMf(p *post, properties map[string][]any) {
|
|
|
|
if properties == nil || p == nil {
|
2021-07-14 16:50:24 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
// Ignore the following properties
|
2024-01-23 07:19:17 +00:00
|
|
|
delete(properties, "url")
|
2023-12-19 11:15:30 +00:00
|
|
|
delete(properties, "photo")
|
|
|
|
delete(properties, "photo-alt")
|
2021-07-14 16:50:24 +00:00
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
// Helper function
|
|
|
|
getFirstStringFromArray := func(arr any) string {
|
|
|
|
if strArr, ok := arr.([]any); ok && len(strArr) > 0 {
|
|
|
|
if str, ok := strArr[0].(string); ok {
|
|
|
|
return str
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
return ""
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
2021-07-14 16:50:24 +00:00
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
// Set other properties
|
|
|
|
p.Content = getFirstStringFromArray(properties["content"])
|
|
|
|
delete(properties, "content")
|
|
|
|
p.Published = getFirstStringFromArray(properties["published"])
|
|
|
|
delete(properties, "published")
|
|
|
|
p.Updated = getFirstStringFromArray(properties["updated"])
|
|
|
|
delete(properties, "updated")
|
|
|
|
p.Slug = getFirstStringFromArray(properties["mp-slug"])
|
|
|
|
delete(properties, "mp-slug")
|
|
|
|
p.setChannel(getFirstStringFromArray(properties["mp-channel"]))
|
|
|
|
delete(properties, "mp-channel")
|
2024-04-22 13:49:22 +00:00
|
|
|
p.Visibility = postVisibility(cmp.Or(getFirstStringFromArray(properties["visibility"]), string(p.Visibility)))
|
2023-12-19 11:15:30 +00:00
|
|
|
delete(properties, "visibility")
|
2024-01-23 07:25:21 +00:00
|
|
|
if newStatusString := getFirstStringFromArray(properties["post-status"]); newStatusString != "" {
|
|
|
|
if newStatus := postStatus(newStatusString); newStatus == statusPublished || newStatus == statusDraft {
|
|
|
|
p.Status = newStatus
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete(properties, "post-status")
|
2022-09-23 09:05:07 +00:00
|
|
|
|
2023-12-19 11:15:30 +00:00
|
|
|
for key, value := range properties {
|
|
|
|
p.Parameters[s.mapToParameterName(key)] = cast.ToStringSlice(value)
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|
2023-12-19 11:15:30 +00:00
|
|
|
|
2020-10-14 16:23:56 +00:00
|
|
|
}
|