Skip to content

Commit 4906b7f

Browse files
committed
feat: implement updating profile pictures
1 parent 9408513 commit 4906b7f

3 files changed

Lines changed: 144 additions & 5 deletions

File tree

bluesky/blueskyapi.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ type RecordValue struct { // TODO: Figure out how to make it get different types
278278
CreatedAt time.Time `json:"createdAt,omitempty"`
279279
Description string `json:"description,omitempty"`
280280
DisplayName string `json:"displayName,omitempty"`
281-
Avatar interface{} `json:"avatar,omitempty"` // TODO: implement avatar
281+
Avatar Blob `json:"avatar,omitempty"`
282282
}
283283

284284
type Relationships struct {
@@ -349,6 +349,25 @@ func SendRequest(token *string, method string, url string, body io.Reader) (*htt
349349
return resp, nil
350350
}
351351

352+
func SendRequestWithContentType(token *string, method string, url string, body io.Reader, content_type string) (*http.Response, error) {
353+
client := &http.Client{}
354+
req, err := http.NewRequest(method, url, body)
355+
if err != nil {
356+
return nil, err
357+
}
358+
if token != nil {
359+
req.Header.Set("Authorization", "Bearer "+*token)
360+
}
361+
req.Header.Set("Content-Type", content_type) // 99% sure all bluesky requests are json.
362+
363+
resp, err := client.Do(req)
364+
if err != nil {
365+
return nil, err
366+
}
367+
368+
return resp, nil
369+
}
370+
352371
func GetUserInfo(pds string, token string, screen_name string, nocache bool) (*bridge.TwitterUser, error) {
353372
if !nocache {
354373
if user, found := userCache.Get(screen_name); found {
@@ -1613,6 +1632,33 @@ func GetNotifications(pds string, token string, limit int, context string) (*Not
16131632
return &notifications, nil
16141633
}
16151634

1635+
func UploadBlob(pds string, token string, data []byte, content_type string) (*Blob, error) {
1636+
url := pds + "/xrpc/com.atproto.repo.uploadBlob"
1637+
1638+
resp, err := SendRequestWithContentType(&token, http.MethodPost, url, bytes.NewReader(data), content_type)
1639+
if err != nil {
1640+
return nil, err
1641+
}
1642+
defer resp.Body.Close()
1643+
1644+
if resp.StatusCode != http.StatusOK {
1645+
bodyBytes, _ := io.ReadAll(resp.Body)
1646+
bodyString := string(bodyBytes)
1647+
fmt.Println("Response Status:", resp.StatusCode)
1648+
fmt.Println("Response Body:", bodyString)
1649+
return nil, errors.New("failed to upload blob")
1650+
}
1651+
1652+
blob := struct {
1653+
Blob Blob `json:"blob"`
1654+
}{}
1655+
if err := json.NewDecoder(resp.Body).Decode(&blob); err != nil {
1656+
return nil, err
1657+
}
1658+
1659+
return &blob.Blob, nil
1660+
}
1661+
16161662
// Gets the URI components
16171663
//
16181664
// @param uri: The URI to ge split

twitterv1/account.go

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@ package twitterv1
22

33
import (
44
"fmt"
5+
"io"
56
"strings"
7+
"sync"
68

79
blueskyapi "github.com/Preloading/MastodonTwitterAPI/bluesky"
810
"github.com/Preloading/MastodonTwitterAPI/bridge"
911
"github.com/gofiber/fiber/v2"
1012
)
1113

14+
// Mutex map to store mutexes for each user
15+
var userMutexes = make(map[string]*sync.Mutex)
16+
var mutexMapLock sync.Mutex
17+
18+
func getUserMutex(userID string) *sync.Mutex {
19+
mutexMapLock.Lock()
20+
defer mutexMapLock.Unlock()
21+
if _, exists := userMutexes[userID]; !exists {
22+
userMutexes[userID] = &sync.Mutex{}
23+
}
24+
return userMutexes[userID]
25+
}
26+
1227
func PushDestinations(c *fiber.Ctx) error {
1328
// TODO: figure out what the hell this is supposed to do to make notifications not crash.
1429
old_udid := c.Query("old_udid")
@@ -87,6 +102,17 @@ func GetSettings(c *fiber.Ctx) error {
87102
}
88103

89104
func UpdateProfile(c *fiber.Ctx) error {
105+
// auth
106+
my_did, pds, _, oauthToken, err := GetAuthFromReq(c)
107+
if err != nil {
108+
return c.Status(fiber.StatusUnauthorized).SendString("OAuth token not found in Authorization header")
109+
}
110+
111+
// Lock the mutex for this user
112+
userMutex := getUserMutex(*my_did)
113+
userMutex.Lock()
114+
defer userMutex.Unlock()
115+
90116
description := c.FormValue("description")
91117
name := c.FormValue("name")
92118
// These don't exist in bluesky.
@@ -100,20 +126,87 @@ func UpdateProfile(c *fiber.Ctx) error {
100126
// some quality of life features
101127
description = strings.ReplaceAll(description, "\\n", "\n")
102128

129+
oldProfile, err := blueskyapi.GetRecord(*pds, "app.bsky.actor.profile", *my_did, "self")
130+
if err != nil {
131+
fmt.Println("Error:", err)
132+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to get profile")
133+
}
134+
135+
oldProfile.Value.DisplayName = name
136+
oldProfile.Value.Description = description
137+
138+
if err := blueskyapi.UpdateRecord(*pds, *oauthToken, "app.bsky.actor.profile", *my_did, "self", oldProfile.CID, oldProfile.Value); err != nil {
139+
fmt.Println("Error:", err)
140+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to update profile")
141+
}
142+
143+
user, err := blueskyapi.GetUserInfo(*pds, *oauthToken, *my_did, true)
144+
if err != nil {
145+
fmt.Println("Error:", err)
146+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to fetch user info")
147+
}
148+
149+
user.Description = description
150+
user.Name = name
151+
152+
xml, err := bridge.XMLEncoder(user, "TwitterUser", "user")
153+
if err != nil {
154+
fmt.Println("Error:", err)
155+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to encode user info")
156+
}
157+
158+
return c.SendString(*xml)
159+
}
160+
161+
func UpdateProfilePicture(c *fiber.Ctx) error {
103162
// auth
104163
my_did, pds, _, oauthToken, err := GetAuthFromReq(c)
105164
if err != nil {
106165
return c.Status(fiber.StatusUnauthorized).SendString("OAuth token not found in Authorization header")
107166
}
108167

168+
// Lock the mutex for this user
169+
userMutex := getUserMutex(*my_did)
170+
userMutex.Lock()
171+
defer userMutex.Unlock()
172+
173+
// get the old profile
109174
oldProfile, err := blueskyapi.GetRecord(*pds, "app.bsky.actor.profile", *my_did, "self")
110175
if err != nil {
111176
fmt.Println("Error:", err)
112177
return c.Status(fiber.StatusInternalServerError).SendString("Failed to get profile")
113178
}
114179

115-
oldProfile.Value.DisplayName = name
116-
oldProfile.Value.Description = description
180+
// get our new image
181+
image, err := c.FormFile("image")
182+
if err != nil {
183+
fmt.Println("Error:", err)
184+
return c.Status(fiber.StatusBadRequest).SendString("Please upload an image")
185+
}
186+
187+
// read the image file content
188+
file, err := image.Open()
189+
if err != nil {
190+
fmt.Println("Error:", err)
191+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to open image file")
192+
}
193+
defer file.Close()
194+
195+
imageData, err := io.ReadAll(file)
196+
if err != nil {
197+
fmt.Println("Error:", err)
198+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to read image file")
199+
}
200+
201+
// upload our new profile picture
202+
profilePictureBlob, err := blueskyapi.UploadBlob(*pds, *oauthToken, imageData, c.Get("Content-Type"))
203+
if err != nil {
204+
fmt.Println("Error:", err)
205+
return c.Status(fiber.StatusInternalServerError).SendString("Failed to upload profile picture")
206+
}
207+
208+
// change our thing
209+
oldProfile.Value.Avatar = *profilePictureBlob
117210

118211
if err := blueskyapi.UpdateRecord(*pds, *oauthToken, "app.bsky.actor.profile", *my_did, "self", oldProfile.CID, oldProfile.Value); err != nil {
119212
fmt.Println("Error:", err)
@@ -126,8 +219,7 @@ func UpdateProfile(c *fiber.Ctx) error {
126219
return c.Status(fiber.StatusInternalServerError).SendString("Failed to fetch user info")
127220
}
128221

129-
user.Description = description
130-
user.Name = name
222+
// ...
131223

132224
xml, err := bridge.XMLEncoder(user, "TwitterUser", "user")
133225
if err != nil {

twitterv1/twitterv1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func InitServer(config *config.Config) {
8585

8686
// Account / Settings
8787
app.Post("/1/account/update_profile.xml", UpdateProfile)
88+
app.Post("/1/account/update_profile_image.xml", UpdateProfilePicture)
8889
app.Get("/1/account/settings.xml", GetSettings)
8990
app.Get("/1/account/push_destinations/device.xml", PushDestinations)
9091

0 commit comments

Comments
 (0)