167 lines
3.9 KiB
Go
167 lines
3.9 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"owl-blogs/app/owlhttp"
|
|
"owl-blogs/app/repository"
|
|
"owl-blogs/interactions"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
type WebmentionService struct {
|
|
InteractionRepository repository.InteractionRepository
|
|
EntryRepository repository.EntryRepository
|
|
Http owlhttp.HttpClient
|
|
}
|
|
|
|
type ParsedHEntry struct {
|
|
Title string
|
|
}
|
|
|
|
func NewWebmentionService(
|
|
interactionRepository repository.InteractionRepository,
|
|
entryRepository repository.EntryRepository,
|
|
http owlhttp.HttpClient,
|
|
) *WebmentionService {
|
|
return &WebmentionService{
|
|
InteractionRepository: interactionRepository,
|
|
EntryRepository: entryRepository,
|
|
Http: http,
|
|
}
|
|
}
|
|
|
|
func readResponseBody(resp *http.Response) (string, error) {
|
|
defer resp.Body.Close()
|
|
bodyBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(bodyBytes), nil
|
|
}
|
|
|
|
func collectText(n *html.Node, buf *bytes.Buffer) {
|
|
|
|
if n.Type == html.TextNode {
|
|
buf.WriteString(n.Data)
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
collectText(c, buf)
|
|
}
|
|
}
|
|
|
|
func (WebmentionService) ParseHEntry(resp *http.Response) (ParsedHEntry, error) {
|
|
htmlStr, err := readResponseBody(resp)
|
|
if err != nil {
|
|
return ParsedHEntry{}, err
|
|
}
|
|
doc, err := html.Parse(strings.NewReader(htmlStr))
|
|
if err != nil {
|
|
return ParsedHEntry{}, err
|
|
}
|
|
|
|
var interpretHFeed func(*html.Node, *ParsedHEntry, bool) (ParsedHEntry, error)
|
|
interpretHFeed = func(n *html.Node, curr *ParsedHEntry, parent bool) (ParsedHEntry, error) {
|
|
attrs := n.Attr
|
|
for _, attr := range attrs {
|
|
if attr.Key == "class" && strings.Contains(attr.Val, "p-name") {
|
|
buf := &bytes.Buffer{}
|
|
collectText(n, buf)
|
|
curr.Title = buf.String()
|
|
return *curr, nil
|
|
}
|
|
}
|
|
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
interpretHFeed(c, curr, false)
|
|
}
|
|
return *curr, nil
|
|
}
|
|
|
|
var findHFeed func(*html.Node) (ParsedHEntry, error)
|
|
findHFeed = func(n *html.Node) (ParsedHEntry, error) {
|
|
attrs := n.Attr
|
|
for _, attr := range attrs {
|
|
if attr.Key == "class" && strings.Contains(attr.Val, "h-entry") {
|
|
return interpretHFeed(n, &ParsedHEntry{}, true)
|
|
}
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
entry, err := findHFeed(c)
|
|
if err == nil {
|
|
return entry, nil
|
|
}
|
|
}
|
|
return ParsedHEntry{}, errors.New("no h-entry found")
|
|
}
|
|
return findHFeed(doc)
|
|
}
|
|
|
|
func (s *WebmentionService) GetExistingWebmention(entryId string, source string, target string) (*interactions.Webmention, error) {
|
|
inters, err := s.InteractionRepository.FindAll(entryId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, interaction := range inters {
|
|
if webm, ok := interaction.(*interactions.Webmention); ok {
|
|
m := webm.MetaData().(interactions.WebmentionInteractionMetaData)
|
|
if m.Source == source && m.Target == target {
|
|
return webm, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *WebmentionService) ProcessWebmention(source string, target string) error {
|
|
resp, err := s.Http.Get(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hEntry, err := s.ParseHEntry(resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
entryId := UrlToEntryId(target)
|
|
_, err = s.EntryRepository.FindById(entryId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
webmention, err := s.GetExistingWebmention(entryId, source, target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if webmention != nil {
|
|
data := interactions.WebmentionInteractionMetaData{
|
|
Source: source,
|
|
Target: target,
|
|
Title: hEntry.Title,
|
|
}
|
|
webmention.SetMetaData(data)
|
|
webmention.SetEntryID(entryId)
|
|
webmention.SetCreatedAt(time.Now())
|
|
err = s.InteractionRepository.Update(webmention)
|
|
return err
|
|
} else {
|
|
webmention = &interactions.Webmention{}
|
|
data := interactions.WebmentionInteractionMetaData{
|
|
Source: source,
|
|
Target: target,
|
|
Title: hEntry.Title,
|
|
}
|
|
webmention.SetMetaData(data)
|
|
webmention.SetEntryID(entryId)
|
|
webmention.SetCreatedAt(time.Now())
|
|
err = s.InteractionRepository.Create(webmention)
|
|
return err
|
|
}
|
|
}
|