diff --git a/activityPub.go b/activityPub.go index 1251224..3d6107f 100644 --- a/activityPub.go +++ b/activityPub.go @@ -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() { diff --git a/activityStreams.go b/activityStreams.go index b7a79ee..968443e 100644 --- a/activityStreams.go +++ b/activityStreams.go @@ -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) +} diff --git a/activityStreams_test.go b/activityStreams_test.go new file mode 100644 index 0000000..3e60b8c --- /dev/null +++ b/activityStreams_test.go @@ -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) +} diff --git a/dbmigrations/00033.sql b/dbmigrations/00033.sql new file mode 100644 index 0000000..617c40a --- /dev/null +++ b/dbmigrations/00033.sql @@ -0,0 +1,2 @@ +alter table activitypub_followers add username text not null default ""; +update activitypub_followers set username = follower; \ No newline at end of file diff --git a/go.mod b/go.mod index caa3ed0..de5c83f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index ee81a3d..3923c8b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/ui.go b/ui.go index 0964ecd..26d5faa 100644 --- a/ui.go +++ b/ui.go @@ -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") }, diff --git a/utils.go b/utils.go index ae5f0a8..d0b20c5 100644 --- a/utils.go +++ b/utils.go @@ -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 -} diff --git a/utils_test.go b/utils_test.go index 7db36d3..3076978 100644 --- a/utils_test.go +++ b/utils_test.go @@ -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) - } - }) - } -}