refactor errors/logging to be more Go-idiomatic
This commit is contained in:
parent
2f6e4992a4
commit
e6bd0ef669
|
@ -34,7 +34,7 @@ type IndiewebData struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Published string `json:"published"`
|
Published string `json:"published"`
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
IndiewebType string `json:"type"`
|
IndiewebType MfType `json:"type"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ func shorten(txt string) string {
|
||||||
if len(txt) <= 250 {
|
if len(txt) <= 250 {
|
||||||
return txt
|
return txt
|
||||||
}
|
}
|
||||||
return txt[0:250] + "..."
|
return txt[:250] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go stuff: entry.Properties["name"][0].(string),
|
// Go stuff: entry.Properties["name"][0].(string),
|
||||||
|
@ -103,7 +103,7 @@ func Prop(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 {
|
func Published(hEntry *microformats.Microformat, utcOffset int) string {
|
||||||
publishedDate := Str(hEntry, "published")
|
publishedDate := Str(hEntry, "published")
|
||||||
if publishedDate == "" {
|
if publishedDate == "" {
|
||||||
return PublishedNow(utcOffset)
|
return PublishedNow(utcOffset)
|
||||||
|
@ -119,20 +119,28 @@ func DetermineAuthorName(hEntry *microformats.Microformat) string {
|
||||||
return authorName
|
return authorName
|
||||||
}
|
}
|
||||||
|
|
||||||
func DetermineType(hEntry *microformats.Microformat) string {
|
type MfType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeLike MfType = "like"
|
||||||
|
TypeBookmark MfType = "bookmark"
|
||||||
|
TypeMention MfType = "mention"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Type(hEntry *microformats.Microformat) MfType {
|
||||||
likeOf := Str(hEntry, "like-of")
|
likeOf := Str(hEntry, "like-of")
|
||||||
if likeOf != "" {
|
if likeOf != "" {
|
||||||
return "like"
|
return TypeLike
|
||||||
}
|
}
|
||||||
bookmarkOf := Str(hEntry, "bookmark-of")
|
bookmarkOf := Str(hEntry, "bookmark-of")
|
||||||
if bookmarkOf != "" {
|
if bookmarkOf != "" {
|
||||||
return "bookmark"
|
return TypeBookmark
|
||||||
}
|
}
|
||||||
return "mention"
|
return TypeMention
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources
|
// Mastodon uids start with "tag:server", but we do want indieweb uids from other sources
|
||||||
func DetermineUrl(hEntry *microformats.Microformat, source string) string {
|
func Url(hEntry *microformats.Microformat, source string) string {
|
||||||
uid := Str(hEntry, "uid")
|
uid := Str(hEntry, "uid")
|
||||||
if uid != "" && strings.HasPrefix(uid, "http") {
|
if uid != "" && strings.HasPrefix(uid, "http") {
|
||||||
return uid
|
return uid
|
||||||
|
@ -144,7 +152,7 @@ func DetermineUrl(hEntry *microformats.Microformat, source string) string {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
|
||||||
func DetermineContent(hEntry *microformats.Microformat) string {
|
func Content(hEntry *microformats.Microformat) string {
|
||||||
bridgyTwitterContent := Str(hEntry, "bridgy-twitter-content")
|
bridgyTwitterContent := Str(hEntry, "bridgy-twitter-content")
|
||||||
if bridgyTwitterContent != "" {
|
if bridgyTwitterContent != "" {
|
||||||
return shorten(bridgyTwitterContent)
|
return shorten(bridgyTwitterContent)
|
||||||
|
|
|
@ -34,7 +34,7 @@ func HandlePost(conf *common.Config) http.HandlerFunc {
|
||||||
Source: rpc.Source(),
|
Source: rpc.Source(),
|
||||||
Target: rpc.Target(),
|
Target: rpc.Target(),
|
||||||
}
|
}
|
||||||
receiver := recv.Receiver{
|
receiver := &recv.Receiver{
|
||||||
RestClient: &rest.HttpClient{},
|
RestClient: &rest.HttpClient{},
|
||||||
Conf: conf,
|
Conf: conf,
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,8 @@ type Sender struct {
|
||||||
func (sender *Sender) SendPingbackToEndpoint(endpoint string, mention mf.Mention) {
|
func (sender *Sender) SendPingbackToEndpoint(endpoint string, mention mf.Mention) {
|
||||||
err := sender.RestClient.Post(endpoint, "text/xml", body.fill(mention))
|
err := sender.RestClient.Post(endpoint, "text/xml", body.fill(mention))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Str("endpoint", endpoint).Str("wm", mention.String()).Msg("Unable to send pingback")
|
log.Err(err).Stringer("wm", mention).Msg("Unable to send pingback")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info().Str("endpoint", endpoint).Str("wm", mention.String()).Msg("Pingback sent")
|
log.Info().Str("endpoint", endpoint).Stringer("wm", mention).Msg("Pingback sent")
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// someone already did this for me, yay! https://siongui.github.io/2015/03/03/go-parse-web-feed-rss-atom/
|
// someone already did this for me, yay!
|
||||||
type Rss2 struct {
|
type Rss2 struct {
|
||||||
XMLName xml.Name `xml:"rss"`
|
XMLName xml.Name `xml:"rss"`
|
||||||
Version string `xml:"version,attr"`
|
Version string `xml:"version,attr"`
|
||||||
|
@ -61,6 +61,7 @@ type Entry struct {
|
||||||
Author Author `xml:"author"`
|
Author Author `xml:"author"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Based on https://siongui.github.io/2015/03/03/go-parse-web-feed-rss-atom/
|
||||||
func ParseFeed(content []byte) (*Rss2, error) {
|
func ParseFeed(content []byte) (*Rss2, error) {
|
||||||
v := &Rss2{}
|
v := &Rss2{}
|
||||||
err := xml.Unmarshal(content, v)
|
err := xml.Unmarshal(content, v)
|
||||||
|
@ -77,5 +78,5 @@ func ParseFeed(content []byte) (*Rss2, error) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, errors.New("not RSS 2.0")
|
return v, errors.New("ParseFeed: not RSS 2.0")
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@ import (
|
||||||
"brainbaking.com/go-jamming/rest"
|
"brainbaking.com/go-jamming/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var httpClient = &rest.HttpClient{}
|
var (
|
||||||
|
httpClient = &rest.HttpClient{}
|
||||||
|
)
|
||||||
|
|
||||||
func HandleGet(conf *common.Config) http.HandlerFunc {
|
func HandleGet(conf *common.Config) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -23,11 +23,11 @@ type Receiver struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (recv *Receiver) Receive(wm mf.Mention) {
|
func (recv *Receiver) Receive(wm mf.Mention) {
|
||||||
log.Info().Str("Webmention", wm.String()).Msg("OK: looks valid")
|
log.Info().Stringer("wm", wm).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().Err(geterr).Msg(" ABORT: invalid url")
|
||||||
recv.deletePossibleOlderWebmention(wm)
|
recv.deletePossibleOlderWebmention(wm)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ func (recv *Receiver) Receive(wm mf.Mention) {
|
||||||
recv.processSourceBody(body, wm)
|
recv.processSourceBody(body, wm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes a possible webmention. Ignores remove errors.
|
||||||
func (recv *Receiver) deletePossibleOlderWebmention(wm mf.Mention) {
|
func (recv *Receiver) deletePossibleOlderWebmention(wm mf.Mention) {
|
||||||
os.Remove(wm.AsPath(recv.Conf))
|
os.Remove(wm.AsPath(recv.Conf))
|
||||||
}
|
}
|
||||||
|
@ -48,7 +49,9 @@ func (recv *Receiver) processSourceBody(body string, wm mf.Mention) {
|
||||||
data := microformats.Parse(strings.NewReader(body), wm.SourceUrl())
|
data := microformats.Parse(strings.NewReader(body), wm.SourceUrl())
|
||||||
indieweb := recv.convertBodyToIndiewebData(body, wm, mf.HEntry(data))
|
indieweb := recv.convertBodyToIndiewebData(body, wm, mf.HEntry(data))
|
||||||
|
|
||||||
recv.saveWebmentionToDisk(wm, indieweb)
|
if err := recv.saveWebmentionToDisk(wm, indieweb); err != nil {
|
||||||
|
log.Err(err).Msg("Unable to save Webmention to disk")
|
||||||
|
}
|
||||||
log.Info().Str("file", wm.AsPath(recv.Conf)).Msg("OK: Webmention processed.")
|
log.Info().Str("file", wm.AsPath(recv.Conf)).Msg("OK: Webmention processed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,46 +62,41 @@ func (recv *Receiver) convertBodyToIndiewebData(body string, wm mf.Mention, hEnt
|
||||||
return recv.parseBodyAsIndiewebSite(hEntry, wm)
|
return recv.parseBodyAsIndiewebSite(hEntry, wm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (recv *Receiver) saveWebmentionToDisk(wm mf.Mention, indieweb *mf.IndiewebData) {
|
func (recv *Receiver) saveWebmentionToDisk(wm mf.Mention, indieweb *mf.IndiewebData) error {
|
||||||
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")
|
return jsonErr
|
||||||
}
|
}
|
||||||
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")
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 mf.Mention) *mf.IndiewebData {
|
func (recv *Receiver) parseBodyAsIndiewebSite(hEntry *microformats.Microformat, wm mf.Mention) *mf.IndiewebData {
|
||||||
name := mf.Str(hEntry, "name")
|
|
||||||
pic := mf.Str(mf.Prop(hEntry, "author"), "photo")
|
|
||||||
mfType := mf.DetermineType(hEntry)
|
|
||||||
|
|
||||||
return &mf.IndiewebData{
|
return &mf.IndiewebData{
|
||||||
Name: name,
|
Name: mf.Str(hEntry, "name"),
|
||||||
Author: mf.IndiewebAuthor{
|
Author: mf.IndiewebAuthor{
|
||||||
Name: mf.DetermineAuthorName(hEntry),
|
Name: mf.DetermineAuthorName(hEntry),
|
||||||
Picture: pic,
|
Picture: mf.Str(mf.Prop(hEntry, "author"), "photo"),
|
||||||
},
|
},
|
||||||
Content: mf.DetermineContent(hEntry),
|
Content: mf.Content(hEntry),
|
||||||
Url: mf.DetermineUrl(hEntry, wm.Source),
|
Url: mf.Url(hEntry, wm.Source),
|
||||||
Published: mf.DeterminePublishedDate(hEntry, recv.Conf.UtcOffset),
|
Published: mf.Published(hEntry, recv.Conf.UtcOffset),
|
||||||
Source: wm.Source,
|
Source: wm.Source,
|
||||||
Target: wm.Target,
|
Target: wm.Target,
|
||||||
IndiewebType: mfType,
|
IndiewebType: mf.Type(hEntry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
titleRegexp = regexp.MustCompile(`<title>(.*?)<\/title>`)
|
||||||
|
)
|
||||||
|
|
||||||
func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm mf.Mention) *mf.IndiewebData {
|
func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm mf.Mention) *mf.IndiewebData {
|
||||||
r := regexp.MustCompile(`<title>(.*?)<\/title>`)
|
title := nonIndiewebTitle(body, wm)
|
||||||
titleMatch := r.FindStringSubmatch(body)
|
|
||||||
title := wm.Source
|
|
||||||
if titleMatch != nil {
|
|
||||||
title = titleMatch[1]
|
|
||||||
}
|
|
||||||
return &mf.IndiewebData{
|
return &mf.IndiewebData{
|
||||||
Author: mf.IndiewebAuthor{
|
Author: mf.IndiewebAuthor{
|
||||||
Name: wm.Source,
|
Name: wm.Source,
|
||||||
|
@ -107,8 +105,17 @@ func (recv *Receiver) parseBodyAsNonIndiewebSite(body string, wm mf.Mention) *mf
|
||||||
Content: title,
|
Content: title,
|
||||||
Published: mf.PublishedNow(recv.Conf.UtcOffset),
|
Published: mf.PublishedNow(recv.Conf.UtcOffset),
|
||||||
Url: wm.Source,
|
Url: wm.Source,
|
||||||
IndiewebType: "mention",
|
IndiewebType: mf.TypeMention,
|
||||||
Source: wm.Source,
|
Source: wm.Source,
|
||||||
Target: wm.Target,
|
Target: wm.Target,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nonIndiewebTitle(body string, wm mf.Mention) string {
|
||||||
|
titleMatch := titleRegexp.FindStringSubmatch(body)
|
||||||
|
title := wm.Source
|
||||||
|
if titleMatch != nil {
|
||||||
|
title = titleMatch[1]
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
|
@ -8,41 +8,41 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeWebmention string = "webmention"
|
typeWebmention string = "webmention"
|
||||||
TypeUnknown string = "unknown"
|
typeUnknown string = "unknown"
|
||||||
TypePingback string = "pingback"
|
typePingback string = "pingback"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (sndr *Sender) discover(target string) (link string, mentionType string) {
|
func (sndr *Sender) discover(target string) (link string, mentionType string) {
|
||||||
mentionType = TypeUnknown
|
mentionType = typeUnknown
|
||||||
header, body, err := sndr.RestClient.GetBody(target)
|
header, body, err := sndr.RestClient.GetBody(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Str("target", target).Msg("Failed to discover possible endpoint, aborting send")
|
log.Warn().Str("target", target).Msg("Failed to discover possible endpoint, aborting send")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(header.Get("link"), TypeWebmention) {
|
if strings.Contains(header.Get("link"), typeWebmention) {
|
||||||
return buildWebmentionHeaderLink(header.Get("link")), TypeWebmention
|
return buildWebmentionHeaderLink(header.Get("link")), typeWebmention
|
||||||
}
|
}
|
||||||
if header.Get("X-Pingback") != "" {
|
if header.Get("X-Pingback") != "" {
|
||||||
return header.Get("X-Pingback"), TypePingback
|
return header.Get("X-Pingback"), typePingback
|
||||||
}
|
}
|
||||||
|
|
||||||
// this also complies with w3.org regulations: relative endpoint could be possible
|
// this also complies with w3.org regulations: relative endpoint could be possible
|
||||||
format := microformats.Parse(strings.NewReader(body), rest.BaseUrlOf(target))
|
format := microformats.Parse(strings.NewReader(body), rest.BaseUrlOf(target))
|
||||||
if len(format.Rels[TypeWebmention]) > 0 {
|
if len(format.Rels[typeWebmention]) > 0 {
|
||||||
mentionType = TypeWebmention
|
mentionType = typeWebmention
|
||||||
link = format.Rels[TypeWebmention][0]
|
link = format.Rels[typeWebmention][0]
|
||||||
} else if len(format.Rels[TypePingback]) > 0 {
|
} else if len(format.Rels[typePingback]) > 0 {
|
||||||
mentionType = TypePingback
|
mentionType = typePingback
|
||||||
link = format.Rels[TypePingback][0]
|
link = format.Rels[typePingback][0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// e.g. Link: <http://aaronpk.example/webmention-endpoint>; rel="webmention"
|
||||||
func buildWebmentionHeaderLink(link string) string {
|
func buildWebmentionHeaderLink(link string) string {
|
||||||
// e.g. Link: <http://aaronpk.example/webmention-endpoint>; rel="webmention"
|
|
||||||
raw := strings.Split(link, ";")[0][1:]
|
raw := strings.Split(link, ";")[0][1:]
|
||||||
return raw[:len(raw)-1]
|
return raw[:len(raw)-1]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,49 +23,49 @@ func TestDiscover(t *testing.T) {
|
||||||
"discover 'unknown' if no link is present",
|
"discover 'unknown' if no link is present",
|
||||||
"https://brainbaking.com/link-discover-test-none.html",
|
"https://brainbaking.com/link-discover-test-none.html",
|
||||||
"",
|
"",
|
||||||
TypeUnknown,
|
typeUnknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"prefer webmentions over pingbacks if both links are present",
|
"prefer webmentions over pingbacks if both links are present",
|
||||||
"https://brainbaking.com/link-discover-bothtypes.html",
|
"https://brainbaking.com/link-discover-bothtypes.html",
|
||||||
"http://aaronpk.example/webmention-endpoint",
|
"http://aaronpk.example/webmention-endpoint",
|
||||||
TypeWebmention,
|
typeWebmention,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pingbacks: discover link if present in header",
|
"pingbacks: discover link if present in header",
|
||||||
"https://brainbaking.com/pingback-discover-test.html",
|
"https://brainbaking.com/pingback-discover-test.html",
|
||||||
"http://aaronpk.example/pingback-endpoint",
|
"http://aaronpk.example/pingback-endpoint",
|
||||||
TypePingback,
|
typePingback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pingbacks: discover link if sole entry somewhere in html",
|
"pingbacks: discover link if sole entry somewhere in html",
|
||||||
"https://brainbaking.com/pingback-discover-test-single.html",
|
"https://brainbaking.com/pingback-discover-test-single.html",
|
||||||
"http://aaronpk.example/pingback-endpoint-body",
|
"http://aaronpk.example/pingback-endpoint-body",
|
||||||
TypePingback,
|
typePingback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pingbacks: use link in header if multiple present in html",
|
"pingbacks: use link in header if multiple present in html",
|
||||||
"https://brainbaking.com/pingback-discover-test-multiple.html",
|
"https://brainbaking.com/pingback-discover-test-multiple.html",
|
||||||
"http://aaronpk.example/pingback-endpoint-header",
|
"http://aaronpk.example/pingback-endpoint-header",
|
||||||
TypePingback,
|
typePingback,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"webmentions: discover link if present in header",
|
"webmentions: discover link if present in header",
|
||||||
"https://brainbaking.com/link-discover-test.html",
|
"https://brainbaking.com/link-discover-test.html",
|
||||||
"http://aaronpk.example/webmention-endpoint",
|
"http://aaronpk.example/webmention-endpoint",
|
||||||
TypeWebmention,
|
typeWebmention,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"webmentions: discover link if sole entry somewhere in html",
|
"webmentions: discover link if sole entry somewhere in html",
|
||||||
"https://brainbaking.com/link-discover-test-single.html",
|
"https://brainbaking.com/link-discover-test-single.html",
|
||||||
"http://aaronpk.example/webmention-endpoint-body",
|
"http://aaronpk.example/webmention-endpoint-body",
|
||||||
TypeWebmention,
|
typeWebmention,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"webmentions: use link in header if multiple present in html",
|
"webmentions: use link in header if multiple present in html",
|
||||||
"https://brainbaking.com/link-discover-test-multiple.html",
|
"https://brainbaking.com/link-discover-test-multiple.html",
|
||||||
"http://aaronpk.example/webmention-endpoint-header",
|
"http://aaronpk.example/webmention-endpoint-header",
|
||||||
TypeWebmention,
|
typeWebmention,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
|
|
@ -54,14 +54,17 @@ func (snder *Sender) Collect(xml string, since time.Time) ([]RSSItem, error) {
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hrefRegexp = regexp.MustCompile(`href="(.+?)"`)
|
||||||
|
extRegexp = regexp.MustCompile(`\.(gif|zip|rar|bz2|gz|7z|jpe?g|tiff?|png|webp|bmp)$`)
|
||||||
|
)
|
||||||
|
|
||||||
func (snder *Sender) collectUniqueHrefsFromDescription(html string) []string {
|
func (snder *Sender) collectUniqueHrefsFromDescription(html string) []string {
|
||||||
r := regexp.MustCompile(`href="(.+?)"`)
|
|
||||||
ext := regexp.MustCompile(`\.(gif|zip|rar|bz2|gz|7z|jpe?g|tiff?|png|webp|bmp)$`)
|
|
||||||
urlmap := common.NewSet()
|
urlmap := common.NewSet()
|
||||||
|
|
||||||
for _, match := range r.FindAllStringSubmatch(html, -1) {
|
for _, match := range hrefRegexp.FindAllStringSubmatch(html, -1) {
|
||||||
url := match[1] // [0] is the match of the entire expression, [1] is the capture group
|
url := match[1] // [0] is the match of the entire expression, [1] is the capture group
|
||||||
if !ext.MatchString(url) && !snder.Conf.ContainsDisallowedDomain(url) {
|
if !extRegexp.MatchString(url) && !snder.Conf.ContainsDisallowedDomain(url) {
|
||||||
urlmap.Add(url)
|
urlmap.Add(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,22 @@ type Sender struct {
|
||||||
|
|
||||||
func (snder *Sender) Send(domain string, since string) {
|
func (snder *Sender) Send(domain string, since string) {
|
||||||
log.Info().Str("domain", domain).Str("since", since).Msg(` OK: someone wants to send mentions`)
|
log.Info().Str("domain", domain).Str("since", since).Msg(` OK: someone wants to send mentions`)
|
||||||
_, feed, err := snder.RestClient.GetBody("https://" + domain + "/index.xml")
|
feedUrl := "https://" + domain + "/index.xml"
|
||||||
|
_, feed, err := snder.RestClient.GetBody(feedUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Str("domain", domain).Msg("Unable to retrieve RSS feed, aborting send")
|
log.Err(err).Str("url", feedUrl).Msg("Unable to retrieve RSS feed, send aborted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
snder.parseRssFeed(feed, common.IsoToTime(since))
|
if err = snder.parseRssFeed(feed, common.IsoToTime(since)); err != nil {
|
||||||
log.Info().Str("domain", domain).Str("since", since).Msg(` OK: sending done.`)
|
log.Err(err).Str("url", feedUrl).Msg("Unable to parse RSS feed, send aborted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (snder *Sender) parseRssFeed(feed string, since time.Time) {
|
func (snder *Sender) parseRssFeed(feed string, since time.Time) error {
|
||||||
items, err := snder.Collect(feed, since)
|
items, err := snder.Collect(feed, since)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Unable to parse RSS fed, aborting send")
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
@ -51,12 +52,13 @@ func (snder *Sender) parseRssFeed(feed string, since time.Time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var mentionFuncs = map[string]func(snder *Sender, mention mf.Mention, endpoint string){
|
var mentionFuncs = map[string]func(snder *Sender, mention mf.Mention, endpoint string){
|
||||||
TypeUnknown: func(snder *Sender, mention mf.Mention, endpoint string) {},
|
typeUnknown: func(snder *Sender, mention mf.Mention, endpoint string) {},
|
||||||
TypeWebmention: sendMentionAsWebmention,
|
typeWebmention: sendMentionAsWebmention,
|
||||||
TypePingback: sendMentionAsPingback,
|
typePingback: sendMentionAsPingback,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (snder *Sender) sendMention(mention mf.Mention) {
|
func (snder *Sender) sendMention(mention mf.Mention) {
|
||||||
|
@ -67,10 +69,10 @@ func (snder *Sender) sendMention(mention mf.Mention) {
|
||||||
func sendMentionAsWebmention(snder *Sender, mention mf.Mention, endpoint string) {
|
func sendMentionAsWebmention(snder *Sender, mention mf.Mention, endpoint string) {
|
||||||
err := snder.RestClient.PostForm(endpoint, mention.AsFormValues())
|
err := snder.RestClient.PostForm(endpoint, mention.AsFormValues())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Str("endpoint", endpoint).Str("wm", mention.String()).Msg("Webmention send failed")
|
log.Err(err).Str("endpoint", endpoint).Stringer("wm", mention).Msg("Webmention send failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info().Str("endpoint", endpoint).Str("wm", mention.String()).Msg("OK: webmention sent.")
|
log.Info().Str("endpoint", endpoint).Stringer("wm", mention).Msg("OK: webmention sent.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMentionAsPingback(snder *Sender, mention mf.Mention, endpoint string) {
|
func sendMentionAsPingback(snder *Sender, mention mf.Mention, endpoint string) {
|
||||||
|
|
|
@ -37,7 +37,7 @@ var (
|
||||||
func (client *HttpClient) PostForm(url string, formData url.Values) error {
|
func (client *HttpClient) PostForm(url string, formData url.Values) error {
|
||||||
resp, err := jammingHttp.PostForm(url, formData)
|
resp, err := jammingHttp.PostForm(url, formData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("POST Form to %s: %v", url, err)
|
||||||
}
|
}
|
||||||
if !isStatusOk(resp) {
|
if !isStatusOk(resp) {
|
||||||
return fmt.Errorf("POST Form to %s: Status code is not OK (%d)", url, resp.StatusCode)
|
return fmt.Errorf("POST Form to %s: Status code is not OK (%d)", url, resp.StatusCode)
|
||||||
|
@ -48,7 +48,7 @@ func (client *HttpClient) PostForm(url string, formData url.Values) error {
|
||||||
func (client *HttpClient) Post(url string, contenType string, body string) error {
|
func (client *HttpClient) Post(url string, contenType string, body string) error {
|
||||||
resp, err := jammingHttp.Post(url, contenType, strings.NewReader(body))
|
resp, err := jammingHttp.Post(url, contenType, strings.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("POST to %s: %v", url, err)
|
||||||
}
|
}
|
||||||
if !isStatusOk(resp) {
|
if !isStatusOk(resp) {
|
||||||
return fmt.Errorf("POST to %s: Status code is not OK (%d)", url, resp.StatusCode)
|
return fmt.Errorf("POST to %s: Status code is not OK (%d)", url, resp.StatusCode)
|
||||||
|
@ -60,28 +60,19 @@ func (client *HttpClient) Post(url string, contenType string, body string) error
|
||||||
func (client *HttpClient) GetBody(url string) (http.Header, string, error) {
|
func (client *HttpClient) GetBody(url string) (http.Header, string, error) {
|
||||||
resp, geterr := client.Get(url)
|
resp, geterr := client.Get(url)
|
||||||
if geterr != nil {
|
if geterr != nil {
|
||||||
return nil, "", geterr
|
return nil, "", fmt.Errorf("GET from %s: %v", url, geterr)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ReadBodyFromResponse(resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.Header, body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadBodyFromResponse(resp *http.Response) (string, error) {
|
|
||||||
if !isStatusOk(resp) {
|
if !isStatusOk(resp) {
|
||||||
return "", fmt.Errorf("Status code is not OK (%d)", resp.StatusCode)
|
return nil, "", fmt.Errorf("GET from %s: Status code is not OK (%d)", url, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, readerr := ioutil.ReadAll(resp.Body)
|
body, readerr := ioutil.ReadAll(resp.Body)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if readerr != nil {
|
if readerr != nil {
|
||||||
return "", readerr
|
return nil, "", fmt.Errorf("GET from %s: unable to read body: %v", url, readerr)
|
||||||
}
|
}
|
||||||
return string(body), nil
|
return resp.Header, string(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isStatusOk(resp *http.Response) bool {
|
func isStatusOk(resp *http.Response) bool {
|
||||||
|
|
Loading…
Reference in New Issue