porting all github actions from bluesky-social/indigo to tangled CI

refactor helpers in to separate files

+49
automod/helpers/account.go
··· 1 + package helpers 2 + 3 + import ( 4 + "time" 5 + 6 + "github.com/bluesky-social/indigo/automod" 7 + ) 8 + 9 + // no accounts exist before this time 10 + var atprotoAccountEpoch = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) 11 + 12 + // returns true if account creation timestamp is plausible: not-nil, not in distant past, not in the future 13 + func plausibleAccountCreation(when *time.Time) bool { 14 + if when == nil { 15 + return false 16 + } 17 + // this is mostly to check for misconfigurations or null values (eg, UNIX epoch zero means "unknown" not actually 1970) 18 + if !when.After(atprotoAccountEpoch) { 19 + return false 20 + } 21 + // a timestamp in the future would also indicate some misconfiguration 22 + if when.After(time.Now().Add(time.Hour)) { 23 + return false 24 + } 25 + return true 26 + } 27 + 28 + // checks if account was created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false' 29 + func AccountIsYoungerThan(c *automod.AccountContext, age time.Duration) bool { 30 + // TODO: consider swapping priority order here (and below) 31 + if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) { 32 + return time.Since(*c.Account.CreatedAt) < age 33 + } 34 + if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) { 35 + return time.Since(*c.Account.Private.IndexedAt) < age 36 + } 37 + return false 38 + } 39 + 40 + // checks if account was *not* created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false' 41 + func AccountIsOlderThan(c *automod.AccountContext, age time.Duration) bool { 42 + if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) { 43 + return time.Since(*c.Account.CreatedAt) >= age 44 + } 45 + if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) { 46 + return time.Since(*c.Account.Private.IndexedAt) >= age 47 + } 48 + return false 49 + }
+61
automod/helpers/account_test.go
··· 1 + package helpers 2 + 3 + import ( 4 + "testing" 5 + "time" 6 + 7 + "github.com/bluesky-social/indigo/atproto/identity" 8 + "github.com/bluesky-social/indigo/atproto/syntax" 9 + "github.com/bluesky-social/indigo/automod" 10 + "github.com/stretchr/testify/assert" 11 + ) 12 + 13 + func TestAccountIsYoungerThan(t *testing.T) { 14 + assert := assert.New(t) 15 + 16 + am := automod.AccountMeta{ 17 + Identity: &identity.Identity{ 18 + DID: syntax.DID("did:plc:abc111"), 19 + Handle: syntax.Handle("handle.example.com"), 20 + }, 21 + Profile: automod.ProfileSummary{}, 22 + Private: nil, 23 + } 24 + now := time.Now() 25 + ac := automod.AccountContext{ 26 + Account: am, 27 + } 28 + assert.False(AccountIsYoungerThan(&ac, time.Hour)) 29 + assert.False(AccountIsOlderThan(&ac, time.Hour)) 30 + 31 + ac.Account.CreatedAt = &now 32 + assert.True(AccountIsYoungerThan(&ac, time.Hour)) 33 + assert.False(AccountIsOlderThan(&ac, time.Hour)) 34 + 35 + yesterday := time.Now().Add(-1 * time.Hour * 24) 36 + ac.Account.CreatedAt = &yesterday 37 + assert.False(AccountIsYoungerThan(&ac, time.Hour)) 38 + assert.True(AccountIsOlderThan(&ac, time.Hour)) 39 + 40 + old := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC) 41 + ac.Account.CreatedAt = &old 42 + assert.False(AccountIsYoungerThan(&ac, time.Hour)) 43 + assert.False(AccountIsYoungerThan(&ac, time.Hour*24*365*100)) 44 + assert.False(AccountIsOlderThan(&ac, time.Hour)) 45 + assert.False(AccountIsOlderThan(&ac, time.Hour*24*365*100)) 46 + 47 + future := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) 48 + ac.Account.CreatedAt = &future 49 + assert.False(AccountIsYoungerThan(&ac, time.Hour)) 50 + assert.False(AccountIsOlderThan(&ac, time.Hour)) 51 + 52 + ac.Account.CreatedAt = nil 53 + ac.Account.Private = &automod.AccountPrivate{ 54 + Email: "account@example.com", 55 + IndexedAt: &yesterday, 56 + } 57 + assert.True(AccountIsYoungerThan(&ac, 48*time.Hour)) 58 + assert.False(AccountIsYoungerThan(&ac, time.Hour)) 59 + assert.True(AccountIsOlderThan(&ac, time.Hour)) 60 + assert.False(AccountIsOlderThan(&ac, 48*time.Hour)) 61 + }
+141
automod/helpers/bsky_test.go
··· 1 + package helpers 2 + 3 + import ( 4 + comatproto "github.com/bluesky-social/indigo/api/atproto" 5 + appbsky "github.com/bluesky-social/indigo/api/bsky" 6 + "testing" 7 + 8 + "github.com/stretchr/testify/assert" 9 + ) 10 + 11 + func TestParentOrRootIsDid(t *testing.T) { 12 + assert := assert.New(t) 13 + 14 + post1 := &appbsky.FeedPost{ 15 + Text: "some random post that i dreamt up last night, idk", 16 + Reply: &appbsky.FeedPost_ReplyRef{ 17 + Root: &comatproto.RepoStrongRef{ 18 + Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 19 + }, 20 + Parent: &comatproto.RepoStrongRef{ 21 + Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 22 + }, 23 + }, 24 + } 25 + 26 + post2 := &appbsky.FeedPost{ 27 + Text: "some random post that i dreamt up last night, idk", 28 + Reply: &appbsky.FeedPost_ReplyRef{ 29 + Root: &comatproto.RepoStrongRef{ 30 + Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 31 + }, 32 + Parent: &comatproto.RepoStrongRef{ 33 + Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 34 + }, 35 + }, 36 + } 37 + 38 + post3 := &appbsky.FeedPost{ 39 + Text: "some random post that i dreamt up last night, idk", 40 + Reply: &appbsky.FeedPost_ReplyRef{ 41 + Root: &comatproto.RepoStrongRef{ 42 + Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 43 + }, 44 + Parent: &comatproto.RepoStrongRef{ 45 + Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 46 + }, 47 + }, 48 + } 49 + 50 + post4 := &appbsky.FeedPost{ 51 + Text: "some random post that i dreamt up last night, idk", 52 + Reply: &appbsky.FeedPost_ReplyRef{ 53 + Root: &comatproto.RepoStrongRef{ 54 + Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 55 + }, 56 + Parent: &comatproto.RepoStrongRef{ 57 + Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 58 + }, 59 + }, 60 + } 61 + 62 + assert.True(PostParentOrRootIsDid(post1, "did:plc:abc123")) 63 + assert.False(PostParentOrRootIsDid(post1, "did:plc:321abc")) 64 + 65 + assert.True(PostParentOrRootIsDid(post2, "did:plc:abc123")) 66 + assert.True(PostParentOrRootIsDid(post2, "did:plc:321abc")) 67 + 68 + assert.True(PostParentOrRootIsDid(post3, "did:plc:abc123")) 69 + assert.True(PostParentOrRootIsDid(post3, "did:plc:321abc")) 70 + 71 + assert.False(PostParentOrRootIsDid(post4, "did:plc:abc123")) 72 + assert.True(PostParentOrRootIsDid(post4, "did:plc:321abc")) 73 + 74 + didList1 := []string{ 75 + "did:plc:cba321", 76 + "did:web:bsky.app", 77 + "did:plc:abc123", 78 + } 79 + 80 + didList2 := []string{ 81 + "did:plc:321cba", 82 + "did:web:bsky.app", 83 + "did:plc:123abc", 84 + } 85 + 86 + assert.True(PostParentOrRootIsAnyDid(post1, didList1)) 87 + assert.False(PostParentOrRootIsAnyDid(post1, didList2)) 88 + } 89 + 90 + func TestPostMentionsDid(t *testing.T) { 91 + assert := assert.New(t) 92 + 93 + post := &appbsky.FeedPost{ 94 + Text: "@hailey.at what is upppp also hello to @darthbluesky.bsky.social", 95 + Facets: []*appbsky.RichtextFacet{ 96 + { 97 + Features: []*appbsky.RichtextFacet_Features_Elem{ 98 + { 99 + RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{ 100 + Did: "did:plc:abc123", 101 + }, 102 + }, 103 + }, 104 + Index: &appbsky.RichtextFacet_ByteSlice{ 105 + ByteStart: 0, 106 + ByteEnd: 9, 107 + }, 108 + }, 109 + { 110 + Features: []*appbsky.RichtextFacet_Features_Elem{ 111 + { 112 + RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{ 113 + Did: "did:plc:abc456", 114 + }, 115 + }, 116 + }, 117 + Index: &appbsky.RichtextFacet_ByteSlice{ 118 + ByteStart: 39, 119 + ByteEnd: 63, 120 + }, 121 + }, 122 + }, 123 + } 124 + assert.True(PostMentionsDid(post, "did:plc:abc123")) 125 + assert.False(PostMentionsDid(post, "did:plc:cba321")) 126 + 127 + didList1 := []string{ 128 + "did:plc:cba321", 129 + "did:web:bsky.app", 130 + "did:plc:abc456", 131 + } 132 + 133 + didList2 := []string{ 134 + "did:plc:321cba", 135 + "did:web:bsky.app", 136 + "did:plc:123abc", 137 + } 138 + 139 + assert.True(PostMentionsAnyDid(post, didList1)) 140 + assert.False(PostMentionsAnyDid(post, didList2)) 141 + }
-73
automod/helpers/helpers.go automod/helpers/bsky.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "regexp" 6 - "time" 7 5 8 6 appbsky "github.com/bluesky-social/indigo/api/bsky" 9 7 "github.com/bluesky-social/indigo/atproto/syntax" 10 8 "github.com/bluesky-social/indigo/automod" 11 9 "github.com/bluesky-social/indigo/automod/keyword" 12 - 13 - "github.com/spaolacci/murmur3" 14 10 ) 15 - 16 - func DedupeStrings(in []string) []string { 17 - var out []string 18 - seen := make(map[string]bool) 19 - for _, v := range in { 20 - if !seen[v] { 21 - out = append(out, v) 22 - seen[v] = true 23 - } 24 - } 25 - return out 26 - } 27 11 28 12 func ExtractHashtagsPost(post *appbsky.FeedPost) []string { 29 13 var tags []string ··· 152 136 return keyword.TokenizeText(s) 153 137 } 154 138 155 - // based on: https://stackoverflow.com/a/48769624, with no trailing period allowed 156 - var urlRegex = regexp.MustCompile(`(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]*[\w/\-&?=%]+`) 157 - 158 - func ExtractTextURLs(raw string) []string { 159 - return urlRegex.FindAllString(raw, -1) 160 - } 161 - 162 139 func ExtractTextURLsProfile(profile *appbsky.ActorProfile) []string { 163 140 s := "" 164 141 if profile.Description != nil { ··· 191 168 return false 192 169 } 193 170 194 - // returns a fast, compact hash of a string 195 - // 196 - // current implementation uses murmur3, default seed, and hex encoding 197 - func HashOfString(s string) string { 198 - val := murmur3.Sum64([]byte(s)) 199 - return fmt.Sprintf("%016x", val) 200 - } 201 - 202 171 func ParentOrRootIsFollower(c *automod.RecordContext, post *appbsky.FeedPost) bool { 203 172 if post.Reply == nil || IsSelfThread(c, post) { 204 173 return false ··· 238 207 rel = c.GetAccountRelationship(rootDID) 239 208 if rel.FollowedBy { 240 209 return true 241 - } 242 - return false 243 - } 244 - 245 - // no accounts exist before this time 246 - var atprotoAccountEpoch = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) 247 - 248 - // returns true if account creation timestamp is plausible: not-nil, not in distant past, not in the future 249 - func plausibleAccountCreation(when *time.Time) bool { 250 - if when == nil { 251 - return false 252 - } 253 - // this is mostly to check for misconfigurations or null values (eg, UNIX epoch zero means "unknown" not actually 1970) 254 - if !when.After(atprotoAccountEpoch) { 255 - return false 256 - } 257 - // a timestamp in the future would also indicate some misconfiguration 258 - if when.After(time.Now().Add(time.Hour)) { 259 - return false 260 - } 261 - return true 262 - } 263 - 264 - // checks if account was created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false' 265 - func AccountIsYoungerThan(c *automod.AccountContext, age time.Duration) bool { 266 - // TODO: consider swapping priority order here (and below) 267 - if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) { 268 - return time.Since(*c.Account.CreatedAt) < age 269 - } 270 - if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) { 271 - return time.Since(*c.Account.Private.IndexedAt) < age 272 - } 273 - return false 274 - } 275 - 276 - // checks if account was *not* created recently, based on either public or private account metadata. if metadata isn't available at all, or seems bogus, returns 'false' 277 - func AccountIsOlderThan(c *automod.AccountContext, age time.Duration) bool { 278 - if c.Account.CreatedAt != nil && plausibleAccountCreation(c.Account.CreatedAt) { 279 - return time.Since(*c.Account.CreatedAt) >= age 280 - } 281 - if c.Account.Private != nil && plausibleAccountCreation(c.Account.Private.IndexedAt) { 282 - return time.Since(*c.Account.Private.IndexedAt) >= age 283 210 } 284 211 return false 285 212 }
-251
automod/helpers/helpers_test.go
··· 1 - package helpers 2 - 3 - import ( 4 - comatproto "github.com/bluesky-social/indigo/api/atproto" 5 - appbsky "github.com/bluesky-social/indigo/api/bsky" 6 - "testing" 7 - "time" 8 - 9 - "github.com/bluesky-social/indigo/atproto/identity" 10 - "github.com/bluesky-social/indigo/atproto/syntax" 11 - "github.com/bluesky-social/indigo/automod" 12 - "github.com/bluesky-social/indigo/automod/keyword" 13 - "github.com/stretchr/testify/assert" 14 - ) 15 - 16 - func TestTokenizeText(t *testing.T) { 17 - assert := assert.New(t) 18 - 19 - fixtures := []struct { 20 - s string 21 - out []string 22 - }{ 23 - { 24 - s: "1 'Two' three!", 25 - out: []string{"1", "two", "three"}, 26 - }, 27 - { 28 - s: " foo1;bar2,baz3...", 29 - out: []string{"foo1", "bar2", "baz3"}, 30 - }, 31 - { 32 - s: "https://example.com/index.html", 33 - out: []string{"https", "example", "com", "index", "html"}, 34 - }, 35 - } 36 - 37 - for _, fix := range fixtures { 38 - assert.Equal(fix.out, keyword.TokenizeText(fix.s)) 39 - } 40 - } 41 - 42 - func TestExtractURL(t *testing.T) { 43 - assert := assert.New(t) 44 - 45 - fixtures := []struct { 46 - s string 47 - out []string 48 - }{ 49 - { 50 - s: "this is a description with example.com mentioned in the middle", 51 - out: []string{"example.com"}, 52 - }, 53 - { 54 - s: "this is another example with https://en.wikipedia.org/index.html: and archive.org, and https://eff.org/... and bsky.app.", 55 - out: []string{"https://en.wikipedia.org/index.html", "archive.org", "https://eff.org/", "bsky.app"}, 56 - }, 57 - } 58 - 59 - for _, fix := range fixtures { 60 - assert.Equal(fix.out, ExtractTextURLs(fix.s)) 61 - } 62 - } 63 - 64 - func TestHashOfString(t *testing.T) { 65 - assert := assert.New(t) 66 - 67 - // hashing function should be consistent over time 68 - assert.Equal("4e6f69c0e3d10992", HashOfString("dummy-value")) 69 - } 70 - 71 - func TestAccountIsYoungerThan(t *testing.T) { 72 - assert := assert.New(t) 73 - 74 - am := automod.AccountMeta{ 75 - Identity: &identity.Identity{ 76 - DID: syntax.DID("did:plc:abc111"), 77 - Handle: syntax.Handle("handle.example.com"), 78 - }, 79 - Profile: automod.ProfileSummary{}, 80 - Private: nil, 81 - } 82 - now := time.Now() 83 - ac := automod.AccountContext{ 84 - Account: am, 85 - } 86 - assert.False(AccountIsYoungerThan(&ac, time.Hour)) 87 - assert.False(AccountIsOlderThan(&ac, time.Hour)) 88 - 89 - ac.Account.CreatedAt = &now 90 - assert.True(AccountIsYoungerThan(&ac, time.Hour)) 91 - assert.False(AccountIsOlderThan(&ac, time.Hour)) 92 - 93 - yesterday := time.Now().Add(-1 * time.Hour * 24) 94 - ac.Account.CreatedAt = &yesterday 95 - assert.False(AccountIsYoungerThan(&ac, time.Hour)) 96 - assert.True(AccountIsOlderThan(&ac, time.Hour)) 97 - 98 - old := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC) 99 - ac.Account.CreatedAt = &old 100 - assert.False(AccountIsYoungerThan(&ac, time.Hour)) 101 - assert.False(AccountIsYoungerThan(&ac, time.Hour*24*365*100)) 102 - assert.False(AccountIsOlderThan(&ac, time.Hour)) 103 - assert.False(AccountIsOlderThan(&ac, time.Hour*24*365*100)) 104 - 105 - future := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC) 106 - ac.Account.CreatedAt = &future 107 - assert.False(AccountIsYoungerThan(&ac, time.Hour)) 108 - assert.False(AccountIsOlderThan(&ac, time.Hour)) 109 - 110 - ac.Account.CreatedAt = nil 111 - ac.Account.Private = &automod.AccountPrivate{ 112 - Email: "account@example.com", 113 - IndexedAt: &yesterday, 114 - } 115 - assert.True(AccountIsYoungerThan(&ac, 48*time.Hour)) 116 - assert.False(AccountIsYoungerThan(&ac, time.Hour)) 117 - assert.True(AccountIsOlderThan(&ac, time.Hour)) 118 - assert.False(AccountIsOlderThan(&ac, 48*time.Hour)) 119 - } 120 - 121 - func TestParentOrRootIsDid(t *testing.T) { 122 - assert := assert.New(t) 123 - 124 - post1 := &appbsky.FeedPost{ 125 - Text: "some random post that i dreamt up last night, idk", 126 - Reply: &appbsky.FeedPost_ReplyRef{ 127 - Root: &comatproto.RepoStrongRef{ 128 - Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 129 - }, 130 - Parent: &comatproto.RepoStrongRef{ 131 - Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 132 - }, 133 - }, 134 - } 135 - 136 - post2 := &appbsky.FeedPost{ 137 - Text: "some random post that i dreamt up last night, idk", 138 - Reply: &appbsky.FeedPost_ReplyRef{ 139 - Root: &comatproto.RepoStrongRef{ 140 - Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 141 - }, 142 - Parent: &comatproto.RepoStrongRef{ 143 - Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 144 - }, 145 - }, 146 - } 147 - 148 - post3 := &appbsky.FeedPost{ 149 - Text: "some random post that i dreamt up last night, idk", 150 - Reply: &appbsky.FeedPost_ReplyRef{ 151 - Root: &comatproto.RepoStrongRef{ 152 - Uri: "at://did:plc:abc123/app.bsky.feed.post/rkey123", 153 - }, 154 - Parent: &comatproto.RepoStrongRef{ 155 - Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 156 - }, 157 - }, 158 - } 159 - 160 - post4 := &appbsky.FeedPost{ 161 - Text: "some random post that i dreamt up last night, idk", 162 - Reply: &appbsky.FeedPost_ReplyRef{ 163 - Root: &comatproto.RepoStrongRef{ 164 - Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 165 - }, 166 - Parent: &comatproto.RepoStrongRef{ 167 - Uri: "at://did:plc:321abc/app.bsky.feed.post/rkey123", 168 - }, 169 - }, 170 - } 171 - 172 - assert.True(PostParentOrRootIsDid(post1, "did:plc:abc123")) 173 - assert.False(PostParentOrRootIsDid(post1, "did:plc:321abc")) 174 - 175 - assert.True(PostParentOrRootIsDid(post2, "did:plc:abc123")) 176 - assert.True(PostParentOrRootIsDid(post2, "did:plc:321abc")) 177 - 178 - assert.True(PostParentOrRootIsDid(post3, "did:plc:abc123")) 179 - assert.True(PostParentOrRootIsDid(post3, "did:plc:321abc")) 180 - 181 - assert.False(PostParentOrRootIsDid(post4, "did:plc:abc123")) 182 - assert.True(PostParentOrRootIsDid(post4, "did:plc:321abc")) 183 - 184 - didList1 := []string{ 185 - "did:plc:cba321", 186 - "did:web:bsky.app", 187 - "did:plc:abc123", 188 - } 189 - 190 - didList2 := []string{ 191 - "did:plc:321cba", 192 - "did:web:bsky.app", 193 - "did:plc:123abc", 194 - } 195 - 196 - assert.True(PostParentOrRootIsAnyDid(post1, didList1)) 197 - assert.False(PostParentOrRootIsAnyDid(post1, didList2)) 198 - } 199 - 200 - func TestPostMentionsDid(t *testing.T) { 201 - assert := assert.New(t) 202 - 203 - post := &appbsky.FeedPost{ 204 - Text: "@hailey.at what is upppp also hello to @darthbluesky.bsky.social", 205 - Facets: []*appbsky.RichtextFacet{ 206 - { 207 - Features: []*appbsky.RichtextFacet_Features_Elem{ 208 - { 209 - RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{ 210 - Did: "did:plc:abc123", 211 - }, 212 - }, 213 - }, 214 - Index: &appbsky.RichtextFacet_ByteSlice{ 215 - ByteStart: 0, 216 - ByteEnd: 9, 217 - }, 218 - }, 219 - { 220 - Features: []*appbsky.RichtextFacet_Features_Elem{ 221 - { 222 - RichtextFacet_Mention: &appbsky.RichtextFacet_Mention{ 223 - Did: "did:plc:abc456", 224 - }, 225 - }, 226 - }, 227 - Index: &appbsky.RichtextFacet_ByteSlice{ 228 - ByteStart: 39, 229 - ByteEnd: 63, 230 - }, 231 - }, 232 - }, 233 - } 234 - assert.True(PostMentionsDid(post, "did:plc:abc123")) 235 - assert.False(PostMentionsDid(post, "did:plc:cba321")) 236 - 237 - didList1 := []string{ 238 - "did:plc:cba321", 239 - "did:web:bsky.app", 240 - "did:plc:abc456", 241 - } 242 - 243 - didList2 := []string{ 244 - "did:plc:321cba", 245 - "did:web:bsky.app", 246 - "did:plc:123abc", 247 - } 248 - 249 - assert.True(PostMentionsAnyDid(post, didList1)) 250 - assert.False(PostMentionsAnyDid(post, didList2)) 251 - }
+35
automod/helpers/text.go
··· 1 + package helpers 2 + 3 + import ( 4 + "fmt" 5 + "regexp" 6 + 7 + "github.com/spaolacci/murmur3" 8 + ) 9 + 10 + func DedupeStrings(in []string) []string { 11 + var out []string 12 + seen := make(map[string]bool) 13 + for _, v := range in { 14 + if !seen[v] { 15 + out = append(out, v) 16 + seen[v] = true 17 + } 18 + } 19 + return out 20 + } 21 + 22 + // returns a fast, compact hash of a string 23 + // 24 + // current implementation uses murmur3, default seed, and hex encoding 25 + func HashOfString(s string) string { 26 + val := murmur3.Sum64([]byte(s)) 27 + return fmt.Sprintf("%016x", val) 28 + } 29 + 30 + // based on: https://stackoverflow.com/a/48769624, with no trailing period allowed 31 + var urlRegex = regexp.MustCompile(`(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-&?=%.]*[\w/\-&?=%]+`) 32 + 33 + func ExtractTextURLs(raw string) []string { 34 + return urlRegex.FindAllString(raw, -1) 35 + }
+64
automod/helpers/text_test.go
··· 1 + package helpers 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/bluesky-social/indigo/automod/keyword" 7 + 8 + "github.com/stretchr/testify/assert" 9 + ) 10 + 11 + func TestTokenizeText(t *testing.T) { 12 + assert := assert.New(t) 13 + 14 + fixtures := []struct { 15 + s string 16 + out []string 17 + }{ 18 + { 19 + s: "1 'Two' three!", 20 + out: []string{"1", "two", "three"}, 21 + }, 22 + { 23 + s: " foo1;bar2,baz3...", 24 + out: []string{"foo1", "bar2", "baz3"}, 25 + }, 26 + { 27 + s: "https://example.com/index.html", 28 + out: []string{"https", "example", "com", "index", "html"}, 29 + }, 30 + } 31 + 32 + for _, fix := range fixtures { 33 + assert.Equal(fix.out, keyword.TokenizeText(fix.s)) 34 + } 35 + } 36 + 37 + func TestExtractURL(t *testing.T) { 38 + assert := assert.New(t) 39 + 40 + fixtures := []struct { 41 + s string 42 + out []string 43 + }{ 44 + { 45 + s: "this is a description with example.com mentioned in the middle", 46 + out: []string{"example.com"}, 47 + }, 48 + { 49 + s: "this is another example with https://en.wikipedia.org/index.html: and archive.org, and https://eff.org/... and bsky.app.", 50 + out: []string{"https://en.wikipedia.org/index.html", "archive.org", "https://eff.org/", "bsky.app"}, 51 + }, 52 + } 53 + 54 + for _, fix := range fixtures { 55 + assert.Equal(fix.out, ExtractTextURLs(fix.s)) 56 + } 57 + } 58 + 59 + func TestHashOfString(t *testing.T) { 60 + assert := assert.New(t) 61 + 62 + // hashing function should be consistent over time 63 + assert.Equal("4e6f69c0e3d10992", HashOfString("dummy-value")) 64 + }