Skip to content

Commit 8545f8e

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
add avatar support
1 parent 2a0578a commit 8545f8e

File tree

12 files changed

+59
-9
lines changed

12 files changed

+59
-9
lines changed

pkg/bluesky/bluesky.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func parseAPIResponse(data []byte, urlStr, handle string) (*profile.Profile, err
112112
var resp struct {
113113
Handle string `json:"handle"`
114114
DisplayName string `json:"displayName"`
115+
Avatar string `json:"avatar"`
115116
Description string `json:"description"`
116117
CreatedAt string `json:"createdAt"`
117118
}
@@ -126,6 +127,7 @@ func parseAPIResponse(data []byte, urlStr, handle string) (*profile.Profile, err
126127
Authenticated: false,
127128
Username: handle,
128129
Name: resp.DisplayName,
130+
AvatarURL: resp.Avatar,
129131
Bio: resp.Description,
130132
Fields: make(map[string]string),
131133
}

pkg/codeberg/codeberg.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ func parseHTML(data []byte, urlStr, username string) *profile.Profile {
140140
}
141141
}
142142

143+
// Extract avatar URL
144+
avatarURLPattern := regexp.MustCompile(`<img[^>]+class="[^"]*avatar[^"]*"[^>]+src="([^"]+)"`)
145+
if m := avatarURLPattern.FindStringSubmatch(content); len(m) > 1 {
146+
avatarURL := m[1]
147+
// Make relative URLs absolute
148+
if strings.HasPrefix(avatarURL, "/") {
149+
avatarURL = "https://codeberg.org" + avatarURL
150+
}
151+
prof.AvatarURL = avatarURL
152+
}
153+
143154
// Fallback: Extract from profile-avatar-name header
144155
// Pattern: <span class="header text center">Woohyun Joh</span>
145156
if prof.Name == "" {

pkg/devto/devto.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ func parseHTML(data []byte, urlStr, username string) *profile.Profile {
122122
p.Name = strings.TrimSpace(html.UnescapeString(m[1]))
123123
}
124124

125+
// Extract avatar URL from profile image
126+
avatarPattern := regexp.MustCompile(`<img[^>]+class="[^"]*crayons-avatar[^"]*"[^>]+src="([^"]+)"`)
127+
if m := avatarPattern.FindStringSubmatch(content); len(m) > 1 {
128+
p.AvatarURL = m[1]
129+
}
130+
125131
// Fallback to og:title
126132
if p.Name == "" {
127133
title := htmlutil.Title(content)

pkg/github/github.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ func parseJSON(data []byte, urlStr, _ string) (*profile.Profile, error) {
741741

742742
// Add avatar URL
743743
if ghUser.AvatarURL != "" {
744-
prof.Fields["avatar_url"] = ghUser.AvatarURL
744+
prof.AvatarURL = ghUser.AvatarURL
745745
}
746746

747747
// Add account type
@@ -841,7 +841,7 @@ func (c *Client) parseProfileFromHTML(ctx context.Context, html, urlStr, usernam
841841
// Extract avatar URL
842842
avatarPattern := regexp.MustCompile(`<img[^>]+class="[^"]*avatar avatar-user[^"]*"[^>]+src="([^"]+)"`)
843843
if matches := avatarPattern.FindStringSubmatch(html); len(matches) > 1 {
844-
prof.Fields["avatar_url"] = matches[1]
844+
prof.AvatarURL = matches[1]
845845
}
846846

847847
c.logger.DebugContext(ctx, "parsed profile from HTML",

pkg/linktree/linktree.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ func parseAccountInfo(p *profile.Profile, pageProps map[string]any) {
178178
if desc, ok := account["description"].(string); ok {
179179
p.Bio = desc
180180
}
181+
// Extract avatar URL from profilePictureUrl
182+
if avatarURL, ok := account["profilePictureUrl"].(string); ok && avatarURL != "" {
183+
p.AvatarURL = avatarURL
184+
}
181185
}
182186

183187
func parseLinks(p *profile.Profile, pageProps map[string]any) {

pkg/mastodon/mastodon.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ func (*Client) parseAPIResponse(data []byte) (*profile.Profile, string, error) {
168168
ID string `json:"id"`
169169
Username string `json:"username"`
170170
DisplayName string `json:"display_name"`
171+
Avatar string `json:"avatar"`
171172
Note string `json:"note"`
172173
CreatedAt string `json:"created_at"`
173174
Fields []struct {
@@ -215,6 +216,11 @@ func (*Client) parseAPIResponse(data []byte) (*profile.Profile, string, error) {
215216
p.CreatedAt = acc.CreatedAt
216217
}
217218

219+
// Add avatar URL
220+
if acc.Avatar != "" {
221+
p.AvatarURL = acc.Avatar
222+
}
223+
218224
return p, acc.ID, nil
219225
}
220226

pkg/profile/profile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type Profile struct {
4949
// Core profile data
5050
Username string `json:",omitempty"` // Handle/username (without @ prefix)
5151
Name string `json:",omitempty"` // Display name
52+
AvatarURL string `json:",omitempty"` // Profile photo/avatar URL
5253
Bio string `json:",omitempty"` // Profile bio/description
5354
Location string `json:",omitempty"` // Geographic location
5455
Website string `json:",omitempty"` // Personal website URL

pkg/sociopath/integration_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func TestIntegrationLiveFetch(t *testing.T) {
224224
CreatedAt: "2022-11-03T00:00:00.000Z",
225225
},
226226
cmpOpts: []cmp.Option{
227-
cmpopts.IgnoreFields(profile.Profile{}, "Location", "Website", "UpdatedAt", "SocialLinks", "Fields", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
227+
cmpopts.IgnoreFields(profile.Profile{}, "AvatarURL", "Location", "Website", "UpdatedAt", "SocialLinks", "Fields", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
228228
},
229229
},
230230
{
@@ -635,7 +635,7 @@ func TestIntegrationLiveFetch(t *testing.T) {
635635
// Name, Bio, Location are empty when auth is broken
636636
},
637637
cmpOpts: []cmp.Option{
638-
cmpopts.IgnoreFields(profile.Profile{}, "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
638+
cmpopts.IgnoreFields(profile.Profile{}, "AvatarURL", "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
639639
},
640640
},
641641
{
@@ -660,7 +660,7 @@ func TestIntegrationLiveFetch(t *testing.T) {
660660
Username: "mattmoor",
661661
},
662662
cmpOpts: []cmp.Option{
663-
cmpopts.IgnoreFields(profile.Profile{}, "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
663+
cmpopts.IgnoreFields(profile.Profile{}, "AvatarURL", "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
664664
},
665665
},
666666
{
@@ -685,7 +685,7 @@ func TestIntegrationLiveFetch(t *testing.T) {
685685
Username: "austen-bryan-23485a19",
686686
},
687687
cmpOpts: []cmp.Option{
688-
cmpopts.IgnoreFields(profile.Profile{}, "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
688+
cmpopts.IgnoreFields(profile.Profile{}, "AvatarURL", "Fields", "Name", "Bio", "Location", "Website", "CreatedAt", "UpdatedAt", "SocialLinks", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
689689
},
690690
},
691691
}
@@ -696,7 +696,8 @@ func TestIntegrationLiveFetch(t *testing.T) {
696696
// Fields, SocialLinks contain varying platform-specific data
697697
// UpdatedAt, Posts, Unstructured change with activity
698698
// UTCOffset depends on user's timezone settings
699-
cmpopts.IgnoreFields(profile.Profile{}, "Bio", "Location", "Website", "Fields", "SocialLinks", "UpdatedAt", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
699+
// AvatarURL varies by user and can change
700+
cmpopts.IgnoreFields(profile.Profile{}, "AvatarURL", "Bio", "Location", "Website", "Fields", "SocialLinks", "UpdatedAt", "Posts", "Unstructured", "IsGuess", "Confidence", "GuessMatch", "UTCOffset"),
700701
}
701702

702703
for _, tt := range tests {

pkg/stackoverflow/stackoverflow.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ func parseHTML(data []byte, urlStr, username string) *profile.Profile {
109109
}
110110
}
111111

112+
// Extract avatar URL from profile image
113+
avatarPattern := regexp.MustCompile(`<img[^>]+class="[^"]*s-avatar[^"]*"[^>]+src="([^"]+)"`)
114+
if m := avatarPattern.FindStringSubmatch(content); len(m) > 1 {
115+
p.AvatarURL = m[1]
116+
}
117+
112118
// Extract location
113119
locPattern := regexp.MustCompile(`<div[^>]*class="[^"]*wmx2[^"]*truncate[^"]*"[^>]*title="([^"]+)"`)
114120
if m := locPattern.FindStringSubmatch(content); len(m) > 1 {

pkg/tiktok/tiktok.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ func (c *Client) parseProfile(ctx context.Context, body []byte, profileURL strin
199199
if name, ok := user["nickname"].(string); ok {
200200
p.Name = name
201201
}
202+
if avatarURL, ok := user["avatarLarger"].(string); ok {
203+
p.AvatarURL = avatarURL
204+
} else if avatarURL, ok := user["avatarMedium"].(string); ok {
205+
p.AvatarURL = avatarURL
206+
}
202207
if signature, ok := user["signature"].(string); ok {
203208
p.Bio = signature
204209
}

0 commit comments

Comments
 (0)