From a826f1918eb73d611b5b81a8546a3d76ecb8c932 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Thu, 4 May 2023 23:46:31 -0500 Subject: [PATCH] Use Discord avatars as HMN avatars --- src/discord/message_handling.go | 9 ++++--- src/website/discord.go | 45 ++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/discord/message_handling.go b/src/discord/message_handling.go index cfd9df1..6cbc2da 100644 --- a/src/discord/message_handling.go +++ b/src/discord/message_handling.go @@ -431,7 +431,7 @@ var discordDownloadClient = &http.Client{ type DiscordResourceBadStatusCode error -func downloadDiscordResource(ctx context.Context, url string) ([]byte, string, error) { +func DownloadDiscordResource(ctx context.Context, url string) ([]byte, string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, "", oops.New(err, "failed to make Discord download request") @@ -491,7 +491,7 @@ func saveAttachment( height = *attachment.Height } - content, _, err := downloadDiscordResource(ctx, attachment.Url) + content, _, err := DownloadDiscordResource(ctx, attachment.Url) if err != nil { return nil, oops.New(err, "failed to download Discord attachment") } @@ -561,7 +561,7 @@ func saveEmbed( } maybeSaveImageish := func(i EmbedImageish, contentTypeCheck func(string) bool) (*uuid.UUID, error) { - content, contentType, err := downloadDiscordResource(ctx, *i.Url) + content, contentType, err := DownloadDiscordResource(ctx, *i.Url) if err != nil { var statusError DiscordResourceBadStatusCode if errors.As(err, &statusError) { @@ -838,7 +838,8 @@ func HandleSnippetForInternedMessage(ctx context.Context, dbConn db.ConnOrTx, in } // TODO(asaf): I believe this will also match https://example.com?hello=1&whatever=5 -// Probably need to add word boundaries. +// +// Probably need to add word boundaries. var REDiscordTag = regexp.MustCompile(`&([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)`) func getDiscordTags(content string) []string { diff --git a/src/website/discord.go b/src/website/discord.go index 7abd9d5..1ecab4c 100644 --- a/src/website/discord.go +++ b/src/website/discord.go @@ -1,12 +1,14 @@ package website import ( + "context" "errors" "fmt" "net/http" "strings" "time" + "git.handmade.network/hmn/hmn/src/assets" "git.handmade.network/hmn/hmn/src/config" "git.handmade.network/hmn/hmn/src/db" "git.handmade.network/hmn/hmn/src/discord" @@ -14,6 +16,7 @@ import ( "git.handmade.network/hmn/hmn/src/models" "git.handmade.network/hmn/hmn/src/oops" "git.handmade.network/hmn/hmn/src/utils" + "github.com/google/uuid" ) // This callback handles Discord account linking whether the user is signed in @@ -194,16 +197,26 @@ func DiscordOAuthCallback(c *RequestContext) ResponseData { return c.RejectRequest(fmt.Sprintf("There is already a Handmade Network account with the username \"%s\".", user.Username)) } + var avatarAssetID *uuid.UUID + if user.Avatar != nil { + // Note! Not using the transaction here. Don't want to fail the login due to avatars. + if avatarAsset, err := saveDiscordAvatar(c, c.Conn, user.ID, *user.Avatar); err == nil { + avatarAssetID = &avatarAsset.ID + } else { + c.Logger.Warn().Err(err).Msg("failed to save Discord avatar") + } + } + newHMNUser, err := db.QueryOne[models.User](c, tx, ` INSERT INTO hmn_user ( - username, email, password, date_joined, registration_ip + username, email, password, avatar_asset_id, date_joined, registration_ip ) VALUES ( - $1, $2, '', $3, $4 + $1, $2, '', $3, $4, $5 ) RETURNING $columns `, - user.Username, strings.ToLower(user.Email), time.Now(), c.GetIP(), + user.Username, strings.ToLower(user.Email), avatarAssetID, time.Now(), c.GetIP(), ) if err != nil { return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to create new HMN user for Discord login")) @@ -366,3 +379,29 @@ func DiscordShowcaseBacklog(c *RequestContext) ResponseData { return c.Redirect(hmnurl.BuildUserProfile(c.CurrentUser.Username), http.StatusSeeOther) } + +func saveDiscordAvatar(ctx context.Context, conn db.ConnOrTx, userID, avatarHash string) (*models.Asset, error) { + const size = 256 + + filename := fmt.Sprintf("%s.png", avatarHash) + url := fmt.Sprintf("https://cdn.discordapp.com/avatars/%s/%s?size=%d", userID, filename, size) + + content, _, err := discord.DownloadDiscordResource(ctx, url) + if err != nil { + return nil, oops.New(err, "failed to download Discord avatar") + } + + asset, err := assets.Create(ctx, conn, assets.CreateInput{ + Content: content, + Filename: filename, + ContentType: "image/png", + + Width: size, + Height: size, + }) + if err != nil { + return nil, oops.New(err, "failed to save asset for Discord attachment") + } + + return asset, nil +}