diff --git a/pkg/modules/avatar/avatar.go b/pkg/modules/avatar/avatar.go index aefb9d50f..321fb8553 100644 --- a/pkg/modules/avatar/avatar.go +++ b/pkg/modules/avatar/avatar.go @@ -18,6 +18,7 @@ package avatar import ( "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/modules/avatar/botmarble" "code.vikunja.io/api/pkg/modules/avatar/empty" "code.vikunja.io/api/pkg/modules/avatar/gravatar" "code.vikunja.io/api/pkg/modules/avatar/initials" @@ -47,6 +48,7 @@ func FlushAllCaches(u *user.User) { &ldap.Provider{}, &openid.Provider{}, &marble.Provider{}, + &botmarble.Provider{}, &empty.Provider{}, } for _, p := range providers { diff --git a/pkg/modules/avatar/botmarble/botmarble.go b/pkg/modules/avatar/botmarble/botmarble.go new file mode 100644 index 000000000..03e025ded --- /dev/null +++ b/pkg/modules/avatar/botmarble/botmarble.go @@ -0,0 +1,44 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package botmarble + +import ( + "code.vikunja.io/api/pkg/modules/avatar/marble" + "code.vikunja.io/api/pkg/user" +) + +// botColors is a cool-toned palette distinct from the marble default so bot avatars are visually recognizable as bots at a glance. +var botColors = []string{ + "#3B82F6", + "#06B6D4", + "#8B5CF6", + "#14B8A6", + "#6366F1", +} + +// Provider renders marble-style avatars using the bot-specific palette. +type Provider struct{} + +func (p *Provider) GetAvatar(u *user.User, size int64) ([]byte, string, error) { + return marble.GenerateSVG(u, size, botColors) +} + +func (p *Provider) AsDataURI(u *user.User, size int64) (string, error) { + return marble.GenerateDataURI(u, size, botColors) +} + +func (p *Provider) FlushCache(_ *user.User) error { return nil } diff --git a/pkg/modules/avatar/marble/marble.go b/pkg/modules/avatar/marble/marble.go index 2fcad6be6..9f7493253 100644 --- a/pkg/modules/avatar/marble/marble.go +++ b/pkg/modules/avatar/marble/marble.go @@ -34,7 +34,7 @@ func (p *Provider) FlushCache(_ *user.User) error { return nil } const avatarSize = 80 -var colors = []string{ +var defaultColors = []string{ "#A3A948", "#EDB92E", "#F85931", @@ -62,12 +62,12 @@ func getUnit(number int, rang, index int) int { return value } -func getPropsForUser(u *user.User) []*props { +func getPropsForUser(u *user.User, palette []string) []*props { ps := []*props{} - for i := 0; i < 3; i++ { + for i := range 3 { f := float64(getUnit(int(u.ID)*(i+1), avatarSize/10, 0)) ps = append(ps, &props{ - Color: colors[(int(u.ID)+i)%(len(colors)-1)], + Color: palette[(int(u.ID)+i)%len(palette)], TranslateX: getUnit(int(u.ID)*(i+1), avatarSize/10, 1), TranslateY: getUnit(int(u.ID)*(i+1), avatarSize/10, 2), Scale: 1.2 + f/10, @@ -78,13 +78,14 @@ func getPropsForUser(u *user.User) []*props { return ps } -func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) { +// GenerateSVG renders a marble-style SVG avatar for the given user using the provided palette. +func GenerateSVG(u *user.User, size int64, palette []string) (avatar []byte, mimeType string, err error) { s := strconv.FormatInt(size, 10) avatarSizeStr := strconv.Itoa(avatarSize) avatarSizeHalf := strconv.Itoa(avatarSize / 2) - ps := getPropsForUser(u) + ps := getPropsForUser(u, palette) return []byte(``), "image/svg+xml", nil } -// AsDataURI returns a data URI for the SVG avatar -func (p *Provider) AsDataURI(u *user.User, size int64) (string, error) { - avatarData, mimeType, err := p.GetAvatar(u, size) +// GenerateDataURI returns a base64-encoded data URI for a marble-style SVG avatar using the provided palette. +func GenerateDataURI(u *user.User, size int64, palette []string) (string, error) { + avatarData, mimeType, err := GenerateSVG(u, size, palette) if err != nil { return "", err } - // Encode the SVG as base64 and create a data URI base64Data := base64.StdEncoding.EncodeToString(avatarData) dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, base64Data) return dataURI, nil } + +func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) { + return GenerateSVG(u, size, defaultColors) +} + +// AsDataURI returns a data URI for the SVG avatar +func (p *Provider) AsDataURI(u *user.User, size int64) (string, error) { + return GenerateDataURI(u, size, defaultColors) +} diff --git a/pkg/routes/api/v1/avatar.go b/pkg/routes/api/v1/avatar.go index 5bddb31f5..d98b9dc32 100644 --- a/pkg/routes/api/v1/avatar.go +++ b/pkg/routes/api/v1/avatar.go @@ -22,6 +22,7 @@ import ( "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/modules/avatar" + "code.vikunja.io/api/pkg/modules/avatar/botmarble" "code.vikunja.io/api/pkg/modules/avatar/empty" "code.vikunja.io/api/pkg/modules/avatar/upload" "code.vikunja.io/api/pkg/user" @@ -68,6 +69,10 @@ func GetAvatar(c *echo.Context) error { avatarProvider = &empty.Provider{} } + if found && u.IsBot() { + avatarProvider = &botmarble.Provider{} + } + size := c.QueryParam("size") var sizeInt int64 = 250 // Default size of 250 if size != "" {