Compare commits
1 commit
develop
...
add-previe
Author | SHA1 | Date | |
---|---|---|---|
|
faf83a5849 |
10 changed files with 110 additions and 279 deletions
33
account.go
33
account.go
|
@ -13,7 +13,7 @@ package writefreely
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/writefreely/writefreely/mailer"
|
"github.com/mailgun/mailgun-go"
|
||||||
"github.com/writefreely/writefreely/spam"
|
"github.com/writefreely/writefreely/spam"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -1378,19 +1378,13 @@ func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) e
|
||||||
|
|
||||||
func emailPasswordReset(app *App, toEmail, token string) error {
|
func emailPasswordReset(app *App, toEmail, token string) error {
|
||||||
// Send email
|
// Send email
|
||||||
mlr, err := mailer.New(app.cfg.Email)
|
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email."
|
footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email."
|
||||||
|
|
||||||
plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)
|
plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)
|
||||||
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
|
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.AddTag("Password Reset")
|
m.AddTag("Password Reset")
|
||||||
m.SetHTML(fmt.Sprintf(`<html>
|
m.SetHtml(fmt.Sprintf(`<html>
|
||||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||||
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
||||||
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
||||||
|
@ -1400,7 +1394,8 @@ func emailPasswordReset(app *App, toEmail, token string) error {
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara))
|
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara))
|
||||||
return mlr.Send(m)
|
_, _, err := gun.Send(m)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func loginViaEmail(app *App, alias, redirectTo string) error {
|
func loginViaEmail(app *App, alias, redirectTo string) error {
|
||||||
|
@ -1429,21 +1424,15 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
mlr, err := mailer.New(app.cfg.Email)
|
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
toEmail := u.EmailClear(app.keys)
|
toEmail := u.EmailClear(app.keys)
|
||||||
footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email."
|
footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email."
|
||||||
|
|
||||||
plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara)
|
plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara)
|
||||||
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
|
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.AddTag("Email Login")
|
m.AddTag("Email Login")
|
||||||
|
|
||||||
m.SetHTML(fmt.Sprintf(`<html>
|
m.SetHtml(fmt.Sprintf(`<html>
|
||||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||||
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
|
||||||
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
|
||||||
|
@ -1452,7 +1441,9 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara))
|
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara))
|
||||||
return mlr.Send(m)
|
_, _, err = gun.Send(m)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error {
|
func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error {
|
||||||
|
|
|
@ -436,17 +436,6 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
|
||||||
a.AppendObject(f.Raw())
|
a.AppendObject(f.Raw())
|
||||||
_, to = f.GetActor(0)
|
_, to = f.GetActor(0)
|
||||||
obj := f.Raw().GetObjectIRI(0)
|
obj := f.Raw().GetObjectIRI(0)
|
||||||
if obj == nil {
|
|
||||||
if debugging {
|
|
||||||
log.Error("GetObjectIRI on Follow for actor is empty; trying object")
|
|
||||||
}
|
|
||||||
ao := f.Raw().GetObject(0)
|
|
||||||
if ao == nil {
|
|
||||||
log.Error("Fell back to GetObject and none parsed, so no actor ID! Follow request probably FAILED!")
|
|
||||||
} else {
|
|
||||||
obj = ao.GetId()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.AppendActor(obj)
|
a.AppendActor(obj)
|
||||||
|
|
||||||
// First get actor information
|
// First get actor information
|
||||||
|
|
14
app.go
14
app.go
|
@ -429,11 +429,15 @@ func Initialize(apper Apper, debug bool) (*App, error) {
|
||||||
|
|
||||||
initActivityPub(apper.App())
|
initActivityPub(apper.App())
|
||||||
|
|
||||||
if apper.App().cfg.Email.Enabled() {
|
if apper.App().cfg.Email.Domain != "" || apper.App().cfg.Email.MailgunPrivate != "" {
|
||||||
log.Info("Starting publish jobs queue...")
|
if apper.App().cfg.Email.Domain == "" {
|
||||||
go startPublishJobsQueue(apper.App())
|
log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.")
|
||||||
} else {
|
} else if apper.App().cfg.Email.MailgunPrivate == "" {
|
||||||
log.Error("[FAILED] Starting publish jobs queue: no email provider is configured.")
|
log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.")
|
||||||
|
} else {
|
||||||
|
log.Info("Starting publish jobs queue...")
|
||||||
|
go startPublishJobsQueue(apper.App())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle local timeline, if enabled
|
// Handle local timeline, if enabled
|
||||||
|
|
|
@ -884,6 +884,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
|
||||||
// Serve ActivityStreams data now, if requested
|
// Serve ActivityStreams data now, if requested
|
||||||
if IsActivityPubRequest(r) {
|
if IsActivityPubRequest(r) {
|
||||||
ac := c.PersonObject()
|
ac := c.PersonObject()
|
||||||
|
ac.Context = []interface{}{activitystreams.Namespace}
|
||||||
setCacheControl(w, apCacheTime)
|
setCacheControl(w, apCacheTime)
|
||||||
return impart.RenderActivityJSON(w, ac, http.StatusOK)
|
return impart.RenderActivityJSON(w, ac, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,17 +171,8 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
EmailCfg struct {
|
EmailCfg struct {
|
||||||
// SMTP configuration values
|
|
||||||
Host string `ini:"smtp_host"`
|
|
||||||
Port int `ini:"smtp_port"`
|
|
||||||
Username string `ini:"smtp_username"`
|
|
||||||
Password string `ini:"smtp_password"`
|
|
||||||
EnableStartTLS bool `ini:"smtp_enable_start_tls"`
|
|
||||||
|
|
||||||
// Mailgun configuration values
|
|
||||||
Domain string `ini:"domain"`
|
Domain string `ini:"domain"`
|
||||||
MailgunPrivate string `ini:"mailgun_private"`
|
MailgunPrivate string `ini:"mailgun_private"`
|
||||||
MailgunEurope bool `ini:"mailgun_europe"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config holds the complete configuration for running a writefreely instance
|
// Config holds the complete configuration for running a writefreely instance
|
||||||
|
@ -251,8 +242,7 @@ func (ac *AppCfg) LandingPath() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc EmailCfg) Enabled() bool {
|
func (lc EmailCfg) Enabled() bool {
|
||||||
return (lc.Domain != "" && lc.MailgunPrivate != "") ||
|
return lc.Domain != "" && lc.MailgunPrivate != ""
|
||||||
lc.Username != "" && lc.Password != "" && lc.Host != "" && lc.Port > 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac AppCfg) SignupPath() string {
|
func (ac AppCfg) SignupPath() string {
|
||||||
|
|
37
email.go
37
email.go
|
@ -14,7 +14,6 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/writefreely/writefreely/mailer"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -22,6 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/aymerick/douceur/inliner"
|
"github.com/aymerick/douceur/inliner"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mailgun/mailgun-go"
|
||||||
stripmd "github.com/writeas/go-strip-markdown/v2"
|
stripmd "github.com/writeas/go-strip-markdown/v2"
|
||||||
"github.com/writeas/impart"
|
"github.com/writeas/impart"
|
||||||
"github.com/writeas/web-core/data"
|
"github.com/writeas/web-core/data"
|
||||||
|
@ -307,14 +307,8 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca
|
||||||
|
|
||||||
Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%`
|
Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%`
|
||||||
|
|
||||||
mlr, err := mailer.New(app.cfg.Email)
|
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||||
if err != nil {
|
m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
|
||||||
return err
|
|
||||||
}
|
|
||||||
m, err := mlr.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
|
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
|
||||||
if replyTo != "" {
|
if replyTo != "" {
|
||||||
m.SetReplyTo(replyTo)
|
m.SetReplyTo(replyTo)
|
||||||
|
@ -411,13 +405,13 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.SetHTML(html)
|
m.SetHtml(html)
|
||||||
|
|
||||||
log.Info("[email] Adding %d recipient(s)", len(subs))
|
log.Info("[email] Adding %d recipient(s)", len(subs))
|
||||||
for _, s := range subs {
|
for _, s := range subs {
|
||||||
e := s.FinalEmail(app.keys)
|
e := s.FinalEmail(app.keys)
|
||||||
log.Info("[email] Adding %s", e)
|
log.Info("[email] Adding %s", e)
|
||||||
err = m.AddRecipientAndVariables(e, map[string]string{
|
err = m.AddRecipientAndVariables(e, map[string]interface{}{
|
||||||
"id": s.ID,
|
"id": s.ID,
|
||||||
"to": e,
|
"to": e,
|
||||||
"token": s.Token,
|
"token": s.Token,
|
||||||
|
@ -427,8 +421,8 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mlr.Send(m)
|
res, _, err := gun.Send(m)
|
||||||
log.Info("[email] Email sent")
|
log.Info("[email] Send result: %s", res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to send post email: %v", err)
|
log.Error("Unable to send post email: %v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -443,23 +437,17 @@ func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) er
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send email
|
// Send email
|
||||||
mlr, err := mailer.New(app.cfg.Email)
|
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser):
|
plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser):
|
||||||
|
|
||||||
` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + `
|
` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + `
|
||||||
|
|
||||||
If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.`
|
If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.`
|
||||||
m, err := mlr.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
|
m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.AddTag("Email Verification")
|
m.AddTag("Email Verification")
|
||||||
|
|
||||||
m.SetHTML(`<html>
|
m.SetHtml(`<html>
|
||||||
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
|
||||||
<div style="font-size: 1.2em;">
|
<div style="font-size: 1.2em;">
|
||||||
<p>Confirm your subscription to <a href="` + c.CanonicalURL() + `">` + c.DisplayTitle() + `</a> to start receiving future posts:</p>
|
<p>Confirm your subscription to <a href="` + c.CanonicalURL() + `">` + c.DisplayTitle() + `</a> to start receiving future posts:</p>
|
||||||
|
@ -468,10 +456,7 @@ If you didn't subscribe to this site or you're not sure why you're getting this
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`)
|
</html>`)
|
||||||
err = mlr.Send(m)
|
gun.Send(m)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -46,15 +46,13 @@ require (
|
||||||
github.com/writeas/import v0.2.1
|
github.com/writeas/import v0.2.1
|
||||||
github.com/writeas/monday v1.3.0
|
github.com/writeas/monday v1.3.0
|
||||||
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320
|
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320
|
||||||
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431
|
github.com/writeas/web-core v1.7.0
|
||||||
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b
|
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b
|
||||||
github.com/writefreely/go-nodeinfo v1.2.0
|
github.com/writefreely/go-nodeinfo v1.2.0
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.35.0
|
||||||
golang.org/x/net v0.30.0
|
golang.org/x/net v0.30.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/xhit/go-simple-mail/v2 v2.16.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.as/core/socks v1.0.0 // indirect
|
code.as/core/socks v1.0.0 // indirect
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
@ -84,13 +82,12 @@ require (
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sasha-s/go-deadlock v0.3.1 // indirect
|
github.com/sasha-s/go-deadlock v0.3.1 // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
|
||||||
github.com/writeas/go-writeas/v2 v2.0.2 // indirect
|
github.com/writeas/go-writeas/v2 v2.0.2 // indirect
|
||||||
github.com/writeas/openssl-go v1.0.0 // indirect
|
github.com/writeas/openssl-go v1.0.0 // indirect
|
||||||
github.com/writeas/slug v1.2.0 // indirect
|
github.com/writeas/slug v1.2.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
55
go.sum
55
go.sum
|
@ -73,6 +73,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||||
|
@ -178,8 +179,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
|
|
||||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
|
||||||
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
||||||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||||
github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY=
|
github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY=
|
||||||
|
@ -209,14 +208,12 @@ github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt
|
||||||
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
|
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
|
||||||
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g=
|
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g=
|
||||||
github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ=
|
github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ=
|
||||||
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431 h1:ruqL2u87k504PXkR/fC4DcfZyyHmCindlpjOQKmyOsY=
|
github.com/writeas/web-core v1.7.0 h1:79bpoTXOLTHhCYaPyl7euNNVNZ/HBLkDxv98s/XRZhM=
|
||||||
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431/go.mod h1:7+idL4Y4woF7MnUfNX2mvkaQ8nLIJXths2y5iYPtA3k=
|
github.com/writeas/web-core v1.7.0/go.mod h1:doPbvwwYCyrLoyrIMH5m+14uCfG3SHMrMcGsj8NIlkM=
|
||||||
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0=
|
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0=
|
||||||
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
|
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
|
||||||
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
|
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
|
||||||
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
|
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
|
||||||
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
|
||||||
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
@ -226,12 +223,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
@ -242,11 +244,14 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -254,6 +259,10 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -271,33 +280,45 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
181
mailer/mailer.go
181
mailer/mailer.go
|
@ -1,181 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright © 2024 Musing Studio LLC.
|
|
||||||
*
|
|
||||||
* This file is part of WriteFreely.
|
|
||||||
*
|
|
||||||
* WriteFreely is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License, included
|
|
||||||
* in the LICENSE file in this source code package.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package mailer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/mailgun/mailgun-go"
|
|
||||||
"github.com/writeas/web-core/log"
|
|
||||||
"github.com/writefreely/writefreely/config"
|
|
||||||
mail "github.com/xhit/go-simple-mail/v2"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Mailer holds configurations for the preferred mailing provider.
|
|
||||||
Mailer struct {
|
|
||||||
smtp *mail.SMTPServer
|
|
||||||
mailGun *mailgun.MailgunImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message holds the email contents and metadata for the preferred mailing provider.
|
|
||||||
Message struct {
|
|
||||||
mgMsg *mailgun.Message
|
|
||||||
smtpMsg *SmtpMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
SmtpMessage struct {
|
|
||||||
from string
|
|
||||||
replyTo string
|
|
||||||
subject string
|
|
||||||
recipients []Recipient
|
|
||||||
html string
|
|
||||||
text string
|
|
||||||
}
|
|
||||||
|
|
||||||
Recipient struct {
|
|
||||||
email string
|
|
||||||
vars map[string]string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates a new Mailer from the instance's config.EmailCfg, returning an error if not properly configured.
|
|
||||||
func New(eCfg config.EmailCfg) (*Mailer, error) {
|
|
||||||
m := &Mailer{}
|
|
||||||
if eCfg.Domain != "" && eCfg.MailgunPrivate != "" {
|
|
||||||
m.mailGun = mailgun.NewMailgun(eCfg.Domain, eCfg.MailgunPrivate)
|
|
||||||
if eCfg.MailgunEurope {
|
|
||||||
m.mailGun.SetAPIBase("https://api.eu.mailgun.net/v3")
|
|
||||||
}
|
|
||||||
} else if eCfg.Username != "" && eCfg.Password != "" && eCfg.Host != "" && eCfg.Port > 0 {
|
|
||||||
m.smtp = mail.NewSMTPClient()
|
|
||||||
m.smtp.Host = eCfg.Host
|
|
||||||
m.smtp.Port = eCfg.Port
|
|
||||||
m.smtp.Username = eCfg.Username
|
|
||||||
m.smtp.Password = eCfg.Password
|
|
||||||
if eCfg.EnableStartTLS {
|
|
||||||
m.smtp.Encryption = mail.EncryptionSTARTTLS
|
|
||||||
}
|
|
||||||
// To allow sending multiple email
|
|
||||||
m.smtp.KeepAlive = true
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("no email provider is configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessage creates a new Message from the given parameters.
|
|
||||||
func (m *Mailer) NewMessage(from, subject, text string, to ...string) (*Message, error) {
|
|
||||||
msg := &Message{}
|
|
||||||
if m.mailGun != nil {
|
|
||||||
msg.mgMsg = m.mailGun.NewMessage(from, subject, text, to...)
|
|
||||||
} else if m.smtp != nil {
|
|
||||||
msg.smtpMsg = &SmtpMessage{
|
|
||||||
from: from,
|
|
||||||
replyTo: "",
|
|
||||||
subject: subject,
|
|
||||||
recipients: make([]Recipient, len(to)),
|
|
||||||
html: "",
|
|
||||||
text: text,
|
|
||||||
}
|
|
||||||
for _, r := range to {
|
|
||||||
msg.smtpMsg.recipients = append(msg.smtpMsg.recipients, Recipient{r, make(map[string]string)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTML sets the body of the message.
|
|
||||||
func (m *Message) SetHTML(html string) {
|
|
||||||
if m.smtpMsg != nil {
|
|
||||||
m.smtpMsg.html = html
|
|
||||||
} else if m.mgMsg != nil {
|
|
||||||
m.mgMsg.SetHtml(html)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) SetReplyTo(replyTo string) {
|
|
||||||
if m.smtpMsg != nil {
|
|
||||||
m.smtpMsg.replyTo = replyTo
|
|
||||||
} else {
|
|
||||||
m.mgMsg.SetReplyTo(replyTo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTag attaches a tag to the Message for providers that support it.
|
|
||||||
func (m *Message) AddTag(tag string) {
|
|
||||||
if m.mgMsg != nil {
|
|
||||||
m.mgMsg.AddTag(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) AddRecipientAndVariables(r string, vars map[string]string) error {
|
|
||||||
if m.smtpMsg != nil {
|
|
||||||
m.smtpMsg.recipients = append(m.smtpMsg.recipients, Recipient{r, vars})
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
varsInterfaces := make(map[string]interface{}, len(vars))
|
|
||||||
for k, v := range vars {
|
|
||||||
varsInterfaces[k] = v
|
|
||||||
}
|
|
||||||
return m.mgMsg.AddRecipientAndVariables(r, varsInterfaces)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send sends the given message via the preferred provider.
|
|
||||||
func (m *Mailer) Send(msg *Message) error {
|
|
||||||
if m.smtp != nil {
|
|
||||||
client, err := m.smtp.Connect()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
emailSent := false
|
|
||||||
for _, r := range msg.smtpMsg.recipients {
|
|
||||||
customMsg := mail.NewMSG()
|
|
||||||
customMsg.SetFrom(msg.smtpMsg.from)
|
|
||||||
if msg.smtpMsg.replyTo != "" {
|
|
||||||
customMsg.SetReplyTo(msg.smtpMsg.replyTo)
|
|
||||||
}
|
|
||||||
customMsg.SetSubject(msg.smtpMsg.subject)
|
|
||||||
customMsg.AddTo(r.email)
|
|
||||||
cText := msg.smtpMsg.text
|
|
||||||
cHtml := msg.smtpMsg.html
|
|
||||||
for v, value := range r.vars {
|
|
||||||
placeHolder := fmt.Sprintf("%%recipient.%s%%", v)
|
|
||||||
cText = strings.ReplaceAll(cText, placeHolder, value)
|
|
||||||
cHtml = strings.ReplaceAll(cHtml, placeHolder, value)
|
|
||||||
}
|
|
||||||
customMsg.SetBody(mail.TextHTML, cHtml)
|
|
||||||
customMsg.AddAlternative(mail.TextPlain, cText)
|
|
||||||
e := customMsg.Error
|
|
||||||
if e == nil {
|
|
||||||
e = customMsg.Send(client)
|
|
||||||
}
|
|
||||||
if e == nil {
|
|
||||||
emailSent = true
|
|
||||||
} else {
|
|
||||||
log.Error("Unable to send email to %s: %v", r.email, e)
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !emailSent {
|
|
||||||
// only send an error if no email could be sent (to avoid retry of successfully sent emails)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if m.mailGun != nil {
|
|
||||||
_, _, err := m.mailGun.Send(msg.mgMsg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
34
posts.go
34
posts.go
|
@ -56,6 +56,7 @@ type PostType string
|
||||||
const (
|
const (
|
||||||
postArch PostType = "archive"
|
postArch PostType = "archive"
|
||||||
|
|
||||||
|
shortCodeMore = "<!--more-->"
|
||||||
shortCodePaid = "<!--paid-->"
|
shortCodePaid = "<!--paid-->"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1289,6 +1290,39 @@ func (p *PublicPost) ActivityObject(app *App) *activitystreams.Object {
|
||||||
o.CC = append(o.CC, iri)
|
o.CC = append(o.CC, iri)
|
||||||
o.Tag = append(o.Tag, activitystreams.Tag{Type: "Mention", HRef: iri, Name: handle})
|
o.Tag = append(o.Tag, activitystreams.Tag{Type: "Mention", HRef: iri, Name: handle})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add shortened Note as the `preview` property if this is an Article
|
||||||
|
if o.Type == "Article" {
|
||||||
|
o.Preview = p.PreviewObject(app, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreviewObject returns an activitystreams.Object that can be used as an Article's `preview` property.
|
||||||
|
func (p *PublicPost) PreviewObject(app *App, art *activitystreams.Object) *activitystreams.Object {
|
||||||
|
o := activitystreams.NewNoteObject()
|
||||||
|
o.To = nil
|
||||||
|
o.ID = art.ID
|
||||||
|
o.URL = art.URL
|
||||||
|
o.Published = art.Published
|
||||||
|
o.Updated = art.Updated
|
||||||
|
o.Tag = art.Tag
|
||||||
|
o.Attachment = art.Attachment
|
||||||
|
|
||||||
|
baseURL := p.Collection.CanonicalURL()
|
||||||
|
// Try to truncate at user-defined excerpt, if exists
|
||||||
|
exc := strings.Index(p.Content, shortCodeMore)
|
||||||
|
if exc == -1 {
|
||||||
|
// No excerpt; fall back to truncating at first paragraph
|
||||||
|
exc = strings.Index(p.Content, "\n\n")
|
||||||
|
}
|
||||||
|
if exc > -1 {
|
||||||
|
p.HTMLExcerpt = template.HTML(applyMarkdown([]byte(p.Content[:exc]), baseURL, app.cfg))
|
||||||
|
} else {
|
||||||
|
p.HTMLExcerpt = p.HTMLContent
|
||||||
|
}
|
||||||
|
o.Content = strings.TrimRight(string(p.Excerpt()), "\n")
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue