Simplify activitypub followers listing, parse and save activitypub usernames in @user@example.org format

This commit is contained in:
Jan-Lukas Else 2022-12-07 16:43:13 +01:00
parent 4407f0aae1
commit fc8e8e9a9d
9 changed files with 76 additions and 84 deletions

View File

@ -247,7 +247,7 @@ func (a *goBlog) apOnCreateUpdate(blog *configBlog, requestActor *ap.Actor, acti
// It's a reply
original := object.GetID().String()
name := requestActor.Name.First().Value.String()
if username := requestActor.PreferredUsername.First().String(); name == "" && username != "" {
if username := apUsername(requestActor); name == "" && username != "" {
name = username
}
website := requestActor.GetLink().String()
@ -388,27 +388,30 @@ func (db *database) apGetAllInboxes(blog string) (inboxes []string, err error) {
}
type apFollower struct {
follower, inbox string
follower, inbox, username string
}
func (db *database) apGetAllFollowers(blog string) (followers []*apFollower, err error) {
rows, err := db.Query("select follower, inbox from activitypub_followers where blog = @blog", sql.Named("blog", blog))
rows, err := db.Query("select follower, inbox, username from activitypub_followers where blog = @blog", sql.Named("blog", blog))
if err != nil {
return nil, err
}
var follower, inbox string
var follower, inbox, username string
for rows.Next() {
err = rows.Scan(&follower, &inbox)
err = rows.Scan(&follower, &inbox, &username)
if err != nil {
return nil, err
}
followers = append(followers, &apFollower{follower: follower, inbox: inbox})
followers = append(followers, &apFollower{follower: follower, inbox: inbox, username: username})
}
return followers, nil
}
func (db *database) apAddFollower(blog, follower, inbox string) error {
_, err := db.Exec("insert or replace into activitypub_followers (blog, follower, inbox) values (@blog, @follower, @inbox)", sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox))
func (db *database) apAddFollower(blog, follower, inbox, username string) error {
_, err := db.Exec(
"insert or replace into activitypub_followers (blog, follower, inbox, username) values (@blog, @follower, @inbox, @username)",
sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox), sql.Named("username", username),
)
return err
}
@ -463,7 +466,7 @@ func (a *goBlog) apUndelete(p *post) {
func (a *goBlog) apAccept(blogName, blogIri string, blog *configBlog, follow *ap.Activity) {
newFollower := follow.Actor.GetID()
log.Println("New follow request:", newFollower.String())
log.Println("New follow request from follower id:", newFollower.String())
// Get remote actor
follower, status, err := a.apGetRemoteActor(newFollower.String(), blogIri)
if err != nil || status != 0 {
@ -479,7 +482,8 @@ func (a *goBlog) apAccept(blogName, blogIri string, blog *configBlog, follow *ap
if inbox == "" {
return
}
if err = a.db.apAddFollower(blogName, follower.GetID().String(), inbox.String()); err != nil {
username := apUsername(follower)
if err = a.db.apAddFollower(blogName, follower.GetID().String(), inbox.String(), username); err != nil {
return
}
// Send accept response to the new follower
@ -488,7 +492,7 @@ func (a *goBlog) apAccept(blogName, blogIri string, blog *configBlog, follow *ap
accept.Actor = a.apAPIri(blog)
_ = a.apQueueSendSigned(a.apIri(blog), inbox.String(), accept)
// Notification
a.sendNotification(fmt.Sprintf("%s started following %s", newFollower.String(), a.apIri(blog)))
a.sendNotification(fmt.Sprintf("%s (%s) started following %s", username, follower.GetID().String(), a.apIri(blog)))
}
func (a *goBlog) apSendProfileUpdates() {

View File

@ -6,6 +6,7 @@ import (
"encoding/pem"
"fmt"
"net/http"
"net/url"
"github.com/araddon/dateparse"
ct "github.com/elnormous/contenttype"
@ -151,3 +152,12 @@ func (a *goBlog) serveAPItem(item any, w http.ResponseWriter, r *http.Request) {
w.Header().Set(contentType, contenttype.ASUTF8)
_ = a.min.Get().Minify(contenttype.AS, w, bytes.NewReader(binary))
}
func apUsername(person *ap.Person) string {
preferredUsername := person.PreferredUsername.First().Value.String()
u, err := url.Parse(person.GetLink().String())
if err != nil || u == nil || u.Host == "" || preferredUsername == "" {
return person.GetLink().String()
}
return fmt.Sprintf("@%s@%s", preferredUsername, u.Host)
}

32
activityStreams_test.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"testing"
ap "github.com/go-ap/activitypub"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_apUsername(t *testing.T) {
item, err := ap.UnmarshalJSON([]byte(`
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://example.org/users/user",
"type": "Person",
"preferredUsername": "user",
"name": "Example user",
"url": "https://example.org/@user"
}
`))
require.NoError(t, err)
actor, err := ap.ToActor(item)
require.NoError(t, err)
username := apUsername(actor)
assert.Equal(t, "@user@example.org", username)
}

2
dbmigrations/00033.sql Normal file
View File

@ -0,0 +1,2 @@
alter table activitypub_followers add username text not null default "";
update activitypub_followers set username = follower;

8
go.mod
View File

@ -21,9 +21,9 @@ require (
github.com/elnormous/contenttype v1.0.3
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead
github.com/emersion/go-smtp v0.15.0
github.com/go-ap/activitypub v0.0.0-20221206062958-cae46e718d79
github.com/go-ap/activitypub v0.0.0-20221207073405-5d6d22cbc42e
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.0.7
github.com/go-chi/chi/v5 v5.0.8
github.com/go-fed/httpsig v1.1.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/google/uuid v1.3.0
@ -61,8 +61,8 @@ require (
github.com/yuin/goldmark v1.5.3
// master
github.com/yuin/goldmark-emoji v1.0.2-0.20210607094911-0487583eca38
golang.org/x/crypto v0.3.0
golang.org/x/net v0.3.0
golang.org/x/crypto v0.4.0
golang.org/x/net v0.4.0
golang.org/x/sync v0.1.0
golang.org/x/text v0.5.0
gopkg.in/yaml.v3 v3.0.1

16
go.sum
View File

@ -124,14 +124,14 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-ap/activitypub v0.0.0-20221206062958-cae46e718d79 h1:iZ2ITdeyrEpQL0nOIWpdje6qtk3cBBuylB5fI7TI3Rc=
github.com/go-ap/activitypub v0.0.0-20221206062958-cae46e718d79/go.mod h1:1oVD0h0aPT3OEE1ZoSUoym/UGKzxe+e0y8K2AkQ1Hqs=
github.com/go-ap/activitypub v0.0.0-20221207073405-5d6d22cbc42e h1:Qb0THMsDaDvRqvAwUFzJmN6WRbweiHUOoy9nY6GCw2M=
github.com/go-ap/activitypub v0.0.0-20221207073405-5d6d22cbc42e/go.mod h1:1oVD0h0aPT3OEE1ZoSUoym/UGKzxe+e0y8K2AkQ1Hqs=
github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea h1:ywGtLGVjJjMrq4mu35Qmu+NtlhlTk/gTayE6Bb4tQZk=
github.com/go-ap/errors v0.0.0-20221205040414-01c1adfc98ea/go.mod h1:SaTNjEEkp0q+w3pUS1ccyEL/lUrHteORlDq/e21mCc8=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -418,8 +418,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
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=
@ -494,8 +494,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

25
ui.go
View File

@ -1627,30 +1627,15 @@ func (a *goBlog) renderActivityPubFollowers(hb *htmlbuilder.HtmlBuilder, rd *ren
hb.WriteElementClose("h1")
// List followers
hb.WriteElementOpen("table")
hb.WriteElementOpen("thead")
hb.WriteElementOpen("th", "class", "tal")
hb.WriteEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "apfollower"))
hb.WriteElementClose("th")
hb.WriteElementOpen("th", "class", "tar")
hb.WriteEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "apinbox"))
hb.WriteElementClose("th")
hb.WriteElementClose("thead")
hb.WriteElementOpen("tbody")
hb.WriteElementOpen("ul")
for _, follower := range aprd.followers {
hb.WriteElementOpen("tr")
hb.WriteElementOpen("td", "class", "tal")
hb.WriteElementOpen("li")
hb.WriteElementOpen("a", "href", follower.follower, "target", "_blank")
hb.WriteEscaped(follower.follower)
hb.WriteEscaped(follower.username)
hb.WriteElementClose("a")
hb.WriteElementClose("td")
hb.WriteElementOpen("td", "class", "tar")
hb.WriteEscaped(follower.inbox)
hb.WriteElementClose("td")
hb.WriteElementClose("tr")
hb.WriteElementClose("li")
}
hb.WriteElementClose("tbody")
hb.WriteElementClose("table")
hb.WriteElementClose("ul")
hb.WriteElementClose("main")
},

View File

@ -6,7 +6,6 @@ import (
"fmt"
"io"
"math/rand"
"mime"
"net/http"
"net/http/httptest"
"net/url"
@ -404,21 +403,3 @@ func stringToInt(s string) int {
func loStringNotEmpty(s string, _ int) bool {
return s != ""
}
func mimeTypeFromUrl(urlString string) string {
parsedUrl, err := url.Parse(urlString)
if err != nil {
return ""
}
ext := path.Ext(parsedUrl.Path)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
switch ext {
case ".jpg":
mimeType = "image/jpeg"
default:
mimeType = "image/" + strings.TrimPrefix(ext, ".")
}
}
return mimeType
}

View File

@ -1,7 +1,6 @@
package main
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
@ -168,24 +167,3 @@ func Test_groupStrings(t *testing.T) {
assert.Equal(t, "H", groups[3].Identifier)
assert.Equal(t, "🚴", groups[4].Identifier)
}
func Test_mimeTypeFromUrl(t *testing.T) {
type test struct {
url string
want string
}
tests := []*test{
{url: "https://example.com/profile.jpg", want: "image/jpeg"},
{url: "https://example.com/profile.jpeg", want: "image/jpeg"},
{url: "https://example.com/profile.png", want: "image/png"},
{url: "https://example.com/profile.png?v=3", want: "image/png"},
{url: "/profile.png?v=3", want: "image/png"},
}
for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
if got := mimeTypeFromUrl(tt.url); got != tt.want {
t.Errorf("mimeTypeFromUrl() = %v, want %v", got, tt.want)
}
})
}
}