port pingbacks from js impl
This commit is contained in:
parent
97be3c8323
commit
e717f6312b
|
@ -2,13 +2,102 @@
|
||||||
package pingback
|
package pingback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/wgroeneveld/go-jamming/app/webmention"
|
||||||
|
"github.com/wgroeneveld/go-jamming/rest"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/wgroeneveld/go-jamming/common"
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Handle(conf *common.Config) http.HandlerFunc {
|
func HandlePost(conf *common.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
pingbackError(w, "Unable to read request body")
|
||||||
|
}
|
||||||
|
rpc := &XmlRPCMethodCall{}
|
||||||
|
err = xml.Unmarshal(body, rpc)
|
||||||
|
if err != nil {
|
||||||
|
pingbackError(w, "Unable to unmarshal XMLRPC request body")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validate(rpc, conf) {
|
||||||
|
pingbackError(w, "malformed pingback request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wm := webmention.Mention{
|
||||||
|
Source: rpc.Source(),
|
||||||
|
Target: rpc.Target(),
|
||||||
|
}
|
||||||
|
receiver := webmention.Receiver{
|
||||||
|
RestClient: &rest.HttpClient{},
|
||||||
|
Conf: conf,
|
||||||
|
}
|
||||||
|
go receiver.Receive(wm)
|
||||||
|
pingbackSuccess(w, "Thanks, bro. Will process this soon, pinky swear!")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var successXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodResponse>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value>
|
||||||
|
<string>
|
||||||
|
{{ . }}
|
||||||
|
</string>
|
||||||
|
</value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodResponse>
|
||||||
|
`
|
||||||
|
// compile once, execute as many times as needed.
|
||||||
|
var successTpl, _ = template.New("success").Parse(successXml)
|
||||||
|
|
||||||
|
func pingbackSuccess(w http.ResponseWriter, msg string) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
successTpl.Execute(w, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// according to the XML-RPC spec, always return a 200, but encode it into the XML.
|
||||||
|
func pingbackError(w http.ResponseWriter, msg string) {
|
||||||
|
xml := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodResponse>
|
||||||
|
<fault>
|
||||||
|
<value>
|
||||||
|
<struct>
|
||||||
|
<member>
|
||||||
|
<name>
|
||||||
|
faultCode
|
||||||
|
</name>
|
||||||
|
<value>
|
||||||
|
<int>
|
||||||
|
0
|
||||||
|
</int>
|
||||||
|
</value>
|
||||||
|
</member>
|
||||||
|
<member>
|
||||||
|
<name>
|
||||||
|
faultString
|
||||||
|
</name>
|
||||||
|
<value>
|
||||||
|
<string>
|
||||||
|
Sorry pal. Malformed request? Or something else, who knows...
|
||||||
|
</string>
|
||||||
|
</value>
|
||||||
|
</member>
|
||||||
|
</struct>
|
||||||
|
</value>
|
||||||
|
</fault>
|
||||||
|
</methodResponse>`
|
||||||
|
log.Error().Str("msg", msg).Msg("Pingback receive went wrong")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(xml))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package pingback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func validate(rpc *XmlRPCMethodCall, conf *common.Config) bool {
|
||||||
|
if rpc.MethodName != "pingback.ping" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(rpc.Params.Parameters) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
target := rpc.Target()
|
||||||
|
if !strings.HasPrefix(target, "http") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := conf.FetchDomain(target)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
source := rpc.Source()
|
||||||
|
if !strings.HasPrefix(source, "http") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if source == target {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
package pingback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/wgroeneveld/go-jamming/common"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var conf *common.Config = &common.Config{
|
||||||
|
AllowedWebmentionSources: []string{
|
||||||
|
"brainbaking.com",
|
||||||
|
"jefklakscodex.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
label string
|
||||||
|
xml string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"not valid if methodName is not pingback.ping",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>ka.tsjing</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not valid if less than two parameters",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not valid if more than two parameters",
|
||||||
|
`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not valid if target is not in trusted domains from config",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://flashballz.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not valid if target is not http(s)",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>gemini://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not valid if source is not http(s)",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>gemini://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"is valid if pingback.ping and two http(s) parameters of which target is trusted",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://cool.site</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"is not valid if source and target are the same urls",
|
||||||
|
`
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/post/2021/03/cool-ness</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.label, func(t *testing.T) {
|
||||||
|
var xmlObj XmlRPCMethodCall
|
||||||
|
err := xml.Unmarshal([]byte(tc.xml), &xmlObj)
|
||||||
|
assert.NoError(t, err, "XML invalid in test case")
|
||||||
|
|
||||||
|
result := validate(&xmlObj, conf)
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package pingback
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
/* e.g. (see tests)
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/kristien.html</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>
|
||||||
|
*/
|
||||||
|
type XmlRPCMethodCall struct {
|
||||||
|
XMLName xml.Name `xml:"methodCall"`
|
||||||
|
MethodName string `xml:"methodName"`
|
||||||
|
Params XmlRPCParams `xml:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpc *XmlRPCMethodCall) Source() string {
|
||||||
|
return rpc.Params.Parameters[0].Value.String
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rpc *XmlRPCMethodCall) Target() string {
|
||||||
|
return rpc.Params.Parameters[1].Value.String
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlRPCParams struct {
|
||||||
|
XMLName xml.Name `xml:"params"`
|
||||||
|
Parameters []XmlRPCParam `xml:"param"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlRPCParam struct {
|
||||||
|
XMLName xml.Name `xml:"param"`
|
||||||
|
Value XmlRPCValue `xml:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type XmlRPCValue struct {
|
||||||
|
String string `xml:"string"`
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package pingback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See https://www.hixie.ch/specs/pingback/pingback#refsXMLRPC
|
||||||
|
func TestMarshallValidXMLRPC(t *testing.T) {
|
||||||
|
xmlString := `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<methodCall>
|
||||||
|
<methodName>pingback.ping</methodName>
|
||||||
|
<params>
|
||||||
|
<param>
|
||||||
|
<value><string>https://brainbaking.com/kristien.html</string></value>
|
||||||
|
</param>
|
||||||
|
<param>
|
||||||
|
<value><string>https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/</string></value>
|
||||||
|
</param>
|
||||||
|
</params>
|
||||||
|
</methodCall>`
|
||||||
|
var rpc XmlRPCMethodCall
|
||||||
|
err := xml.Unmarshal([]byte(xmlString), &rpc)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "pingback.ping", rpc.MethodName)
|
||||||
|
assert.Equal(t, "https://brainbaking.com/kristien.html", rpc.Params.Parameters[0].Value.String)
|
||||||
|
assert.Equal(t, "https://kristienthoelen.be/2021/03/22/de-stadia-van-een-burn-out-in-welk-stadium-zit-jij/", rpc.Params.Parameters[1].Value.String)
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ import (
|
||||||
// https://blog.questionable.services/article/http-handler-error-handling-revisited/ is the better idea, but more work
|
// https://blog.questionable.services/article/http-handler-error-handling-revisited/ is the better idea, but more work
|
||||||
func (s *server) routes() {
|
func (s *server) routes() {
|
||||||
s.router.HandleFunc("/", index.Handle(s.conf)).Methods("GET")
|
s.router.HandleFunc("/", index.Handle(s.conf)).Methods("GET")
|
||||||
s.router.HandleFunc("/pingback", pingback.Handle(s.conf)).Methods("POST")
|
s.router.HandleFunc("/pingback", pingback.HandlePost(s.conf)).Methods("POST")
|
||||||
s.router.HandleFunc("/webmention", webmention.HandlePost(s.conf)).Methods("POST")
|
s.router.HandleFunc("/webmention", webmention.HandlePost(s.conf)).Methods("POST")
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandleGet(s.conf))).Methods("GET")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandleGet(s.conf))).Methods("GET")
|
||||||
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandlePut(s.conf))).Methods("PUT")
|
s.router.HandleFunc("/webmention/{domain}/{token}", s.authorizedOnly(webmention.HandlePut(s.conf))).Methods("PUT")
|
||||||
|
|
|
@ -37,16 +37,16 @@ func HandlePost(conf *common.Config) http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm := webmention{
|
wm := Mention{
|
||||||
source: r.FormValue("source"),
|
Source: r.FormValue("source"),
|
||||||
target: target,
|
Target: target,
|
||||||
}
|
}
|
||||||
recv := &receiver{
|
recv := &Receiver{
|
||||||
restClient: httpClient,
|
RestClient: httpClient,
|
||||||
conf: conf,
|
Conf: conf,
|
||||||
}
|
}
|
||||||
|
|
||||||
go recv.receive(wm)
|
go recv.Receive(wm)
|
||||||
rest.Accept(w)
|
rest.Accept(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package webmention
|
package webmention
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"willnorris.com/go/microformats"
|
"willnorris.com/go/microformats"
|
||||||
)
|
)
|
||||||
|
@ -80,3 +81,57 @@ func mfProp(mf *microformats.Microformat, key string) *microformats.Microformat
|
||||||
}
|
}
|
||||||
return val[0].(*microformats.Microformat)
|
return val[0].(*microformats.Microformat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func determinePublishedDate(hEntry *microformats.Microformat, utcOffset int) string {
|
||||||
|
publishedDate := mfStr(hEntry, "published")
|
||||||
|
if publishedDate == "" {
|
||||||
|
return publishedNow(utcOffset)
|
||||||
|
}
|
||||||
|
return publishedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineAuthorName(hEntry *microformats.Microformat) string {
|
||||||
|
authorName := mfStr(mfProp(hEntry, "author"), "name")
|
||||||
|
if authorName == "" {
|
||||||
|
return mfProp(hEntry, "author").Value
|
||||||
|
}
|
||||||
|
return authorName
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineMfType(hEntry *microformats.Microformat) string {
|
||||||
|
likeOf := mfStr(hEntry, "like-of")
|
||||||
|
if likeOf != "" {
|
||||||
|
return "like"
|
||||||
|
}
|
||||||
|
bookmarkOf := mfStr(hEntry, "bookmark-of")
|
||||||
|
if bookmarkOf != "" {
|
||||||
|
return "bookmark"
|
||||||
|
}
|
||||||
|
return "mention"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources
|
||||||
|
func determineUrl(hEntry *microformats.Microformat, source string) string {
|
||||||
|
uid := mfStr(hEntry, "uid")
|
||||||
|
if uid != "" && strings.HasPrefix(uid, "http") {
|
||||||
|
return uid
|
||||||
|
}
|
||||||
|
url := mfStr(hEntry, "url")
|
||||||
|
if url != "" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineContent(hEntry *microformats.Microformat) string {
|
||||||
|
bridgyTwitterContent := mfStr(hEntry, "bridgy-twitter-content")
|
||||||
|
if bridgyTwitterContent != "" {
|
||||||
|
return shorten(bridgyTwitterContent)
|
||||||
|
}
|
||||||
|
summary := mfStr(hEntry, "summary")
|
||||||
|
if summary != "" {
|
||||||
|
return shorten(summary)
|
||||||
|
}
|
||||||
|
contentEntry := mfMap(hEntry, "content")["value"]
|
||||||
|
return shorten(contentEntry)
|
||||||
|
}
|
|
@ -18,39 +18,39 @@ import (
|
||||||
"willnorris.com/go/microformats"
|
"willnorris.com/go/microformats"
|
||||||
)
|
)
|
||||||
|
|
||||||
type webmention struct {
|
type Mention struct {
|
||||||
source string
|
Source string
|
||||||
target string
|
Target string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *webmention) String() string {
|
func (wm *Mention) String() string {
|
||||||
return fmt.Sprintf("source: %s, target: %s", wm.source, wm.target)
|
return fmt.Sprintf("source: %s, target: %s", wm.Source, wm.Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *webmention) asPath(conf *common.Config) string {
|
func (wm *Mention) asPath(conf *common.Config) string {
|
||||||
filename := fmt.Sprintf("%x", md5.Sum([]byte("source=" + wm.source + ",target=" + wm.target)))
|
filename := fmt.Sprintf("%x", md5.Sum([]byte("source=" + wm.Source+ ",target=" + wm.Target)))
|
||||||
domain, _ := conf.FetchDomain(wm.target)
|
domain, _ := conf.FetchDomain(wm.Target)
|
||||||
return conf.DataPath + "/" + domain + "/" + filename + ".json"
|
return conf.DataPath + "/" + domain + "/" + filename + ".json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *webmention) sourceUrl() *url.URL {
|
func (wm *Mention) sourceUrl() *url.URL {
|
||||||
url, _ := url.Parse(wm.source)
|
url, _ := url.Parse(wm.Source)
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// used as a "class" to iject dependencies, just to be able to test. Do NOT like htis.
|
// used as a "class" to iject dependencies, just to be able to test. Do NOT like htis.
|
||||||
// Is there a better way? e.g. in validate, I just pass rest.Client as an arg. Not great either.
|
// Is there a better way? e.g. in validate, I just pass rest.Client as an arg. Not great either.
|
||||||
type receiver struct {
|
type Receiver struct {
|
||||||
restClient rest.Client
|
RestClient rest.Client
|
||||||
conf *common.Config
|
Conf *common.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (recv *receiver) receive(wm webmention) {
|
func (recv *Receiver) Receive(wm Mention) {
|
||||||
log.Info().Str("webmention", wm.String()).Msg("OK: looks valid")
|
log.Info().Str("Webmention", wm.String()).Msg("OK: looks valid")
|
||||||
body, geterr := recv.restClient.GetBody(wm.source)
|
body, geterr := recv.RestClient.GetBody(wm.Source)
|
||||||
|
|
||||||
if geterr != nil {
|
if geterr != nil {
|
||||||
log.Warn().Str("source", wm.source).Msg(" ABORT: invalid url")
|
log.Warn().Str("source", wm.Source).Msg(" ABORT: invalid url")
|
||||||
recv.deletePossibleOlderWebmention(wm)
|
recv.deletePossibleOlderWebmention(wm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ func (recv *receiver) receive(wm webmention) {
|
||||||
recv.processSourceBody(body, wm)
|
recv.processSourceBody(body, wm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (recv *receiver) deletePossibleOlderWebmention(wm webmention) {
|
func (recv *Receiver) deletePossibleOlderWebmention(wm Mention) {
|
||||||
os.Remove(wm.asPath(recv.conf))
|
os.Remove(wm.asPath(recv.Conf))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHEntry(data *microformats.Data) *microformats.Microformat {
|
func getHEntry(data *microformats.Data) *microformats.Microformat {
|
||||||
|
@ -72,40 +72,40 @@ func getHEntry(data *microformats.Data) *microformats.Microformat {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (recv *receiver) processSourceBody(body string, wm webmention) {
|
func (recv *Receiver) processSourceBody(body string, wm Mention) {
|
||||||
if !strings.Contains(body, wm.target) {
|
if !strings.Contains(body, wm.Target) {
|
||||||
log.Warn().Str("target", wm.target).Msg("ABORT: no mention of target found in html src of source!")
|
log.Warn().Str("target", wm.Target).Msg("ABORT: no mention of target found in html src of source!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r := strings.NewReader(body)
|
data := microformats.Parse(strings.NewReader(body), wm.sourceUrl())
|
||||||
data := microformats.Parse(r, wm.sourceUrl())
|
indieweb := recv.convertBodyToIndiewebData(body, wm, getHEntry(data))
|
||||||
hEntry := getHEntry(data)
|
|
||||||
var indieweb *indiewebData
|
|
||||||
if hEntry == nil {
|
|
||||||
indieweb = recv.parseBodyAsNonIndiewebSite(body, wm)
|
|
||||||
} else {
|
|
||||||
indieweb = recv.parseBodyAsIndiewebSite(hEntry, wm)
|
|
||||||
}
|
|
||||||
|
|
||||||
recv.saveWebmentionToDisk(wm, indieweb)
|
recv.saveWebmentionToDisk(wm, indieweb)
|
||||||
log.Info().Str("file", wm.asPath(recv.conf)).Msg("OK: webmention processed.")
|
log.Info().Str("file", wm.asPath(recv.Conf)).Msg("OK: Webmention processed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (recv *receiver) saveWebmentionToDisk(wm webmention, indieweb *indiewebData) {
|
func (recv *Receiver) convertBodyToIndiewebData(body string, wm Mention, hEntry *microformats.Microformat) *indiewebData {
|
||||||
|
if hEntry == nil {
|
||||||
|
return recv.parseBodyAsNonIndiewebSite(body, wm)
|
||||||
|
}
|
||||||
|
return recv.parseBodyAsIndiewebSite(hEntry, wm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (recv *Receiver) saveWebmentionToDisk(wm Mention, indieweb *indiewebData) {
|
||||||
jsonData, jsonErr := json.Marshal(indieweb)
|
jsonData, jsonErr := json.Marshal(indieweb)
|
||||||
if jsonErr != nil {
|
if jsonErr != nil {
|
||||||
log.Err(jsonErr).Msg("Unable to serialize webmention into JSON")
|
log.Err(jsonErr).Msg("Unable to serialize Webmention into JSON")
|
||||||
}
|
}
|
||||||
err := ioutil.WriteFile(wm.asPath(recv.conf), jsonData, fs.ModePerm)
|
err := ioutil.WriteFile(wm.asPath(recv.Conf), jsonData, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Unable to save webmention to disk")
|
log.Err(err).Msg("Unable to save Webmention to disk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO I'm smelling very unstable code, apply https://golang.org/doc/effective_go#recover here?
|
// TODO I'm smelling very unstable code, apply https://golang.org/doc/effective_go#recover here?
|
||||||
// see https://github.com/willnorris/microformats/blob/main/microformats.go
|
// see https://github.com/willnorris/microformats/blob/main/microformats.go
|
||||||
func (recv *receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, wm webmention) *indiewebData {
|
func (recv *Receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, wm Mention) *indiewebData {
|
||||||
name := mfStr(hEntry, "name")
|
name := mfStr(hEntry, "name")
|
||||||
pic := mfStr(mfProp(hEntry, "author"), "photo")
|
pic := mfStr(mfProp(hEntry, "author"), "photo")
|
||||||
mfType := determineMfType(hEntry)
|
mfType := determineMfType(hEntry)
|
||||||
|
@ -117,85 +117,31 @@ func (recv *receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat,
|
||||||
Picture: pic,
|
Picture: pic,
|
||||||
},
|
},
|
||||||
Content: determineContent(hEntry),
|
Content: determineContent(hEntry),
|
||||||
Url: determineUrl(hEntry, wm.source),
|
Url: determineUrl(hEntry, wm.Source),
|
||||||
Published: determinePublishedDate(hEntry, recv.conf.UtcOffset),
|
Published: determinePublishedDate(hEntry, recv.Conf.UtcOffset),
|
||||||
Source: wm.source,
|
Source: wm.Source,
|
||||||
Target: wm.target,
|
Target: wm.Target,
|
||||||
IndiewebType: mfType,
|
IndiewebType: mfType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func determinePublishedDate(hEntry *microformats.Microformat, utcOffset int) string {
|
func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm Mention) *indiewebData {
|
||||||
publishedDate := mfStr(hEntry, "published")
|
|
||||||
if publishedDate == "" {
|
|
||||||
return publishedNow(utcOffset)
|
|
||||||
}
|
|
||||||
return publishedDate
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineAuthorName(hEntry *microformats.Microformat) string {
|
|
||||||
authorName := mfStr(mfProp(hEntry, "author"), "name")
|
|
||||||
if authorName == "" {
|
|
||||||
return mfProp(hEntry, "author").Value
|
|
||||||
}
|
|
||||||
return authorName
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineMfType(hEntry *microformats.Microformat) string {
|
|
||||||
likeOf := mfStr(hEntry, "like-of")
|
|
||||||
if likeOf != "" {
|
|
||||||
return "like"
|
|
||||||
}
|
|
||||||
bookmarkOf := mfStr(hEntry, "bookmark-of")
|
|
||||||
if bookmarkOf != "" {
|
|
||||||
return "bookmark"
|
|
||||||
}
|
|
||||||
return "mention"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources
|
|
||||||
func determineUrl(hEntry *microformats.Microformat, source string) string {
|
|
||||||
uid := mfStr(hEntry, "uid")
|
|
||||||
if uid != "" && strings.HasPrefix(uid, "http") {
|
|
||||||
return uid
|
|
||||||
}
|
|
||||||
url := mfStr(hEntry, "url")
|
|
||||||
if url != "" {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
return source
|
|
||||||
}
|
|
||||||
|
|
||||||
func determineContent(hEntry *microformats.Microformat) string {
|
|
||||||
bridgyTwitterContent := mfStr(hEntry, "bridgy-twitter-content")
|
|
||||||
if bridgyTwitterContent != "" {
|
|
||||||
return shorten(bridgyTwitterContent)
|
|
||||||
}
|
|
||||||
summary := mfStr(hEntry, "summary")
|
|
||||||
if summary != "" {
|
|
||||||
return shorten(summary)
|
|
||||||
}
|
|
||||||
contentEntry := mfMap(hEntry, "content")["value"]
|
|
||||||
return shorten(contentEntry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (recv *receiver) parseBodyAsNonIndiewebSite(body string, wm webmention) *indiewebData {
|
|
||||||
r := regexp.MustCompile(`<title>(.*?)<\/title>`)
|
r := regexp.MustCompile(`<title>(.*?)<\/title>`)
|
||||||
titleMatch := r.FindStringSubmatch(body)
|
titleMatch := r.FindStringSubmatch(body)
|
||||||
title := wm.source
|
title := wm.Source
|
||||||
if titleMatch != nil {
|
if titleMatch != nil {
|
||||||
title = titleMatch[1]
|
title = titleMatch[1]
|
||||||
}
|
}
|
||||||
return &indiewebData{
|
return &indiewebData{
|
||||||
Author: indiewebAuthor{
|
Author: indiewebAuthor{
|
||||||
Name: wm.source,
|
Name: wm.Source,
|
||||||
},
|
},
|
||||||
Name: title,
|
Name: title,
|
||||||
Content: title,
|
Content: title,
|
||||||
Published: publishedNow(recv.conf.UtcOffset),
|
Published: publishedNow(recv.Conf.UtcOffset),
|
||||||
Url: wm.source,
|
Url: wm.Source,
|
||||||
IndiewebType: "mention",
|
IndiewebType: "mention",
|
||||||
Source: wm.source,
|
Source: wm.Source,
|
||||||
Target: wm.target,
|
Target: wm.Target,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,9 @@ var conf = &common.Config{
|
||||||
|
|
||||||
|
|
||||||
func TestConvertWebmentionToPath(t *testing.T) {
|
func TestConvertWebmentionToPath(t *testing.T) {
|
||||||
wm := webmention{
|
wm := Mention{
|
||||||
source: "https://brainbaking.com",
|
Source: "https://brainbaking.com",
|
||||||
target: "https://jefklakscodex.com/articles",
|
Target: "https://jefklakscodex.com/articles",
|
||||||
}
|
}
|
||||||
|
|
||||||
result := wm.asPath(conf)
|
result := wm.asPath(conf)
|
||||||
|
@ -42,56 +42,56 @@ func writeSomethingTo(filename string) {
|
||||||
func TestReceive(t *testing.T) {
|
func TestReceive(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
label string
|
label string
|
||||||
wm webmention
|
wm Mention
|
||||||
json string
|
json string
|
||||||
} {
|
} {
|
||||||
{
|
{
|
||||||
label: "receive a webmention bookmark via twitter",
|
label: "receive a Webmention bookmark via twitter",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-bridgy-twitter-source.html",
|
Source: "https://brainbaking.com/valid-bridgy-twitter-source.html",
|
||||||
target: "https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag",
|
Target: "https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag",
|
||||||
},
|
},
|
||||||
json: `{"author":{"name":"Jamie Tanna","picture":"https://www.jvt.me/img/profile.png"},"name":"","content":"Recommended read:\nThe IndieWeb Mixed Bag - Thoughts about the (d)evolution of blog interactions\nhttps://brainbaking.com/post/2021/03/the-indieweb-mixed-bag/","published":"2021-03-15T12:42:00+0000","url":"https://brainbaking.com/mf2/2021/03/1bkre/","type":"bookmark","source":"https://brainbaking.com/valid-bridgy-twitter-source.html","target":"https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag"}`,
|
json: `{"author":{"name":"Jamie Tanna","picture":"https://www.jvt.me/img/profile.png"},"name":"","content":"Recommended read:\nThe IndieWeb Mixed Bag - Thoughts about the (d)evolution of blog interactions\nhttps://brainbaking.com/post/2021/03/the-indieweb-mixed-bag/","published":"2021-03-15T12:42:00+0000","url":"https://brainbaking.com/mf2/2021/03/1bkre/","type":"bookmark","source":"https://brainbaking.com/valid-bridgy-twitter-source.html","target":"https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "receive a brid.gy webmention like",
|
label: "receive a brid.gy Webmention like",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-bridgy-like.html",
|
Source: "https://brainbaking.com/valid-bridgy-like.html",
|
||||||
// wrapped in a a class="u-like-of" tag
|
// wrapped in a a class="u-like-of" tag
|
||||||
target: "https://brainbaking.com/valid-indieweb-target.html",
|
Target: "https://brainbaking.com/valid-indieweb-target.html",
|
||||||
},
|
},
|
||||||
// no dates in bridgy-to-mastodon likes...
|
// no dates in bridgy-to-mastodon likes...
|
||||||
json: `{"author":{"name":"Stampeding Longhorn","picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"},"name":"","content":"","published":"2020-01-01T12:30:00","url":"https://chat.brainbaking.com/notice/A4nx1rFwKUJYSe4TqK#favorited-by-A4nwg4LYyh4WgrJOXg","type":"like","source":"https://brainbaking.com/valid-bridgy-like.html","target":"https://brainbaking.com/valid-indieweb-target.html"}`,
|
json: `{"author":{"name":"Stampeding Longhorn","picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"},"name":"","content":"","published":"2020-01-01T12:30:00","url":"https://chat.brainbaking.com/notice/A4nx1rFwKUJYSe4TqK#favorited-by-A4nwg4LYyh4WgrJOXg","type":"like","source":"https://brainbaking.com/valid-bridgy-like.html","target":"https://brainbaking.com/valid-indieweb-target.html"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "receive a brid.gy webmention that has a url and photo without value",
|
label: "receive a brid.gy Webmention that has a url and photo without value",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-bridgy-source.html",
|
Source: "https://brainbaking.com/valid-bridgy-source.html",
|
||||||
target: "https://brainbaking.com/valid-indieweb-target.html",
|
Target: "https://brainbaking.com/valid-indieweb-target.html",
|
||||||
},
|
},
|
||||||
json: `{"author":{"name":"Stampeding Longhorn", "picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"}, "content":"@wouter The cat pictures are awesome. for jest tests!", "name":"@wouter The cat pictures are awesome. for jest tests!", "published":"2021-03-02T16:17:18.000Z", "source":"https://brainbaking.com/valid-bridgy-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://social.linux.pizza/@StampedingLonghorn/105821099684887793"}`,
|
json: `{"author":{"name":"Stampeding Longhorn", "picture":"https://cdn.social.linux.pizza/v1/AUTH_91eb37814936490c95da7b85993cc2ff/sociallinuxpizza/accounts/avatars/000/185/996/original/9e36da0c093cfc9b.png"}, "content":"@wouter The cat pictures are awesome. for jest tests!", "name":"@wouter The cat pictures are awesome. for jest tests!", "published":"2021-03-02T16:17:18.000Z", "source":"https://brainbaking.com/valid-bridgy-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://social.linux.pizza/@StampedingLonghorn/105821099684887793"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "receive saves a JSON file of indieweb-metadata if all is valid",
|
label: "receive saves a JSON file of indieweb-metadata if all is valid",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-indieweb-source.html",
|
Source: "https://brainbaking.com/valid-indieweb-source.html",
|
||||||
target: "https://jefklakscodex.com/articles",
|
Target: "https://jefklakscodex.com/articles",
|
||||||
},
|
},
|
||||||
json: `{"author":{"name":"Wouter Groeneveld","picture":"https://brainbaking.com//img/avatar.jpg"},"name":"I just learned about https://www.inklestudios.com/...","content":"This is cool, I just found out about valid indieweb target - so cool","published":"2021-03-06T12:41:00","url":"https://brainbaking.com/notes/2021/03/06h12m41s48/","type":"mention","source":"https://brainbaking.com/valid-indieweb-source.html","target":"https://jefklakscodex.com/articles"}`,
|
json: `{"author":{"name":"Wouter Groeneveld","picture":"https://brainbaking.com//img/avatar.jpg"},"name":"I just learned about https://www.inklestudios.com/...","content":"This is cool, I just found out about valid indieweb target - so cool","published":"2021-03-06T12:41:00","url":"https://brainbaking.com/notes/2021/03/06h12m41s48/","type":"mention","source":"https://brainbaking.com/valid-indieweb-source.html","target":"https://jefklakscodex.com/articles"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "receive saves a JSON file of indieweb-metadata with summary as content if present",
|
label: "receive saves a JSON file of indieweb-metadata with summary as content if present",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-indieweb-source-with-summary.html",
|
Source: "https://brainbaking.com/valid-indieweb-source-with-summary.html",
|
||||||
target: "https://brainbaking.com/valid-indieweb-target.html",
|
Target: "https://brainbaking.com/valid-indieweb-target.html",
|
||||||
},
|
},
|
||||||
json: `{"author":{"name":"Wouter Groeneveld", "picture":"https://brainbaking.com//img/avatar.jpg"}, "content":"This is cool, this is a summary!", "name":"I just learned about https://www.inklestudios.com/...", "published":"2021-03-06T12:41:00", "source":"https://brainbaking.com/valid-indieweb-source-with-summary.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/notes/2021/03/06h12m41s48/"}`,
|
json: `{"author":{"name":"Wouter Groeneveld", "picture":"https://brainbaking.com//img/avatar.jpg"}, "content":"This is cool, this is a summary!", "name":"I just learned about https://www.inklestudios.com/...", "published":"2021-03-06T12:41:00", "source":"https://brainbaking.com/valid-indieweb-source-with-summary.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/notes/2021/03/06h12m41s48/"}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "receive saves a JSON file of non-indieweb-data such as title if all is valid",
|
label: "receive saves a JSON file of non-indieweb-data such as title if all is valid",
|
||||||
wm: webmention{
|
wm: Mention{
|
||||||
source: "https://brainbaking.com/valid-nonindieweb-source.html",
|
Source: "https://brainbaking.com/valid-nonindieweb-source.html",
|
||||||
target: "https://brainbaking.com/valid-indieweb-target.html",
|
Target: "https://brainbaking.com/valid-indieweb-target.html",
|
||||||
},
|
},
|
||||||
json: `{"author":{"name":"https://brainbaking.com/valid-nonindieweb-source.html", "picture":""}, "content":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "name":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "published":"2020-01-01T12:30:00", "source":"https://brainbaking.com/valid-nonindieweb-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/valid-nonindieweb-source.html"}`,
|
json: `{"author":{"name":"https://brainbaking.com/valid-nonindieweb-source.html", "picture":""}, "content":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "name":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "published":"2020-01-01T12:30:00", "source":"https://brainbaking.com/valid-nonindieweb-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/valid-nonindieweb-source.html"}`,
|
||||||
},
|
},
|
||||||
|
@ -106,14 +106,14 @@ func TestReceive(t *testing.T) {
|
||||||
return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC)
|
return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := &receiver {
|
receiver := &Receiver{
|
||||||
conf: conf,
|
Conf: conf,
|
||||||
restClient: &mocks.RestClientMock{
|
RestClient: &mocks.RestClientMock{
|
||||||
GetBodyFunc: mocks.RelPathGetBodyFunc(t),
|
GetBodyFunc: mocks.RelPathGetBodyFunc(t),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver.receive(tc.wm)
|
receiver.Receive(tc.wm)
|
||||||
|
|
||||||
actualJson, _ := ioutil.ReadFile(tc.wm.asPath(conf))
|
actualJson, _ := ioutil.ReadFile(tc.wm.asPath(conf))
|
||||||
assert.JSONEq(t, tc.json, string(actualJson))
|
assert.JSONEq(t, tc.json, string(actualJson))
|
||||||
|
@ -125,9 +125,9 @@ func TestReceiveTargetDoesNotExistAnymoreDeletesPossiblyOlderWebmention(t *testi
|
||||||
os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm)
|
os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm)
|
||||||
defer os.RemoveAll("testdata")
|
defer os.RemoveAll("testdata")
|
||||||
|
|
||||||
wm := webmention{
|
wm := Mention{
|
||||||
source: "https://brainbaking.com",
|
Source: "https://brainbaking.com",
|
||||||
target: "https://jefklakscodex.com/articles",
|
Target: "https://jefklakscodex.com/articles",
|
||||||
}
|
}
|
||||||
filename := wm.asPath(conf)
|
filename := wm.asPath(conf)
|
||||||
writeSomethingTo(filename)
|
writeSomethingTo(filename)
|
||||||
|
@ -137,31 +137,31 @@ func TestReceiveTargetDoesNotExistAnymoreDeletesPossiblyOlderWebmention(t *testi
|
||||||
return "", errors.New("whoops")
|
return "", errors.New("whoops")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
receiver := &receiver {
|
receiver := &Receiver{
|
||||||
conf: conf,
|
Conf: conf,
|
||||||
restClient: client,
|
RestClient: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver.receive(wm)
|
receiver.Receive(wm)
|
||||||
assert.NoFileExists(t, filename)
|
assert.NoFileExists(t, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReceiveTargetThatDoesNotPointToTheSourceDoesNothing(t *testing.T) {
|
func TestReceiveTargetThatDoesNotPointToTheSourceDoesNothing(t *testing.T) {
|
||||||
wm := webmention{
|
wm := Mention{
|
||||||
source: "https://brainbaking.com/valid-indieweb-source.html",
|
Source: "https://brainbaking.com/valid-indieweb-source.html",
|
||||||
target: "https://brainbaking.com/valid-indieweb-source.html",
|
Target: "https://brainbaking.com/valid-indieweb-source.html",
|
||||||
}
|
}
|
||||||
filename := wm.asPath(conf)
|
filename := wm.asPath(conf)
|
||||||
writeSomethingTo(filename)
|
writeSomethingTo(filename)
|
||||||
|
|
||||||
receiver := &receiver {
|
receiver := &Receiver{
|
||||||
conf: conf,
|
Conf: conf,
|
||||||
restClient: &mocks.RestClientMock{
|
RestClient: &mocks.RestClientMock{
|
||||||
GetBodyFunc: mocks.RelPathGetBodyFunc(t),
|
GetBodyFunc: mocks.RelPathGetBodyFunc(t),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver.receive(wm)
|
receiver.Receive(wm)
|
||||||
assert.NoFileExists(t, filename)
|
assert.NoFileExists(t, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,12 +169,12 @@ func TestProcessSourceBodyAbortsIfNoMentionOfTargetFoundInSourceHtml(t *testing.
|
||||||
os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm)
|
os.MkdirAll("testdata/jefklakscodex.com", os.ModePerm)
|
||||||
defer os.RemoveAll("testdata")
|
defer os.RemoveAll("testdata")
|
||||||
|
|
||||||
wm := webmention{
|
wm := Mention{
|
||||||
source: "https://brainbaking.com",
|
Source: "https://brainbaking.com",
|
||||||
target: "https://jefklakscodex.com/articles",
|
Target: "https://jefklakscodex.com/articles",
|
||||||
}
|
}
|
||||||
receiver := &receiver {
|
receiver := &Receiver{
|
||||||
conf: conf,
|
Conf: conf,
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver.processSourceBody("<html>my nice body</html>", wm)
|
receiver.processSourceBody("<html>my nice body</html>", wm)
|
||||||
|
|
|
@ -11,5 +11,5 @@ func BadRequest(w http.ResponseWriter) {
|
||||||
|
|
||||||
func Accept(w http.ResponseWriter) {
|
func Accept(w http.ResponseWriter) {
|
||||||
w.WriteHeader(202)
|
w.WriteHeader(202)
|
||||||
w.Write([]byte("Thanks, bro. Will send these webmentions soon, pinky swear!"))
|
w.Write([]byte("Thanks, bro. Will process this soon, pinky swear!"))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue