Avatar improvements
This commit is contained in:
parent
d3f49ad53d
commit
5d97a87152
@ -1,18 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/animenotifier/arn"
|
"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() {
|
func main() {
|
||||||
|
os.Chdir("../../")
|
||||||
|
|
||||||
users, _ := arn.AllUsers()
|
users, _ := arn.AllUsers()
|
||||||
|
|
||||||
usersQueue := make(chan *arn.User)
|
usersQueue := make(chan *arn.User)
|
||||||
@ -23,6 +26,7 @@ func main() {
|
|||||||
go func(workerID int) {
|
go func(workerID int) {
|
||||||
for user := range usersQueue {
|
for user := range usersQueue {
|
||||||
<-rateLimiter.C
|
<-rateLimiter.C
|
||||||
|
|
||||||
if downloadAvatar(user) {
|
if downloadAvatar(user) {
|
||||||
makeWebPAvatar(user)
|
makeWebPAvatar(user)
|
||||||
user.Avatar = "/+" + user.Nick + "/avatar"
|
user.Avatar = "/+" + user.Nick + "/avatar"
|
||||||
@ -39,90 +43,3 @@ func main() {
|
|||||||
usersQueue <- user
|
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
|
|
||||||
}
|
|
||||||
|
79
jobs/avatars/download.go
Normal file
79
jobs/avatars/download.go
Normal file
@ -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
|
||||||
|
}
|
@ -1,40 +1,51 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
_ "image/gif"
|
_ "image/gif"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
_ "image/png"
|
_ "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 {
|
func makeWebPAvatar(user *arn.User) {
|
||||||
f, openErr := os.Open(in)
|
baseName := findOriginalAvatar(user)
|
||||||
|
|
||||||
if openErr != nil {
|
if baseName == "" {
|
||||||
return openErr
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
img, format, decodeErr := image.Decode(f)
|
original := avatarsDirectoryOriginal + baseName
|
||||||
|
outFile := avatarsDirectoryWebP + user.ID + ".webp"
|
||||||
|
|
||||||
if decodeErr != nil {
|
err := avatarToWebP(original, outFile, 80)
|
||||||
return decodeErr
|
|
||||||
|
if err != nil {
|
||||||
|
color.Red(user.Nick + " [WebP]")
|
||||||
|
} else {
|
||||||
|
color.Green(user.Nick + " [WebP]")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
fmt.Println(format, img.Bounds().Dx(), img.Bounds().Dy())
|
|
||||||
|
func avatarToWebP(in string, out string, quality float32) error {
|
||||||
fileOut, writeErr := os.Create(out)
|
img, _, loadErr := arn.LoadImage(in)
|
||||||
|
|
||||||
if writeErr != nil {
|
if loadErr != nil {
|
||||||
return writeErr
|
return loadErr
|
||||||
}
|
}
|
||||||
|
|
||||||
encodeErr := webp.Encode(fileOut, img, &webp.Options{
|
// Big avatar
|
||||||
Quality: quality,
|
saveErr := arn.SaveWebP(img, out, quality)
|
||||||
})
|
|
||||||
|
if saveErr != nil {
|
||||||
return encodeErr
|
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
|
||||||
}
|
}
|
||||||
|
17
main.go
17
main.go
@ -96,6 +96,23 @@ func main() {
|
|||||||
return ctx.Error(http.StatusBadRequest, err.Error(), err)
|
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
|
// Elements
|
||||||
app.Get("/images/elements/:file", func(ctx *aero.Context) string {
|
app.Get("/images/elements/:file", func(ctx *aero.Context) string {
|
||||||
return ctx.File("images/elements/" + ctx.Get("file"))
|
return ctx.File("images/elements/" + ctx.Get("file"))
|
||||||
|
@ -4,15 +4,12 @@ component Avatar(user *arn.User)
|
|||||||
|
|
||||||
component AvatarNoLink(user *arn.User)
|
component AvatarNoLink(user *arn.User)
|
||||||
if user.Avatar != ""
|
if user.Avatar != ""
|
||||||
if strings.Contains(user.Avatar, "gravatar.com")
|
img.user-image(src=user.Avatar + "/small", alt=user.Nick)
|
||||||
img.user-image(src=user.Avatar + "?s=100&r=x&d=mm", alt=user.Nick)
|
|
||||||
else
|
else
|
||||||
img.user-image(src=user.Avatar, alt=user.Nick)
|
SVGAvatar
|
||||||
else
|
|
||||||
SVGAvatar(50)
|
|
||||||
|
|
||||||
component SVGAvatar(size int)
|
component SVGAvatar
|
||||||
svg.user-image(width=size, height=size, viewBox="0 0 50 50")
|
svg.user-image(viewBox="0 0 50 50")
|
||||||
circle.head(cx="25", cy="19", r="10")
|
circle.head(cx="25", cy="19", r="10")
|
||||||
circle.body(cx="25", cy="50", r="20")
|
circle.body(cx="25", cy="50", r="20")
|
||||||
//- text(x="25", y="44", text-anchor="middle") TODO
|
//- text(x="25", y="44", text-anchor="middle") TODO
|
@ -1,7 +1,7 @@
|
|||||||
component ProfileImage(user *arn.User)
|
component ProfileImage(user *arn.User)
|
||||||
if user.Avatar != ""
|
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
|
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.head(cx="25", cy="19", r="10")
|
||||||
circle.body(cx="25", cy="50", r="20")
|
circle.body(cx="25", cy="50", r="20")
|
13
mixins/ThreadLink.pixy
Normal file
13
mixins/ThreadLink.pixy
Normal file
@ -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]))
|
@ -8,17 +8,3 @@ component Forum(tag string, threads []*arn.Thread)
|
|||||||
component ThreadList(threads []*arn.Thread)
|
component ThreadList(threads []*arn.Thread)
|
||||||
each thread in threads
|
each thread in threads
|
||||||
ThreadLink(thread)
|
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]))
|
|
@ -17,10 +17,10 @@ component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList,
|
|||||||
Icon("comment")
|
Icon("comment")
|
||||||
span.tagline-text No tagline yet.
|
span.tagline-text No tagline yet.
|
||||||
|
|
||||||
//- if user != nil && viewUser.website
|
if viewUser.Website != ""
|
||||||
//- p.profile-field.website
|
p.profile-field.website
|
||||||
//- Icon("home")
|
Icon("home")
|
||||||
//- a(href=viewUser.website.startsWith('http') ? viewUser.website : 'http://' + viewUser.website, target='_blank', rel='nofollow')= viewUser.website.replace('http://', '').replace('https://', '')
|
a(href=viewUser.WebsiteURL(), target="_blank", rel="nofollow")= viewUser.Website
|
||||||
|
|
||||||
if viewUser.Accounts.Osu.Nick != "" && viewUser.Accounts.Osu.PP >= 1000
|
if viewUser.Accounts.Osu.Nick != "" && viewUser.Accounts.Osu.PP >= 1000
|
||||||
p.profile-field.osu(title="osu! performance points")
|
p.profile-field.osu(title="osu! performance points")
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
.user-avatars
|
.user-avatars
|
||||||
horizontal-wrap
|
horizontal-wrap
|
||||||
|
justify-content center
|
||||||
|
border-radius 3px
|
||||||
|
|
||||||
|
.user-image
|
||||||
|
margin 0.4rem
|
@ -1,4 +1,5 @@
|
|||||||
.user-image
|
.user-image
|
||||||
|
display block
|
||||||
width avatar-size
|
width avatar-size
|
||||||
height avatar-size
|
height avatar-size
|
||||||
border-radius 100%
|
border-radius 100%
|
||||||
|
Loading…
Reference in New Issue
Block a user