test
0

Configure Feed

Select the types of activity you want to include in your feed.

automod

+284 -6
+25 -4
automod/helpers/bsky.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "strings" 5 6 6 7 appbsky "github.com/bluesky-social/indigo/api/bsky" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" ··· 71 72 } 72 73 73 74 func ExtractPostBlobCIDsPost(post *appbsky.FeedPost) []string { 75 + if post.Embed == nil { 76 + return []string{} 77 + } 78 + 74 79 var out []string 75 80 if post.Embed.EmbedImages != nil { 76 81 for _, img := range post.Embed.EmbedImages.Images { ··· 85 90 } 86 91 } 87 92 } 93 + if post.Embed.EmbedGallery != nil { 94 + for _, item := range post.Embed.EmbedGallery.Items { 95 + if item.EmbedGallery_Image != nil && item.EmbedGallery_Image.Image != nil { 96 + out = append(out, item.EmbedGallery_Image.Image.Ref.String()) 97 + } 98 + } 99 + } 100 + 88 101 return DedupeStrings(out) 89 102 } 90 103 ··· 100 113 } 101 114 102 115 func ExtractTextTokensPost(post *appbsky.FeedPost) []string { 103 - s := post.Text 116 + s := strings.Builder{} 117 + s.WriteString(post.Text) 104 118 if post.Embed != nil { 105 119 if post.Embed.EmbedImages != nil { 106 120 for _, img := range post.Embed.EmbedImages.Images { 107 121 if img.Alt != "" { 108 - s += " " + img.Alt 122 + s.WriteString(" " + img.Alt) 109 123 } 110 124 } 111 125 } ··· 114 128 if media.EmbedImages != nil { 115 129 for _, img := range media.EmbedImages.Images { 116 130 if img.Alt != "" { 117 - s += " " + img.Alt 131 + s.WriteString(" " + img.Alt) 118 132 } 119 133 } 120 134 } 121 135 } 136 + if post.Embed.EmbedGallery != nil { 137 + for _, item := range post.Embed.EmbedGallery.Items { 138 + if item.EmbedGallery_Image != nil && item.EmbedGallery_Image.Alt != "" { 139 + s.WriteString(" " + item.EmbedGallery_Image.Alt) 140 + } 141 + } 142 + } 122 143 } 123 - return keyword.TokenizeText(s) 144 + return keyword.TokenizeText(s.String()) 124 145 } 125 146 126 147 func ExtractTextTokensProfile(profile *appbsky.ActorProfile) []string {
+257 -1
automod/helpers/bsky_test.go
··· 1 1 package helpers 2 2 3 3 import ( 4 + "testing" 5 + 4 6 comatproto "github.com/bluesky-social/indigo/api/atproto" 5 7 appbsky "github.com/bluesky-social/indigo/api/bsky" 6 - "testing" 8 + "github.com/bluesky-social/indigo/automod/keyword" 9 + lexutil "github.com/bluesky-social/indigo/lex/util" 7 10 11 + "github.com/ipfs/go-cid" 8 12 "github.com/stretchr/testify/assert" 9 13 ) 10 14 ··· 139 143 assert.True(PostMentionsAnyDid(post, didList1)) 140 144 assert.False(PostMentionsAnyDid(post, didList2)) 141 145 } 146 + 147 + func mustBlob(t *testing.T, c string) *lexutil.LexBlob { 148 + t.Helper() 149 + parsed, err := cid.Decode(c) 150 + if err != nil { 151 + t.Fatalf("invalid test CID %q: %v", c, err) 152 + } 153 + return &lexutil.LexBlob{ 154 + Ref: lexutil.LexLink(parsed), 155 + MimeType: "image/jpeg", 156 + Size: 1024, 157 + } 158 + } 159 + 160 + func TestExtractPostBlobCIDsPost(t *testing.T) { 161 + cidA := "bafkreieqq463374bbcbeq7gpmet5rvrpeqow6t4rtjzrkhnlumdylagaqa" 162 + cidB := "bafkreicwamkg77pijyudfbdmskelsnuztr6gp62lqfjv3e3urbs3gxnv2m" 163 + 164 + tests := []struct { 165 + name string 166 + embed *appbsky.FeedPost_Embed 167 + expected []string 168 + }{ 169 + { 170 + name: "nil embed", 171 + embed: nil, 172 + expected: []string{}, 173 + }, 174 + { 175 + name: "empty embed", 176 + embed: &appbsky.FeedPost_Embed{}, 177 + expected: nil, 178 + }, 179 + { 180 + name: "images only", 181 + embed: &appbsky.FeedPost_Embed{ 182 + EmbedImages: &appbsky.EmbedImages{ 183 + Images: []*appbsky.EmbedImages_Image{ 184 + {Image: mustBlob(t, cidA)}, 185 + {Image: mustBlob(t, cidB)}, 186 + }, 187 + }, 188 + }, 189 + expected: []string{cidA, cidB}, 190 + }, 191 + { 192 + name: "recordWithMedia images", 193 + embed: &appbsky.FeedPost_Embed{ 194 + EmbedRecordWithMedia: &appbsky.EmbedRecordWithMedia{ 195 + Media: &appbsky.EmbedRecordWithMedia_Media{ 196 + EmbedImages: &appbsky.EmbedImages{ 197 + Images: []*appbsky.EmbedImages_Image{ 198 + {Image: mustBlob(t, cidA)}, 199 + }, 200 + }, 201 + }, 202 + }, 203 + }, 204 + expected: []string{cidA}, 205 + }, 206 + { 207 + name: "gallery only", 208 + embed: &appbsky.FeedPost_Embed{ 209 + EmbedGallery: &appbsky.EmbedGallery{ 210 + Items: []*appbsky.EmbedGallery_Items_Elem{ 211 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidA)}}, 212 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidB)}}, 213 + }, 214 + }, 215 + }, 216 + expected: []string{cidA, cidB}, 217 + }, 218 + { 219 + name: "gallery with nil EmbedGallery_Image element", 220 + embed: &appbsky.FeedPost_Embed{ 221 + EmbedGallery: &appbsky.EmbedGallery{ 222 + Items: []*appbsky.EmbedGallery_Items_Elem{ 223 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidA)}}, 224 + {EmbedGallery_Image: nil}, 225 + }, 226 + }, 227 + }, 228 + expected: []string{cidA}, 229 + }, 230 + { 231 + name: "gallery with nil Image blob", 232 + embed: &appbsky.FeedPost_Embed{ 233 + EmbedGallery: &appbsky.EmbedGallery{ 234 + Items: []*appbsky.EmbedGallery_Items_Elem{ 235 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidA)}}, 236 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: nil}}, 237 + }, 238 + }, 239 + }, 240 + expected: []string{cidA}, 241 + }, 242 + { 243 + name: "images and gallery with duplicate CID are deduped", 244 + embed: &appbsky.FeedPost_Embed{ 245 + EmbedImages: &appbsky.EmbedImages{ 246 + Images: []*appbsky.EmbedImages_Image{ 247 + {Image: mustBlob(t, cidA)}, 248 + }, 249 + }, 250 + EmbedGallery: &appbsky.EmbedGallery{ 251 + Items: []*appbsky.EmbedGallery_Items_Elem{ 252 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidA)}}, 253 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Image: mustBlob(t, cidB)}}, 254 + }, 255 + }, 256 + }, 257 + expected: []string{cidA, cidB}, 258 + }, 259 + } 260 + 261 + for _, tc := range tests { 262 + t.Run(tc.name, func(t *testing.T) { 263 + assert := assert.New(t) 264 + post := &appbsky.FeedPost{ 265 + Text: "irrelevant", 266 + Embed: tc.embed, 267 + } 268 + got := ExtractPostBlobCIDsPost(post) 269 + assert.ElementsMatch(tc.expected, got) 270 + }) 271 + } 272 + } 273 + 274 + func TestExtractTextTokensPost(t *testing.T) { 275 + tests := []struct { 276 + name string 277 + text string 278 + embed *appbsky.FeedPost_Embed 279 + expectedText string 280 + }{ 281 + { 282 + name: "text only, no embed", 283 + text: "hello world", 284 + embed: nil, 285 + expectedText: "hello world", 286 + }, 287 + { 288 + name: "text plus image alt", 289 + text: "hello", 290 + embed: &appbsky.FeedPost_Embed{ 291 + EmbedImages: &appbsky.EmbedImages{ 292 + Images: []*appbsky.EmbedImages_Image{ 293 + {Alt: "sunset"}, 294 + }, 295 + }, 296 + }, 297 + expectedText: "hello sunset", 298 + }, 299 + { 300 + name: "text plus recordWithMedia image alt", 301 + text: "hi", 302 + embed: &appbsky.FeedPost_Embed{ 303 + EmbedRecordWithMedia: &appbsky.EmbedRecordWithMedia{ 304 + Media: &appbsky.EmbedRecordWithMedia_Media{ 305 + EmbedImages: &appbsky.EmbedImages{ 306 + Images: []*appbsky.EmbedImages_Image{ 307 + {Alt: "cat"}, 308 + }, 309 + }, 310 + }, 311 + }, 312 + }, 313 + expectedText: "hi cat", 314 + }, 315 + { 316 + name: "text plus gallery alts", 317 + text: "post", 318 + embed: &appbsky.FeedPost_Embed{ 319 + EmbedGallery: &appbsky.EmbedGallery{ 320 + Items: []*appbsky.EmbedGallery_Items_Elem{ 321 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "one"}}, 322 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "two"}}, 323 + }, 324 + }, 325 + }, 326 + expectedText: "post one two", 327 + }, 328 + { 329 + name: "gallery with nil EmbedGallery_Image element", 330 + text: "x", 331 + embed: &appbsky.FeedPost_Embed{ 332 + EmbedGallery: &appbsky.EmbedGallery{ 333 + Items: []*appbsky.EmbedGallery_Items_Elem{ 334 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "a"}}, 335 + {EmbedGallery_Image: nil}, 336 + }, 337 + }, 338 + }, 339 + expectedText: "x a", 340 + }, 341 + { 342 + name: "gallery item with empty alt is skipped", 343 + text: "x", 344 + embed: &appbsky.FeedPost_Embed{ 345 + EmbedGallery: &appbsky.EmbedGallery{ 346 + Items: []*appbsky.EmbedGallery_Items_Elem{ 347 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: ""}}, 348 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "b"}}, 349 + }, 350 + }, 351 + }, 352 + expectedText: "x b", 353 + }, 354 + { 355 + name: "combined images and gallery alts", 356 + text: "start", 357 + embed: &appbsky.FeedPost_Embed{ 358 + EmbedImages: &appbsky.EmbedImages{ 359 + Images: []*appbsky.EmbedImages_Image{ 360 + {Alt: "img"}, 361 + }, 362 + }, 363 + EmbedGallery: &appbsky.EmbedGallery{ 364 + Items: []*appbsky.EmbedGallery_Items_Elem{ 365 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "g1"}}, 366 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "g2"}}, 367 + }, 368 + }, 369 + }, 370 + expectedText: "start img g1 g2", 371 + }, 372 + { 373 + name: "empty post text with gallery alt", 374 + text: "", 375 + embed: &appbsky.FeedPost_Embed{ 376 + EmbedGallery: &appbsky.EmbedGallery{ 377 + Items: []*appbsky.EmbedGallery_Items_Elem{ 378 + {EmbedGallery_Image: &appbsky.EmbedGallery_Image{Alt: "only"}}, 379 + }, 380 + }, 381 + }, 382 + expectedText: " only", 383 + }, 384 + } 385 + 386 + for _, tc := range tests { 387 + t.Run(tc.name, func(t *testing.T) { 388 + assert := assert.New(t) 389 + post := &appbsky.FeedPost{ 390 + Text: tc.text, 391 + Embed: tc.embed, 392 + } 393 + got := ExtractTextTokensPost(post) 394 + assert.Equal(keyword.TokenizeText(tc.expectedText), got) 395 + }) 396 + } 397 + }
+2 -1
automod/rules/hashtags.go
··· 43 43 } 44 44 tagTextRatio := float64(tagChars) / float64(len(post.Text)) 45 45 // if there is an image, allow some more tags 46 - if len(tags) > 4 && tagTextRatio > 0.6 && post.Embed.EmbedImages == nil { 46 + hasImages := (post.Embed.EmbedImages != nil && len(post.Embed.EmbedImages.Images) > 0) || (post.Embed.EmbedGallery != nil && len(post.Embed.EmbedGallery.Items) > 0) 47 + if len(tags) > 4 && tagTextRatio > 0.6 && !hasImages { 47 48 c.AddRecordFlag("many-hashtags") 48 49 c.Notify("slack") 49 50 } else if len(tags) > 7 && tagTextRatio > 0.8 {