go-jamming/app/webmention/send/send.go

133 lines
3.8 KiB
Go

package send
import (
"brainbaking.com/go-jamming/app/mf"
"brainbaking.com/go-jamming/app/pingback/send"
"brainbaking.com/go-jamming/common"
"brainbaking.com/go-jamming/db"
"brainbaking.com/go-jamming/rest"
"fmt"
"github.com/rs/zerolog/log"
"strings"
"sync"
)
type Sender struct {
RestClient rest.Client
Conf *common.Config
Repo db.MentionRepo
}
// SendSingle sends out webmentions serially for a single source.
// It does validate the relative path against the domain, which is supposed to be served using https.
func (snder *Sender) SendSingle(domain string, relSource string) {
source := fmt.Sprintf("https://%s/%s", domain, relSource)
log.Info().Str("url", source).Msg(` OK: someone wants to send a single mention`)
_, html, err := snder.RestClient.GetBody(source)
if err != nil {
log.Err(err).Str("url", source).Msg("Unable to validate source, send aborted")
return
}
for _, href := range snder.collectUniqueHrefsFromHtml(html) {
if strings.HasPrefix(href, "http") {
snder.sendMention(mf.Mention{
Source: source,
Target: href,
})
}
}
}
// Send sends out multiple webmentions based on since and what's posted in the RSS feed.
// It first GETs domain/index.xml and goes from there.
func (snder *Sender) Send(domain string) {
lastSent := snder.Repo.LastSentMention(domain)
log.Info().Str("domain", domain).Str("lastsent", lastSent).Msg(` OK: someone wants to send mentions`)
feedUrl, err := snder.discoverRssFeed(domain)
if err != nil {
log.Err(err).Str("url", feedUrl).Msg("Unable to retrieve RSS feed, send aborted")
return
}
_, feed, err := snder.RestClient.GetBody(feedUrl)
// just to be sure. Should not produce an error due to check above, but you never know.
if err != nil {
log.Err(err).Str("url", feedUrl).Msg("Unable to retrieve RSS feed, send aborted")
return
}
lastSent, err = snder.parseRssFeed(feed, lastSent)
if err != nil {
log.Err(err).Str("url", feedUrl).Msg("Unable to parse RSS feed, send aborted")
return
}
snder.Repo.UpdateLastSentMention(domain, lastSent)
log.Info().Str("feed", feedUrl).Str("lastsent", lastSent).Msg(` OK: send processed.`)
}
func (snder *Sender) parseRssFeed(feed string, lastSentLink string) (string, error) {
items, err := snder.Collect(feed, lastSentLink)
if err != nil {
return lastSentLink, err
}
if len(items) == 0 {
return lastSentLink, nil
}
var wg sync.WaitGroup
sema := make(chan struct{}, 20)
for _, item := range items {
for _, href := range item.hrefs {
if strings.HasPrefix(href, "http") {
mention := mf.Mention{
// SOURCE is own domain this time, TARGET = outbound
Source: item.link,
Target: href,
}
wg.Add(1)
go func() {
sema <- struct{}{}
defer func() { <-sema }()
defer wg.Done()
snder.sendMention(mention)
}()
}
}
}
wg.Wait()
// first item is the most recent one!
return items[0].link, nil
}
var mentionFuncs = map[string]func(snder *Sender, mention mf.Mention, endpoint string){
typeUnknown: func(snder *Sender, mention mf.Mention, endpoint string) {},
typeWebmention: sendMentionAsWebmention,
typePingback: sendMentionAsPingback,
}
func (snder *Sender) sendMention(mention mf.Mention) {
endpoint, mentionType := snder.discoverMentionEndpoint(mention.Target)
mentionFuncs[mentionType](snder, mention, endpoint)
}
func sendMentionAsWebmention(snder *Sender, mention mf.Mention, endpoint string) {
err := snder.RestClient.PostForm(endpoint, mention.AsFormValues())
if err != nil {
log.Err(err).Str("endpoint", endpoint).Stringer("wm", mention).Msg("Webmention send failed")
return
}
log.Info().Str("endpoint", endpoint).Stringer("wm", mention).Msg("OK: webmention sent.")
}
func sendMentionAsPingback(snder *Sender, mention mf.Mention, endpoint string) {
pingbackSender := &send.Sender{
RestClient: snder.RestClient,
}
pingbackSender.SendPingbackToEndpoint(endpoint, mention)
}