diff --git a/jobs/avatars/avatars.go b/jobs/avatars/avatars.go index 3afc7991..54e5e49e 100644 --- a/jobs/avatars/avatars.go +++ b/jobs/avatars/avatars.go @@ -1,18 +1,21 @@ package main import ( - "io/ioutil" "os" "runtime" "time" "github.com/animenotifier/arn" - "github.com/fatih/color" - "github.com/parnurzeal/gorequest" - gravatar "github.com/ungerik/go-gravatar" +) + +const ( + avatarsDirectoryOriginal = "images/avatars/original/" + avatarsDirectoryWebP = "images/avatars/webp/" ) func main() { + os.Chdir("../../") + users, _ := arn.AllUsers() usersQueue := make(chan *arn.User) @@ -23,6 +26,7 @@ func main() { go func(workerID int) { for user := range usersQueue { <-rateLimiter.C + if downloadAvatar(user) { makeWebPAvatar(user) user.Avatar = "/+" + user.Nick + "/avatar" @@ -39,90 +43,3 @@ func main() { usersQueue <- user } } - -func findAvatar(user *arn.User, dir string) string { - testExtensions := []string{".jpg", ".png", ".gif", ".webp", ""} - - for _, testExt := range testExtensions { - if _, err := os.Stat(dir + user.ID + testExt); !os.IsNotExist(err) { - return user.ID + testExt - } - } - - return "" -} - -func makeWebPAvatar(user *arn.User) { - baseName := findAvatar(user, "../../images/avatars/original/") - - if baseName == "" { - return - } - - original := "../../images/avatars/original/" + baseName - outFile := "../../images/avatars/webp/" + user.ID + ".webp" - - err := convertFileToWebP(original, outFile, 80) - - if err != nil { - color.Red("[WebP] " + original + " -> " + outFile) - } else { - color.Green("[WebP] " + original + " -> " + outFile) - } -} - -func downloadAvatar(user *arn.User) bool { - if user.Email == "" { - return false - } - - directory := "../../images/avatars/original/" - fileName := directory + user.ID - - // Build URL - url := gravatar.Url(user.Email) + "?s=560&d=404&r=pg" - - // Skip existing avatars - if findAvatar(user, directory) != "" { - color.Yellow(url) - return true - } - - // Download - response, data, err := gorequest.New().Get(url).EndBytes() - - if err != nil { - color.Red(url) - return false - } - - contentType := response.Header.Get("content-type") - - if response.StatusCode != 200 { - color.Red(url) - return false - } - - color.Green(url) - - // Determine file extension - extension := "" - - switch contentType { - case "image/jpeg": - extension = ".jpg" - case "image/png": - extension = ".png" - case "image/gif": - extension = ".gif" - case "image/webp": - extension = ".webp" - } - - fileName += extension - - // Write to disk - ioutil.WriteFile(fileName, data, 0644) - - return true -} diff --git a/jobs/avatars/download.go b/jobs/avatars/download.go new file mode 100644 index 00000000..b8023964 --- /dev/null +++ b/jobs/avatars/download.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/animenotifier/arn" + "github.com/fatih/color" + "github.com/parnurzeal/gorequest" + gravatar "github.com/ungerik/go-gravatar" +) + +func findOriginalAvatar(user *arn.User) string { + return arn.FindFileWithExtension( + user.ID, + avatarsDirectoryOriginal, + []string{ + ".jpg", + ".png", + ".gif", + }, + ) +} + +func downloadAvatar(user *arn.User) bool { + if user.Email == "" { + return false + } + + directory := avatarsDirectoryOriginal + fileName := directory + user.ID + + // Build URL + url := gravatar.Url(user.Email) + "?s=" + fmt.Sprint(arn.AvatarMaxSize) + "&d=404&r=pg" + + // Skip existing avatars + if findOriginalAvatar(user) != "" { + color.Yellow(user.Nick) + return true + } + + // Download + response, data, err := gorequest.New().Get(url).EndBytes() + + if err != nil { + color.Red(user.Nick) + return false + } + + contentType := response.Header.Get("content-type") + + if response.StatusCode != 200 { + color.Red(user.Nick) + return false + } + + // Determine file extension + extension := "" + + switch contentType { + case "image/jpeg": + extension = ".jpg" + case "image/png": + extension = ".png" + case "image/gif": + extension = ".gif" + case "image/webp": + extension = ".webp" + } + + fileName += extension + + // Write to disk + ioutil.WriteFile(fileName, data, 0644) + + color.Green(user.Nick) + + return true +} diff --git a/jobs/avatars/webp.go b/jobs/avatars/webp.go index 7706c0ca..090f0a05 100644 --- a/jobs/avatars/webp.go +++ b/jobs/avatars/webp.go @@ -1,40 +1,51 @@ package main import ( - "fmt" - "image" _ "image/gif" _ "image/jpeg" _ "image/png" - "os" + "strings" - "github.com/chai2010/webp" + "github.com/animenotifier/arn" + "github.com/fatih/color" + "github.com/nfnt/resize" ) -func convertFileToWebP(in string, out string, quality float32) error { - f, openErr := os.Open(in) +func makeWebPAvatar(user *arn.User) { + baseName := findOriginalAvatar(user) - if openErr != nil { - return openErr + if baseName == "" { + return } - img, format, decodeErr := image.Decode(f) + original := avatarsDirectoryOriginal + baseName + outFile := avatarsDirectoryWebP + user.ID + ".webp" - if decodeErr != nil { - return decodeErr + err := avatarToWebP(original, outFile, 80) + + if err != nil { + color.Red(user.Nick + " [WebP]") + } else { + color.Green(user.Nick + " [WebP]") } - - fmt.Println(format, img.Bounds().Dx(), img.Bounds().Dy()) - - fileOut, writeErr := os.Create(out) - - if writeErr != nil { - return writeErr - } - - encodeErr := webp.Encode(fileOut, img, &webp.Options{ - Quality: quality, - }) - - return encodeErr +} + +func avatarToWebP(in string, out string, quality float32) error { + img, _, loadErr := arn.LoadImage(in) + + if loadErr != nil { + return loadErr + } + + // Big avatar + saveErr := arn.SaveWebP(img, out, quality) + + if saveErr != nil { + return saveErr + } + + // Small avatar + smallImg := resize.Resize(arn.AvatarSmallSize, 0, img, resize.Lanczos3) + saveErr = arn.SaveWebP(smallImg, strings.Replace(out, ".webp", ".small.webp", 1), quality) + return saveErr } diff --git a/main.go b/main.go index 261aa3a0..f3f0bb27 100644 --- a/main.go +++ b/main.go @@ -96,6 +96,23 @@ func main() { return ctx.Error(http.StatusBadRequest, err.Error(), err) }) + // Avatars + app.Get("/user/:nick/avatar/small", func(ctx *aero.Context) string { + nick := ctx.Get("nick") + user, err := arn.GetUserByNick(nick) + + if err != nil { + return ctx.Error(http.StatusNotFound, "User not found", err) + } + + if ctx.CanUseWebP() { + return ctx.File("images/avatars/webp/" + user.ID + ".small.webp") + } + + err = errors.New("Your browser doesn't support the WebP image format") + return ctx.Error(http.StatusBadRequest, err.Error(), err) + }) + // Elements app.Get("/images/elements/:file", func(ctx *aero.Context) string { return ctx.File("images/elements/" + ctx.Get("file")) diff --git a/mixins/Avatar.pixy b/mixins/Avatar.pixy index bfbe8b56..845f9f5e 100644 --- a/mixins/Avatar.pixy +++ b/mixins/Avatar.pixy @@ -4,15 +4,12 @@ component Avatar(user *arn.User) component AvatarNoLink(user *arn.User) if user.Avatar != "" - if strings.Contains(user.Avatar, "gravatar.com") - img.user-image(src=user.Avatar + "?s=100&r=x&d=mm", alt=user.Nick) - else - img.user-image(src=user.Avatar, alt=user.Nick) + img.user-image(src=user.Avatar + "/small", alt=user.Nick) else - SVGAvatar(50) + SVGAvatar -component SVGAvatar(size int) - svg.user-image(width=size, height=size, viewBox="0 0 50 50") +component SVGAvatar + svg.user-image(viewBox="0 0 50 50") circle.head(cx="25", cy="19", r="10") circle.body(cx="25", cy="50", r="20") //- text(x="25", y="44", text-anchor="middle") TODO \ No newline at end of file diff --git a/mixins/ProfileImage.pixy b/mixins/ProfileImage.pixy index d45811c4..eb6bae6e 100644 --- a/mixins/ProfileImage.pixy +++ b/mixins/ProfileImage.pixy @@ -1,7 +1,7 @@ component ProfileImage(user *arn.User) if user.Avatar != "" - img.profile-image(src=user.Avatar + "?s=560&r=x&d=mm", alt="Profile image") + img.profile-image(src=user.Avatar, alt="Profile image") else - svg.profile-image(width=280, height=280, viewBox="0 0 50 50", alt="Profile image") + svg.profile-image(viewBox="0 0 50 50", alt="Profile image") circle.head(cx="25", cy="19", r="10") circle.body(cx="25", cy="50", r="20") \ No newline at end of file diff --git a/mixins/ThreadLink.pixy b/mixins/ThreadLink.pixy new file mode 100644 index 00000000..f57a6f8c --- /dev/null +++ b/mixins/ThreadLink.pixy @@ -0,0 +1,13 @@ +component ThreadLink(thread *arn.Thread) + .thread-link(data-sticky=thread.Sticky) + .post-author.thread-author + Avatar(thread.Author()) + .thread-content-container + .thread-content + if thread.Sticky + Icon("thumb-tack") + a.thread-link-title.ajax(href="/threads/" + thread.ID)= thread.Title + .spacer + .thread-reply-count= thread.Replies + .thread-icons + Icon(arn.GetForumIcon(thread.Tags[0])) \ No newline at end of file diff --git a/pages/forum/forum.pixy b/pages/forum/forum.pixy index 93483807..4e51b24b 100644 --- a/pages/forum/forum.pixy +++ b/pages/forum/forum.pixy @@ -7,18 +7,4 @@ component Forum(tag string, threads []*arn.Thread) component ThreadList(threads []*arn.Thread) each thread in threads - ThreadLink(thread) - -component ThreadLink(thread *arn.Thread) - .thread-link(data-sticky=thread.Sticky) - .post-author.thread-author - Avatar(thread.Author()) - .thread-content-container - .thread-content - if thread.Sticky - Icon("thumb-tack") - a.thread-link-title.ajax(href="/threads/" + thread.ID)= thread.Title - .spacer - .thread-reply-count= thread.Replies - .thread-icons - Icon(arn.GetForumIcon(thread.Tags[0])) \ No newline at end of file + ThreadLink(thread) \ No newline at end of file diff --git a/pages/profile/profile.pixy b/pages/profile/profile.pixy index 99cafa31..bfecca98 100644 --- a/pages/profile/profile.pixy +++ b/pages/profile/profile.pixy @@ -17,10 +17,10 @@ component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList, Icon("comment") span.tagline-text No tagline yet. - //- if user != nil && viewUser.website - //- p.profile-field.website - //- Icon("home") - //- a(href=viewUser.website.startsWith('http') ? viewUser.website : 'http://' + viewUser.website, target='_blank', rel='nofollow')= viewUser.website.replace('http://', '').replace('https://', '') + if viewUser.Website != "" + p.profile-field.website + Icon("home") + a(href=viewUser.WebsiteURL(), target="_blank", rel="nofollow")= viewUser.Website if viewUser.Accounts.Osu.Nick != "" && viewUser.Accounts.Osu.PP >= 1000 p.profile-field.osu(title="osu! performance points") diff --git a/pages/users/users.scarlet b/pages/users/users.scarlet index 54f094df..cb7c18ef 100644 --- a/pages/users/users.scarlet +++ b/pages/users/users.scarlet @@ -1,2 +1,7 @@ .user-avatars - horizontal-wrap \ No newline at end of file + horizontal-wrap + justify-content center + border-radius 3px + + .user-image + margin 0.4rem \ No newline at end of file diff --git a/styles/user.scarlet b/styles/user.scarlet index df03f050..5a39867f 100644 --- a/styles/user.scarlet +++ b/styles/user.scarlet @@ -1,4 +1,5 @@ .user-image + display block width avatar-size height avatar-size border-radius 100%