Custom webmention sending implementation and other improvements

This commit is contained in:
Jan-Lukas Else 2020-11-16 18:34:29 +01:00
parent 6dd99289ad
commit 3a866fb3c0
22 changed files with 285 additions and 225 deletions

View File

@ -221,7 +221,7 @@ func apGetRemoteActor(iri string) (*asPerson, int, error) {
return nil, 0, err
}
req.Header.Set("Accept", contentTypeAS)
req.Header.Set("User-Agent", "GoBlog")
req.Header.Set(userAgent, appUserAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, 0, err
@ -273,7 +273,7 @@ func (p *post) apPost() {
createActivity := make(map[string]interface{})
createActivity["@context"] = asContext
createActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
createActivity["id"] = appConfig.Server.PublicAddress + p.Path
createActivity["id"] = p.fullURL()
createActivity["published"] = n.Published
createActivity["type"] = "Create"
createActivity["object"] = n
@ -293,7 +293,7 @@ func (p *post) apUpdate() {
updateActivity := make(map[string]interface{})
updateActivity["@context"] = asContext
updateActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
updateActivity["id"] = appConfig.Server.PublicAddress + p.Path
updateActivity["id"] = p.fullURL()
updateActivity["published"] = time.Now().Format("2006-01-02T15:04:05-07:00")
updateActivity["type"] = "Update"
updateActivity["object"] = n
@ -307,10 +307,10 @@ func (p *post) apAnnounce() {
announceActivity := make(map[string]interface{})
announceActivity["@context"] = asContext
announceActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
announceActivity["id"] = appConfig.Server.PublicAddress + p.Path + "#announce"
announceActivity["id"] = p.fullURL() + "#announce"
announceActivity["published"] = p.toASNote().Published
announceActivity["type"] = "Announce"
announceActivity["object"] = appConfig.Server.PublicAddress + p.Path
announceActivity["object"] = p.fullURL()
apSendToAllFollowers(p.Blog, announceActivity)
}
@ -321,10 +321,10 @@ func (p *post) apDelete() {
deleteActivity := make(map[string]interface{})
deleteActivity["@context"] = asContext
deleteActivity["actor"] = appConfig.Blogs[p.Blog].apIri()
deleteActivity["id"] = appConfig.Server.PublicAddress + p.Path + "#delete"
deleteActivity["id"] = p.fullURL() + "#delete"
deleteActivity["type"] = "Delete"
deleteActivity["object"] = map[string]string{
"id": appConfig.Server.PublicAddress + p.Path,
"id": p.fullURL(),
"type": "Tombstone",
}
apSendToAllFollowers(p.Blog, deleteActivity)
@ -405,7 +405,7 @@ func apSendSigned(blog *configBlog, activity interface{}, to string) error {
}
r.Header.Set("Accept-Charset", "utf-8")
r.Header.Set("Date", time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
r.Header.Set("User-Agent", "GoBlog")
r.Header.Set(userAgent, appUserAgent)
r.Header.Set("Accept", contentTypeASUTF8)
r.Header.Set(contentType, contentTypeASUTF8)
r.Header.Set("Host", iri.Host)

View File

@ -74,8 +74,8 @@ func (p *post) toASNote() *asNote {
Context: asContext,
To: []string{"https://www.w3.org/ns/activitystreams#Public"},
MediaType: contentTypeHTML,
ID: appConfig.Server.PublicAddress + p.Path,
URL: appConfig.Server.PublicAddress + p.Path,
ID: p.fullURL(),
URL: p.fullURL(),
AttributedTo: appConfig.Blogs[p.Blog].apIri(),
}
// Name and Type
@ -133,8 +133,8 @@ func (b *configBlog) serveActivityStreams(blog string, w http.ResponseWriter) {
PreferredUsername: blog,
Inbox: appConfig.Server.PublicAddress + "/activitypub/inbox/" + blog,
PublicKey: &asPublicKey{
Owner: appConfig.Server.PublicAddress + b.Path,
ID: appConfig.Server.PublicAddress + b.Path + "#main-key",
Owner: b.apIri(),
ID: b.apIri() + "#main-key",
PublicKeyPem: string(pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Headers: nil,

2
api.go
View File

@ -55,6 +55,6 @@ func apiPostCreateHugo(w http.ResponseWriter, r *http.Request) {
return
}
}
w.Header().Set("Location", appConfig.Server.PublicAddress+p.Path)
w.Header().Set("Location", p.fullURL())
w.WriteHeader(http.StatusCreated)
}

View File

@ -55,7 +55,7 @@ func generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Reques
}
feed.Add(&feeds.Item{
Title: p.title(),
Link: &feeds.Link{Href: appConfig.Server.PublicAddress + p.Path},
Link: &feeds.Link{Href: p.fullURL()},
Description: p.summary(),
Id: p.Path,
Content: string(p.html()),

10
go.mod
View File

@ -24,7 +24,7 @@ require (
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/lopezator/migrator v0.3.0
github.com/magiconair/properties v1.8.4 // indirect
github.com/mattn/go-sqlite3 v1.14.4
github.com/mattn/go-sqlite3 v1.14.5
github.com/miekg/dns v1.1.35 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
@ -37,23 +37,23 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.10
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/vcraescu/go-paginator v1.0.0
github.com/yuin/goldmark v1.2.1
github.com/yuin/goldmark-emoji v1.0.1
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.16.0 // indirect
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba // indirect
golang.org/x/sys v0.0.0-20201116161645-c061ba923fbb // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/tools v0.0.0-20201116002733-ac45abd4c88c // indirect
golang.org/x/tools v0.0.0-20201116172350-d68bbb546781 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
honnef.co/go/tools v0.0.1-2020.1.6 // indirect
willnorris.com/go/microformats v1.1.1
willnorris.com/go/webmention v0.0.0-20200623235404-057ea514ab98
)

26
go.sum
View File

@ -208,8 +208,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.4 h1:4rQjbDxdu9fSgI/r3KN72G3c2goxknAqHHgPWWs8UlI=
github.com/mattn/go-sqlite3 v1.14.4/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mholt/acmez v0.1.1 h1:KQODCqk+hBn3O7qfCRPj6L96uG65T5BSS95FKNEqtdA=
@ -315,10 +315,10 @@ github.com/tdewolff/parse/v2 v2.5.5/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1I
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/vcraescu/go-paginator v1.0.0 h1:ilNmRhlgG8N44LuxfGoPI2u8guXMA6gUqaPGA5BmRFs=
github.com/vcraescu/go-paginator v1.0.0/go.mod h1:caZCjjt2qcA1O2aDzW7lwAcK4Rxw3LNvdEVF/ONxZWw=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
@ -357,8 +357,8 @@ golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 h1:0WDrJ1E7UolDk1KhTXxxw3Fc8qtk5x7dHP431KHEJls=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582/go.mod h1:tCqSYrHVcf3i63Co2FzBkTCo2gdF6Zak62921dSfraU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -387,7 +387,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -438,12 +437,15 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201116161645-c061ba923fbb h1:+EHGEcgeA7ESswi5i4ojbo7sRzlz7vWoxFGcMuEZtu8=
golang.org/x/sys v0.0.0-20201116161645-c061ba923fbb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201113234701-d7a72108b828 h1:htWEtQEuEVJ4tU/Ngx7Cd/4Q7e3A5Up1owgyBtVsTwk=
golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@ -478,8 +480,8 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnf
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201116002733-ac45abd4c88c h1:quJUizHRFn7XriXTIOCLKSr76x2cMbNGfvfy9ubOO0g=
golang.org/x/tools v0.0.0-20201116002733-ac45abd4c88c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201116172350-d68bbb546781 h1:pupwog4teA+VTW6Kpi3Z6f0AR4+5MBi0+AN5ym9fzaQ=
golang.org/x/tools v0.0.0-20201116172350-d68bbb546781/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
@ -544,5 +546,3 @@ honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzE
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
willnorris.com/go/microformats v1.1.1 h1:h5tk2luq6KBIRcwMGdksxdeea4GGuWrRFie5460OAbo=
willnorris.com/go/microformats v1.1.1/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0=
willnorris.com/go/webmention v0.0.0-20200623235404-057ea514ab98 h1:06Zf8bMVQ+OBHceFvlr/SYZyYnkyVlWIkvediXHyQQU=
willnorris.com/go/webmention v0.0.0-20200623235404-057ea514ab98/go.mod h1:p+ZRAsZS2pzZ6kX3GKWYurf3WZI2ygj7VbR8NM8qwfM=

View File

@ -21,7 +21,7 @@ func (p *post) postPostHooks() {
for _, cmdTmplString := range appConfig.Hooks.PostPost {
go func(p *post, cmdTmplString string) {
executeTemplateCommand("post-post", cmdTmplString, map[string]interface{}{
"URL": appConfig.Server.PublicAddress + p.Path,
"URL": p.fullURL(),
"Post": p,
})
}(p, cmdTmplString)
@ -38,7 +38,7 @@ func (p *post) postUpdateHooks() {
for _, cmdTmplString := range appConfig.Hooks.PostUpdate {
go func(p *post, cmdTmplString string) {
executeTemplateCommand("post-update", cmdTmplString, map[string]interface{}{
"URL": appConfig.Server.PublicAddress + p.Path,
"URL": p.fullURL(),
"Post": p,
})
}(p, cmdTmplString)
@ -54,7 +54,7 @@ func (p *post) postDeleteHooks() {
for _, cmdTmplString := range appConfig.Hooks.PostDelete {
go func(p *post, cmdTmplString string) {
executeTemplateCommand("post-delete", cmdTmplString, map[string]interface{}{
"URL": appConfig.Server.PublicAddress + p.Path,
"URL": p.fullURL(),
"Post": p,
})
}(p, cmdTmplString)

View File

@ -27,6 +27,9 @@ const (
contentTypeHTMLUTF8 = contentTypeHTML + charsetUtf8Suffix
contentTypeJSONUTF8 = contentTypeJSON + charsetUtf8Suffix
contentTypeASUTF8 = contentTypeAS + charsetUtf8Suffix
userAgent = "User-Agent"
appUserAgent = "GoBlog"
)
var (

View File

@ -100,7 +100,7 @@ func (p *post) toMfItem() *microformatItem {
Updated: []string{p.Updated},
Category: p.Parameters[appConfig.Micropub.CategoryParam],
Content: []string{content},
URL: []string{appConfig.Server.PublicAddress + p.Path},
URL: []string{p.fullURL()},
InReplyTo: p.Parameters[appConfig.Micropub.ReplyParam],
LikeOf: p.Parameters[appConfig.Micropub.LikeParam],
BookmarkOf: p.Parameters[appConfig.Micropub.BookmarkParam],
@ -181,7 +181,7 @@ func serveMicropubPost(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Location", appConfig.Server.PublicAddress+p.Path)
w.Header().Add("Location", p.fullURL())
w.WriteHeader(http.StatusAccepted)
return
}

View File

@ -47,7 +47,7 @@ func servePost(w http.ResponseWriter, r *http.Request) {
}
canonical := p.firstParameter("original")
if canonical == "" {
canonical = appConfig.Server.PublicAddress + p.Path
canonical = p.fullURL()
}
render(w, templatePost, &renderData{
blogString: p.Blog,
@ -56,18 +56,6 @@ func servePost(w http.ResponseWriter, r *http.Request) {
})
}
type indexTemplateData struct {
Blog string
Title string
Description string
Posts []*post
HasPrev bool
HasNext bool
First string
Prev string
Next string
}
type postPaginationAdapter struct {
config *postsRequestConfig
nums int
@ -151,7 +139,9 @@ func servePhotos(blog string, path string) func(w http.ResponseWriter, r *http.R
blog: blog,
path: path,
parameter: appConfig.Blogs[blog].Photos.Parameter,
template: templatePhotos,
title: appConfig.Blogs[blog].Photos.Title,
description: appConfig.Blogs[blog].Photos.Description,
summaryTemplate: templatePhotosSummary,
})
}
@ -159,7 +149,6 @@ func serveSearchResults(blog string, path string) func(w http.ResponseWriter, r
return serveIndex(&indexConfig{
blog: blog,
path: path,
template: templateIndex,
})
}
@ -170,7 +159,9 @@ type indexConfig struct {
tax *taxonomy
taxValue string
parameter string
template string
title string
description string
summaryTemplate string
}
func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
@ -205,7 +196,8 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
return
}
// Meta
var title, description string
title := ic.title
description := ic.description
if ic.tax != nil {
title = fmt.Sprintf("%s: %s", ic.tax.Title, ic.taxValue)
} else if ic.section != nil {
@ -238,22 +230,23 @@ func serveIndex(ic *indexConfig) func(w http.ResponseWriter, r *http.Request) {
nextPage = p.Page()
}
nextPath := fmt.Sprintf("%s/page/%d", path, nextPage)
template := ic.template
if len(template) == 0 {
template = templateIndex
summaryTemplate := ic.summaryTemplate
if summaryTemplate == "" {
summaryTemplate = templateSummary
}
render(w, template, &renderData{
render(w, templateIndex, &renderData{
blogString: ic.blog,
Canonical: appConfig.Server.PublicAddress + getBlogRelativePath(ic.blog, path),
Data: &indexTemplateData{
Title: title,
Description: description,
Posts: posts,
HasPrev: p.HasPrev(),
HasNext: p.HasNext(),
First: getBlogRelativePath(ic.blog, path),
Prev: getBlogRelativePath(ic.blog, prevPath),
Next: getBlogRelativePath(ic.blog, nextPath),
Data: map[string]interface{}{
"Title": title,
"Description": description,
"Posts": posts,
"HasPrev": p.HasPrev(),
"HasNext": p.HasNext(),
"First": getBlogRelativePath(ic.blog, path),
"Prev": getBlogRelativePath(ic.blog, prevPath),
"Next": getBlogRelativePath(ic.blog, nextPath),
"SummaryTemplate": summaryTemplate,
},
})
}

View File

@ -8,6 +8,10 @@ import (
"github.com/PuerkitoBio/goquery"
)
func (p *post) fullURL() string {
return appConfig.Server.PublicAddress + p.Path
}
func (p *post) firstParameter(parameter string) (result string) {
if pp := p.Parameters[parameter]; len(pp) > 0 {
result = pp[0]

View File

@ -27,8 +27,9 @@ const templatePost = "post"
const templateError = "error"
const templateIndex = "index"
const templateTaxonomy = "taxonomy"
const templatePhotos = "photos"
const templateSearch = "search"
const templateSummary = "summary"
const templatePhotosSummary = "photosummary"
var templates map[string]*template.Template
var templateFunctions template.FuncMap
@ -76,7 +77,7 @@ func initRendering() error {
},
"postmentions": func(p *post) []*mention {
mentions, _ := getWebmentions(&webmentionsRequestConfig{
target: appConfig.Server.PublicAddress + p.Path,
target: p.fullURL(),
status: webmentionStatusApproved,
asc: true,
})

View File

@ -19,7 +19,7 @@ func serveSitemap(w http.ResponseWriter, r *http.Request) {
sm.Minify = true
for _, p := range posts {
item := &sitemap.URL{
Loc: appConfig.Server.PublicAddress + p.Path}
Loc: p.fullURL()}
var lastMod time.Time
if p.Updated != "" {
lastMod, _ = dateparse.ParseIn(p.Updated, time.Local)

View File

@ -18,7 +18,7 @@ func (p *post) tgPost() {
message.WriteString(title)
message.WriteString("\n\n")
}
message.WriteString(appConfig.Server.PublicAddress + p.Path)
message.WriteString(p.fullURL())
sendTelegramMessage(message.String(), appConfig.Blogs[p.Blog].Telegram.BotToken, appConfig.Blogs[p.Blog].Telegram.ChatID)
}

View File

@ -13,8 +13,9 @@
<hr>
{{ end }}
{{ $blog := .Blog }}
{{ $summaryTemplate := .Data.SummaryTemplate }}
{{ range $i, $post := .Data.Posts }}
{{ include "summary" $blog $post }}
{{ include $summaryTemplate $blog $post }}
{{ end }}
{{ if .Data.HasPrev }}
<p><a href="{{ .Data.Prev }}">{{ string .Blog.Lang "prev" }}</a></p>

View File

@ -1,27 +0,0 @@
{{ define "title" }}
<title>{{ with .Blog.Photos.Title }}{{ . }} - {{ end }}{{ .Blog.Title }}</title>
{{ end }}
{{ define "main" }}
<main>
{{ with .Blog.Photos.Title }}<h1>{{ . }}</h1>{{ end }}
{{ with .Blog.Photos.Description }}{{ md . }}{{ end }}
{{ if (or .Blog.Photos.Title .Blog.Photos.Description) }}
<hr>
{{ end }}
{{ $blog := .Blog }}
{{ range $i, $post := .Data.Posts }}
{{ include "photosummary" $blog $post }}
{{ end }}
{{ if .Data.HasPrev }}
<p><a href="{{ .Data.Prev }}">{{ string .Blog.Lang "prev" }}</a></p>
{{ end }}
{{ if .Data.HasNext }}
<p><a href="{{ .Data.Next }}">{{ string .Blog.Lang "next" }}</a></p>
{{ end }}
</main>
{{ end }}
{{ define "photos" }}
{{ template "base" . }}
{{ end }}

View File

@ -1,23 +0,0 @@
{{ define "title" }}{{ end }}
{{ define "main" }}
<main class=h-entry>
<article>
<data class="u-url hide" value="{{ absolute .Data.Path }}"></data>
{{ with title .Data }}<h1 class=p-name>{{ . }}</h1>{{ end }}
{{ include "postmeta" . }}
{{ if .Data.Content }}
<div class=e-content>
{{ content .Data }}
{{ with p .Data "link" }}
<p><a class="u-bookmark-of" href="{{ . }}" target="_blank" rel="noopener">{{ . }}</a></p>
{{ end }}
</div>
{{ end }}
</article>
</main>
{{ end }}
{{ define "postbasic" }}
{{ template "base" . }}
{{ end }}

View File

@ -9,7 +9,11 @@
{{ $blog := .Blog }}
{{ range $i, $mention := .Data.Verified }}
<div class="p">
<p>From: {{ $mention.Source }}<br/>To: {{ $mention.Target }}<br/>Created: {{ unixtodate $mention.Created }}</p>
<p>
From: <a href="{{ $mention.Source }}" target="_blank" rel="noopener noreferrer">{{ $mention.Source }}</a><br/>
To: <a href="{{ $mention.Target }}" target="_blank">{{ $mention.Target }}</a><br/>
Created: {{ unixtodate $mention.Created }}
</p>
<form method="post">
<input type="submit" formaction="/webmention/admin/approve/{{ $mention.ID }}" value="{{ string $blog.Lang "approve" }}">
<input type="submit" formaction="/webmention/admin/delete/{{ $mention.ID }}" value="{{ string $blog.Lang "delete" }}">

View File

@ -71,10 +71,6 @@ func isAbsoluteURL(s string) bool {
}
func allLinksFromHTML(r io.Reader, baseURL string) ([]string, error) {
bu, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
doc, err := goquery.NewDocumentFromReader(r)
if err != nil {
return nil, err
@ -82,10 +78,25 @@ func allLinksFromHTML(r io.Reader, baseURL string) ([]string, error) {
links := []string{}
doc.Find("a[href]").Each(func(_ int, item *goquery.Selection) {
if href, exists := item.Attr("href"); exists {
if ref, err := url.Parse(href); err == nil {
links = append(links, bu.ResolveReference(ref).String())
}
links = append(links, href)
}
})
return links, nil
links, err = resolveURLReferences(baseURL, links...)
return links, err
}
func resolveURLReferences(base string, refs ...string) ([]string, error) {
b, err := url.Parse(base)
if err != nil {
return nil, err
}
var urls []string
for _, r := range refs {
u, err := url.Parse(r)
if err != nil {
continue
}
urls = append(urls, b.ResolveReference(u).String())
}
return urls, nil
}

View File

@ -4,7 +4,6 @@ import (
"database/sql"
"errors"
"fmt"
"log"
"net/http"
"net/http/httptest"
"strconv"
@ -12,7 +11,6 @@ import (
"time"
"github.com/go-chi/chi"
"willnorris.com/go/webmention"
)
type webmentionStatus string
@ -38,15 +36,6 @@ func initWebmention() {
startWebmentionVerifier()
}
func startWebmentionVerifier() {
go func() {
for {
time.Sleep(30 * time.Second)
verifyNextWebmention()
}
}()
}
func handleWebmention(w http.ResponseWriter, r *http.Request) {
m, err := extractMention(r)
if err != nil {
@ -149,37 +138,6 @@ func webmentionExists(source, target string) bool {
return result == 1
}
func verifyNextWebmention() error {
m := &mention{}
oldStatus := ""
row, err := appDbQueryRow("select id, source, target, status from webmentions where (status = ? or status = ?) limit 1", webmentionStatusNew, webmentionStatusRenew)
if err != nil {
return err
}
if err := row.Scan(&m.ID, &m.Source, &m.Target, &oldStatus); err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
if err := wmVerify(m); err != nil {
// Invalid
return deleteWebmention(m.ID)
}
if len(m.Content) > 500 {
m.Content = m.Content[0:497] + "…"
}
newStatus := webmentionStatusVerified
if strings.HasPrefix(m.Source, appConfig.Server.PublicAddress) {
// Approve if it's server-intern
newStatus = webmentionStatusApproved
}
_, err = appDbExec("update webmentions set status = ?, title = ?, content = ?, author = ? where id = ?", newStatus, m.Title, m.Content, m.Author, m.ID)
if oldStatus == string(webmentionStatusNew) {
sendNotification(fmt.Sprintf("New webmention from %s to %s", m.Source, m.Target))
}
return err
}
func createWebmention(source, target string) (err error) {
if webmentionExists(source, target) {
_, err = appDbExec("update webmentions set status = ? where source = ? and target = ?", webmentionStatusRenew, source, target)
@ -241,36 +199,3 @@ func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
}
return mentions, nil
}
func (p *post) sendWebmentions() error {
url := appConfig.Server.PublicAddress + p.Path
recorder := httptest.NewRecorder()
// Render basic post data
render(recorder, "postbasic", &renderData{
blogString: p.Blog,
Data: p,
})
discovered, err := webmention.DiscoverLinksFromReader(recorder.Result().Body, url, ".h-entry")
if err != nil {
return err
}
client := webmention.New(nil)
for _, link := range discovered {
if strings.HasPrefix(link, appConfig.Server.PublicAddress) {
// Save mention directly
createWebmention(url, link)
continue
}
endpoint, err := client.DiscoverEndpoint(link)
if err != nil || len(endpoint) < 1 {
continue
}
_, err = client.SendWebmention(endpoint, url, link)
if err != nil {
log.Println("Sending webmention to " + link + " failed")
continue
}
log.Println("Sent webmention to " + link)
}
return nil
}

131
webmentionSending.go Normal file
View File

@ -0,0 +1,131 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/tomnomnom/linkheader"
)
func (p *post) sendWebmentions() error {
links := []string{}
contentLinks, err := allLinksFromHTML(strings.NewReader(string(p.html())), p.fullURL())
if err != nil {
return err
}
links = append(links, contentLinks...)
links = append(links, p.firstParameter(appConfig.Micropub.LikeParam), p.firstParameter(appConfig.Micropub.ReplyParam), p.firstParameter(appConfig.Micropub.BookmarkParam))
for _, link := range links {
if link == "" {
continue
}
if strings.HasPrefix(link, appConfig.Server.PublicAddress) {
// Save mention directly
createWebmention(p.fullURL(), link)
continue
}
endpoint := discoverEndpoint(link)
if endpoint == "" {
continue
}
_, err = sendWebmention(endpoint, p.fullURL(), link)
if err != nil {
log.Println("Sending webmention to " + link + " failed")
continue
}
log.Println("Sent webmention to " + link)
}
return nil
}
func sendWebmention(endpoint, source, target string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(url.Values{
"source": []string{source},
"target": []string{target},
}.Encode()))
if err != nil {
return nil, err
}
req.Header.Set(contentType, contentTypeWWWForm)
req.Header.Set(userAgent, appUserAgent)
res, err := http.DefaultClient.Do(req)
if err != nil {
return res, err
}
if code := res.StatusCode; code < 200 || 300 <= code {
return res, fmt.Errorf("response error: %v", res.StatusCode)
}
return res, nil
}
func discoverEndpoint(urlStr string) string {
doRequest := func(method, urlStr string) string {
req, err := http.NewRequest(method, urlStr, nil)
if err != nil {
return ""
}
req.Header.Set(userAgent, appUserAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return ""
}
if code := resp.StatusCode; code < 200 || 300 <= code {
return ""
}
defer resp.Body.Close()
endpoint, err := extractEndpoint(resp)
if err != nil || endpoint == "" {
return ""
}
if urls, err := resolveURLReferences(urlStr, endpoint); err == nil && len(urls) > 0 && urls[0] != "" {
return urls[0]
}
return ""
}
headEndpoint := doRequest(http.MethodHead, urlStr)
if headEndpoint != "" {
return headEndpoint
}
getEndpoint := doRequest(http.MethodGet, urlStr)
if getEndpoint != "" {
return getEndpoint
}
return ""
}
func extractEndpoint(resp *http.Response) (string, error) {
// first check http link headers
if endpoint := wmEndpointHTTPLink(resp.Header); endpoint != "" {
return endpoint, nil
}
// then look in the HTML body
endpoint, err := wmEndpointHTMLLink(resp.Body)
if err != nil {
return "", err
}
return endpoint, nil
}
func wmEndpointHTTPLink(headers http.Header) string {
links := linkheader.ParseMultiple(headers[http.CanonicalHeaderKey("Link")]).FilterByRel("webmention")
for _, link := range links {
if u := link.URL; u != "" {
return u
}
}
return ""
}
func wmEndpointHTMLLink(r io.Reader) (string, error) {
doc, err := goquery.NewDocumentFromReader(r)
if err != nil {
return "", err
}
href, _ := doc.Find("a[href][rel=webmention],link[href][rel=webmention]").Attr("href")
return href, nil
}

View File

@ -2,29 +2,66 @@ package main
import (
"bytes"
"database/sql"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"willnorris.com/go/microformats"
)
func wmVerify(m *mention) error {
client := &http.Client{}
client.CheckRedirect = func(r *http.Request, via []*http.Request) error {
if len(via) > 15 {
return errors.New("too many redirects")
func startWebmentionVerifier() {
go func() {
for {
time.Sleep(30 * time.Second)
verifyNextWebmention()
}
}()
}
func verifyNextWebmention() error {
m := &mention{}
oldStatus := ""
row, err := appDbQueryRow("select id, source, target, status from webmentions where (status = ? or status = ?) limit 1", webmentionStatusNew, webmentionStatusRenew)
if err != nil {
return err
}
if err := row.Scan(&m.ID, &m.Source, &m.Target, &oldStatus); err == sql.ErrNoRows {
return nil
} else if err != nil {
return err
}
if err := wmVerify(m); err != nil {
// Invalid
return deleteWebmention(m.ID)
}
if len(m.Content) > 500 {
m.Content = m.Content[0:497] + "…"
}
newStatus := webmentionStatusVerified
if strings.HasPrefix(m.Source, appConfig.Server.PublicAddress) {
// Approve if it's server-intern
newStatus = webmentionStatusApproved
}
_, err = appDbExec("update webmentions set status = ?, title = ?, content = ?, author = ? where id = ?", newStatus, m.Title, m.Content, m.Author, m.ID)
if oldStatus == string(webmentionStatusNew) {
sendNotification(fmt.Sprintf("New webmention from %s to %s", m.Source, m.Target))
}
return err
}
func wmVerify(m *mention) error {
req, err := http.NewRequest(http.MethodGet, m.Source, nil)
if err != nil {
return err
}
req.Header.Set("User-Agent", "GoBlog")
resp, err := client.Do(req)
req.Header.Set(userAgent, appUserAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}