fork of indigo with slightly nicer lexgen
at main 6.2 kB view raw
1package helpers 2 3import ( 4 "fmt" 5 6 appbsky "github.com/bluesky-social/indigo/api/bsky" 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 "github.com/bluesky-social/indigo/automod" 9 "github.com/bluesky-social/indigo/automod/keyword" 10) 11 12func ExtractHashtagsPost(post *appbsky.FeedPost) []string { 13 var tags []string 14 tags = append(tags, post.Tags...) 15 for _, facet := range post.Facets { 16 for _, feat := range facet.Features { 17 if feat.RichtextFacet_Tag != nil { 18 tags = append(tags, feat.RichtextFacet_Tag.Tag) 19 } 20 } 21 } 22 return DedupeStrings(tags) 23} 24 25func NormalizeHashtag(raw string) string { 26 return keyword.Slugify(raw) 27} 28 29type PostFacet struct { 30 Text string 31 URL *string 32 DID *string 33 Tag *string 34} 35 36func ExtractFacets(post *appbsky.FeedPost) ([]PostFacet, error) { 37 var out []PostFacet 38 39 for _, facet := range post.Facets { 40 for _, feat := range facet.Features { 41 if int(facet.Index.ByteEnd) > len([]byte(post.Text)) || facet.Index.ByteStart > facet.Index.ByteEnd { 42 return nil, fmt.Errorf("invalid facet byte range") 43 } 44 45 txt := string([]byte(post.Text)[facet.Index.ByteStart:facet.Index.ByteEnd]) 46 if txt == "" { 47 return nil, fmt.Errorf("empty facet text") 48 } 49 50 if feat.RichtextFacet_Link != nil { 51 out = append(out, PostFacet{ 52 Text: txt, 53 URL: &feat.RichtextFacet_Link.Uri, 54 }) 55 } 56 if feat.RichtextFacet_Tag != nil { 57 out = append(out, PostFacet{ 58 Text: txt, 59 Tag: &feat.RichtextFacet_Tag.Tag, 60 }) 61 } 62 if feat.RichtextFacet_Mention != nil { 63 out = append(out, PostFacet{ 64 Text: txt, 65 DID: &feat.RichtextFacet_Mention.Did, 66 }) 67 } 68 } 69 } 70 return out, nil 71} 72 73func ExtractPostBlobCIDsPost(post *appbsky.FeedPost) []string { 74 var out []string 75 if post.Embed.EmbedImages != nil { 76 for _, img := range post.Embed.EmbedImages.Images { 77 out = append(out, img.Image.Ref.String()) 78 } 79 } 80 if post.Embed.EmbedRecordWithMedia != nil { 81 media := post.Embed.EmbedRecordWithMedia.Media 82 if media.EmbedImages != nil { 83 for _, img := range media.EmbedImages.Images { 84 out = append(out, img.Image.Ref.String()) 85 } 86 } 87 } 88 return DedupeStrings(out) 89} 90 91func ExtractBlobCIDsProfile(profile *appbsky.ActorProfile) []string { 92 var out []string 93 if profile.Avatar != nil { 94 out = append(out, profile.Avatar.Ref.String()) 95 } 96 if profile.Banner != nil { 97 out = append(out, profile.Banner.Ref.String()) 98 } 99 return DedupeStrings(out) 100} 101 102func ExtractTextTokensPost(post *appbsky.FeedPost) []string { 103 s := post.Text 104 if post.Embed != nil { 105 if post.Embed.EmbedImages != nil { 106 for _, img := range post.Embed.EmbedImages.Images { 107 if img.Alt != "" { 108 s += " " + img.Alt 109 } 110 } 111 } 112 if post.Embed.EmbedRecordWithMedia != nil { 113 media := post.Embed.EmbedRecordWithMedia.Media 114 if media.EmbedImages != nil { 115 for _, img := range media.EmbedImages.Images { 116 if img.Alt != "" { 117 s += " " + img.Alt 118 } 119 } 120 } 121 } 122 } 123 return keyword.TokenizeText(s) 124} 125 126func ExtractTextTokensProfile(profile *appbsky.ActorProfile) []string { 127 s := "" 128 if profile.Description != nil { 129 s += " " + *profile.Description 130 } 131 if profile.DisplayName != nil { 132 s += " " + *profile.DisplayName 133 } 134 return keyword.TokenizeText(s) 135} 136 137func ExtractTextURLsProfile(profile *appbsky.ActorProfile) []string { 138 s := "" 139 if profile.Description != nil { 140 s += " " + *profile.Description 141 } 142 if profile.DisplayName != nil { 143 s += " " + *profile.DisplayName 144 } 145 return ExtractTextURLs(s) 146} 147 148// checks if the post event is a reply post for which the author is replying to themselves, or author is the root author (OP) 149func IsSelfThread(c *automod.RecordContext, post *appbsky.FeedPost) bool { 150 if post.Reply == nil { 151 return false 152 } 153 did := c.Account.Identity.DID.String() 154 parentURI, err := syntax.ParseATURI(post.Reply.Parent.Uri) 155 if err != nil { 156 return false 157 } 158 rootURI, err := syntax.ParseATURI(post.Reply.Root.Uri) 159 if err != nil { 160 return false 161 } 162 163 if parentURI.Authority().String() == did || rootURI.Authority().String() == did { 164 return true 165 } 166 return false 167} 168 169func ParentOrRootIsFollower(c *automod.RecordContext, post *appbsky.FeedPost) bool { 170 if post.Reply == nil || IsSelfThread(c, post) { 171 return false 172 } 173 174 parentURI, err := syntax.ParseATURI(post.Reply.Parent.Uri) 175 if err != nil { 176 c.Logger.Warn("failed to parse reply AT-URI", "uri", post.Reply.Parent.Uri) 177 return false 178 } 179 parentDID, err := parentURI.Authority().AsDID() 180 if err != nil { 181 c.Logger.Warn("reply AT-URI authority not a DID", "uri", post.Reply.Parent.Uri) 182 return false 183 } 184 185 rel := c.GetAccountRelationship(parentDID) 186 if rel.FollowedBy { 187 return true 188 } 189 190 rootURI, err := syntax.ParseATURI(post.Reply.Root.Uri) 191 if err != nil { 192 c.Logger.Warn("failed to parse reply AT-URI", "uri", post.Reply.Root.Uri) 193 return false 194 } 195 rootDID, err := rootURI.Authority().AsDID() 196 if err != nil { 197 c.Logger.Warn("reply AT-URI authority not a DID", "uri", post.Reply.Root.Uri) 198 return false 199 } 200 201 if rootDID == parentDID { 202 return false 203 } 204 205 rel = c.GetAccountRelationship(rootDID) 206 if rel.FollowedBy { 207 return true 208 } 209 return false 210} 211 212func PostParentOrRootIsDid(post *appbsky.FeedPost, did string) bool { 213 if post.Reply == nil { 214 return false 215 } 216 217 rootUri, err := syntax.ParseATURI(post.Reply.Root.Uri) 218 if err != nil || !rootUri.Authority().IsDID() { 219 return false 220 } 221 222 parentUri, err := syntax.ParseATURI(post.Reply.Parent.Uri) 223 if err != nil || !parentUri.Authority().IsDID() { 224 return false 225 } 226 227 return rootUri.Authority().String() == did || parentUri.Authority().String() == did 228} 229 230func PostParentOrRootIsAnyDid(post *appbsky.FeedPost, dids []string) bool { 231 if post.Reply == nil { 232 return false 233 } 234 235 for _, did := range dids { 236 if PostParentOrRootIsDid(post, did) { 237 return true 238 } 239 } 240 241 return false 242} 243 244func PostMentionsDid(post *appbsky.FeedPost, did string) bool { 245 facets, err := ExtractFacets(post) 246 if err != nil { 247 return false 248 } 249 250 for _, facet := range facets { 251 if facet.DID != nil && *facet.DID == did { 252 return true 253 } 254 } 255 256 return false 257} 258 259func PostMentionsAnyDid(post *appbsky.FeedPost, dids []string) bool { 260 for _, did := range dids { 261 if PostMentionsDid(post, did) { 262 return true 263 } 264 } 265 266 return false 267}