Sending Webmentions #10
|
@ -3,6 +3,7 @@ package owl_test
|
|||
import (
|
||||
"h4kor/owl-blogs"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -16,12 +17,20 @@ func (*MockHttpParser) ParseLinks(data []byte) ([]string, error) {
|
|||
return []string{"http://example.com"}, nil
|
||||
}
|
||||
|
||||
func (*MockHttpParser) GetWebmentionEndpoint(data []byte) (string, error) {
|
||||
return "http://example.com/webmention", nil
|
||||
}
|
||||
|
||||
type MockHttpRetriever struct{}
|
||||
|
||||
func (*MockHttpRetriever) Get(url string) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func (m *MockHttpRetriever) Post(url string, data url.Values) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func randomName() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyz")
|
||||
|
|
58
post.go
58
post.go
|
@ -6,9 +6,11 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
|
@ -272,8 +274,28 @@ func (post *Post) AddOutgoingWebmention(target string) error {
|
|||
return post.PersistStatus(status)
|
||||
}
|
||||
|
||||
func (post *Post) UpdateOutgoingWebmention(webmention *WebmentionOut) error {
|
||||
status := post.Status()
|
||||
|
||||
// if target is not in status, add it
|
||||
replaced := false
|
||||
for i, t := range status.Webmentions {
|
||||
if t.Target == webmention.Target {
|
||||
status.Webmentions[i] = *webmention
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !replaced {
|
||||
status.Webmentions = append(status.Webmentions, *webmention)
|
||||
}
|
||||
|
||||
return post.PersistStatus(status)
|
||||
}
|
||||
|
||||
func (post *Post) EnrichWebmention(source string) error {
|
||||
html, err := post.user.repo.Retriever.Get(source)
|
||||
html, err := post.user.repo.HttpClient.Get(source)
|
||||
if err == nil {
|
||||
webmention, err := post.Webmention(source)
|
||||
if err != nil {
|
||||
|
@ -346,3 +368,37 @@ func (post *Post) ScanForLinks() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (post *Post) SendWebmention(webmention WebmentionOut) error {
|
||||
defer post.UpdateOutgoingWebmention(&webmention)
|
||||
webmention.ScannedAt = time.Now()
|
||||
|
||||
html, err := post.user.repo.HttpClient.Get(webmention.Target)
|
||||
if err != nil {
|
||||
// TODO handle error
|
||||
webmention.Supported = false
|
||||
return err
|
||||
}
|
||||
endpoint, err := post.user.repo.Parser.GetWebmentionEndpoint(html)
|
||||
if err != nil {
|
||||
// TODO handle error
|
||||
webmention.Supported = false
|
||||
return err
|
||||
}
|
||||
webmention.Supported = true
|
||||
|
||||
// send webmention
|
||||
payload := url.Values{}
|
||||
payload.Set("source", post.FullUrl())
|
||||
payload.Set("target", webmention.Target)
|
||||
_, err = post.user.repo.HttpClient.Post(endpoint, payload)
|
||||
|
||||
if err != nil {
|
||||
// TODO handle error
|
||||
return err
|
||||
}
|
||||
|
||||
// update webmention status
|
||||
webmention.LastSentAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
|
37
post_test.go
37
post_test.go
|
@ -192,7 +192,7 @@ func TestPersistWebmention(t *testing.T) {
|
|||
|
||||
func TestAddWebmentionCreatesFile(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.HttpClient = &MockHttpRetriever{}
|
||||
repo.Parser = &MockHttpParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
@ -210,7 +210,7 @@ func TestAddWebmentionCreatesFile(t *testing.T) {
|
|||
|
||||
func TestAddWebmentionNotOverwritingFile(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.HttpClient = &MockHttpRetriever{}
|
||||
repo.Parser = &MockHttpParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
@ -240,7 +240,7 @@ func TestAddWebmentionNotOverwritingFile(t *testing.T) {
|
|||
|
||||
func TestAddWebmentionAddsParsedTitle(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.HttpClient = &MockHttpRetriever{}
|
||||
repo.Parser = &MockHttpParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
@ -352,3 +352,34 @@ func TestScanningForLinksDoesNotAddDuplicates(t *testing.T) {
|
|||
t.Errorf("Expected target: %s, got %s", "https://example.com/hello", webmentions[0].Target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanSendWebmention(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.HttpClient = &MockHttpRetriever{}
|
||||
repo.Parser = &MockHttpParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
||||
webmention := owl.WebmentionOut{
|
||||
Target: "http://example.com",
|
||||
}
|
||||
|
||||
err := post.SendWebmention(webmention)
|
||||
if err != nil {
|
||||
t.Errorf("Error sending webmention: %v", err)
|
||||
}
|
||||
|
||||
webmentions := post.OutgoingWebmentions()
|
||||
|
||||
if len(webmentions) != 1 {
|
||||
t.Errorf("Expected 1 webmention, got %d", len(webmentions))
|
||||
}
|
||||
|
||||
if webmentions[0].Target != "http://example.com" {
|
||||
t.Errorf("Expected target: %s, got %s", "http://example.com", webmentions[0].Target)
|
||||
}
|
||||
|
||||
if webmentions[0].LastSentAt.IsZero() {
|
||||
t.Errorf("Expected LastSentAt to be set")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ type Repository struct {
|
|||
single_user_mode bool
|
||||
active_user string
|
||||
allow_raw_html bool
|
||||
Retriever HttpRetriever
|
||||
Parser HttpParser
|
||||
HttpClient HttpClient
|
||||
Parser HtmlParser
|
||||
}
|
||||
|
||||
type RepoConfig struct {
|
||||
|
@ -29,7 +29,7 @@ type RepoConfig struct {
|
|||
}
|
||||
|
||||
func CreateRepository(name string) (Repository, error) {
|
||||
newRepo := Repository{name: name, Parser: OwlMicroformatParser{}, Retriever: OwlHttpRetriever{}}
|
||||
newRepo := Repository{name: name, Parser: OwlHtmlParser{}, HttpClient: OwlHttpClient{}}
|
||||
// check if repository already exists
|
||||
if dirExists(newRepo.Dir()) {
|
||||
return Repository{}, fmt.Errorf("Repository already exists")
|
||||
|
@ -63,7 +63,7 @@ func CreateRepository(name string) (Repository, error) {
|
|||
|
||||
func OpenRepository(name string) (Repository, error) {
|
||||
|
||||
repo := Repository{name: name, Parser: OwlMicroformatParser{}, Retriever: OwlHttpRetriever{}}
|
||||
repo := Repository{name: name, Parser: OwlHtmlParser{}, HttpClient: OwlHttpClient{}}
|
||||
if !dirExists(repo.Dir()) {
|
||||
return Repository{}, fmt.Errorf("Repository does not exist: " + repo.Dir())
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -24,24 +25,26 @@ type WebmentionOut struct {
|
|||
LastSentAt time.Time `yaml:"last_sent_at"`
|
||||
}
|
||||
|
||||
type HttpRetriever interface {
|
||||
type HttpClient interface {
|
||||
Get(url string) ([]byte, error)
|
||||
Post(url string, data url.Values) ([]byte, error)
|
||||
}
|
||||
|
||||
type HttpParser interface {
|
||||
type HtmlParser interface {
|
||||
ParseHEntry(data []byte) (ParsedHEntry, error)
|
||||
ParseLinks(data []byte) ([]string, error)
|
||||
GetWebmentionEndpoint(data []byte) (string, error)
|
||||
}
|
||||
|
||||
type OwlHttpRetriever struct{}
|
||||
type OwlHttpClient struct{}
|
||||
|
||||
type OwlMicroformatParser struct{}
|
||||
type OwlHtmlParser struct{}
|
||||
|
||||
type ParsedHEntry struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
func (OwlHttpRetriever) Get(url string) ([]byte, error) {
|
||||
func (OwlHttpClient) Get(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
|
@ -52,6 +55,17 @@ func (OwlHttpRetriever) Get(url string) ([]byte, error) {
|
|||
return data, err
|
||||
}
|
||||
|
||||
func (OwlHttpClient) Post(url string, data url.Values) ([]byte, error) {
|
||||
resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
var respData []byte
|
||||
_, err = resp.Body.Read(respData)
|
||||
|
||||
return respData, err
|
||||
}
|
||||
|
||||
func collectText(n *html.Node, buf *bytes.Buffer) {
|
||||
if n.Type == html.TextNode {
|
||||
buf.WriteString(n.Data)
|
||||
|
@ -61,7 +75,7 @@ func collectText(n *html.Node, buf *bytes.Buffer) {
|
|||
}
|
||||
}
|
||||
|
||||
func (OwlMicroformatParser) ParseHEntry(data []byte) (ParsedHEntry, error) {
|
||||
func (OwlHtmlParser) ParseHEntry(data []byte) (ParsedHEntry, error) {
|
||||
doc, err := html.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return ParsedHEntry{}, err
|
||||
|
@ -104,7 +118,7 @@ func (OwlMicroformatParser) ParseHEntry(data []byte) (ParsedHEntry, error) {
|
|||
return findHFeed(doc)
|
||||
}
|
||||
|
||||
func (OwlMicroformatParser) ParseLinks(data []byte) ([]string, error) {
|
||||
func (OwlHtmlParser) ParseLinks(data []byte) ([]string, error) {
|
||||
doc, err := html.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return make([]string, 0), err
|
||||
|
@ -129,3 +143,33 @@ func (OwlMicroformatParser) ParseLinks(data []byte) ([]string, error) {
|
|||
return findLinks(doc)
|
||||
|
||||
}
|
||||
|
||||
func (OwlHtmlParser) GetWebmentionEndpoint(data []byte) (string, error) {
|
||||
doc, err := html.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var findEndpoint func(*html.Node) (string, error)
|
||||
findEndpoint = func(n *html.Node) (string, error) {
|
||||
if n.Type == html.ElementNode && n.Data == "link" {
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Key == "rel" && attr.Val == "webmention" {
|
||||
for _, attr := range n.Attr {
|
||||
if attr.Key == "href" {
|
||||
return attr.Val, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
endpoint, err := findEndpoint(c)
|
||||
if err == nil {
|
||||
return endpoint, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no webmention endpoint found")
|
||||
}
|
||||
return findEndpoint(doc)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func TestParseValidHEntry(t *testing.T) {
|
||||
html := []byte("<div class=\"h-entry\"><div class=\"p-name\">Foo</div></div>")
|
||||
parser := &owl.OwlMicroformatParser{}
|
||||
parser := &owl.OwlHtmlParser{}
|
||||
entry, err := parser.ParseHEntry(html)
|
||||
|
||||
if err != nil {
|
||||
|
@ -24,7 +24,7 @@ func TestParseValidHEntry(t *testing.T) {
|
|||
|
||||
func TestParseValidHEntryWithoutTitle(t *testing.T) {
|
||||
html := []byte("<div class=\"h-entry\"></div><div class=\"p-name\">Foo</div>")
|
||||
parser := &owl.OwlMicroformatParser{}
|
||||
parser := &owl.OwlHtmlParser{}
|
||||
entry, err := parser.ParseHEntry(html)
|
||||
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue