feat: add support for custom favicons (#4770)

* feat: add support for custom favicons

* Commit updated API documentation

* chore(i18n): add localization of text

* fix(js): regenerate package lock file

* chore(test): add favicon test

* fix: max size not respected

* Commit updated API documentation

* fix: move favicon.ico to the static dir

* fix: fix tests

* fix: remove hard-coded content-type of icon

* chore: update extracted translations

* feat(admin): add support for resetting to default icon

* chore: use a higher res default favicon

* Commit updated API documentation

---------

Co-authored-by: Owncast <owncast@owncast.online>
This commit is contained in:
Gabe Kangas
2026-01-28 18:07:19 -08:00
committed by GitHub
co-authored by Owncast
parent f8560db614
commit 6abaf20f2b
77 changed files with 981 additions and 177 deletions
@@ -8,6 +8,7 @@ import (
"time"
"github.com/oapi-codegen/runtime"
openapi_types "github.com/oapi-codegen/runtime/types"
)
const (
@@ -569,26 +570,29 @@ type Viewer struct {
// WebConfig defines model for WebConfig.
type WebConfig struct {
AppearanceVariables *map[string]string `json:"appearanceVariables,omitempty"`
Authentication *AuthenticationConfig `json:"authentication,omitempty"`
ChatDisabled *bool `json:"chatDisabled,omitempty"`
CustomStyles *string `json:"customStyles,omitempty"`
ExternalActions *[]ExternalAction `json:"externalActions,omitempty"`
ExtraPageContent *string `json:"extraPageContent,omitempty"`
Federation *FederationConfig `json:"federation,omitempty"`
HideViewerCount *bool `json:"hideViewerCount,omitempty"`
Logo *string `json:"logo,omitempty"`
MaxSocketPayloadSize *int `json:"maxSocketPayloadSize,omitempty"`
Name *string `json:"name,omitempty"`
Notifications *NotificationConfig `json:"notifications,omitempty"`
Nsfw *bool `json:"nsfw,omitempty"`
OfflineMessage *string `json:"offlineMessage,omitempty"`
SocialHandles *[]SocialHandle `json:"socialHandles,omitempty"`
SocketHostOverride *string `json:"socketHostOverride,omitempty"`
StreamTitle *string `json:"streamTitle,omitempty"`
Summary *string `json:"summary,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Version *string `json:"version,omitempty"`
AppearanceVariables *map[string]string `json:"appearanceVariables,omitempty"`
Authentication *AuthenticationConfig `json:"authentication,omitempty"`
ChatDisabled *bool `json:"chatDisabled,omitempty"`
// ChatRequireAuthentication Whether users must authenticate before sending chat messages
ChatRequireAuthentication *bool `json:"chatRequireAuthentication,omitempty"`
CustomStyles *string `json:"customStyles,omitempty"`
ExternalActions *[]ExternalAction `json:"externalActions,omitempty"`
ExtraPageContent *string `json:"extraPageContent,omitempty"`
Federation *FederationConfig `json:"federation,omitempty"`
HideViewerCount *bool `json:"hideViewerCount,omitempty"`
Logo *string `json:"logo,omitempty"`
MaxSocketPayloadSize *int `json:"maxSocketPayloadSize,omitempty"`
Name *string `json:"name,omitempty"`
Notifications *NotificationConfig `json:"notifications,omitempty"`
Nsfw *bool `json:"nsfw,omitempty"`
OfflineMessage *string `json:"offlineMessage,omitempty"`
SocialHandles *[]SocialHandle `json:"socialHandles,omitempty"`
SocketHostOverride *string `json:"socketHostOverride,omitempty"`
StreamTitle *string `json:"streamTitle,omitempty"`
Summary *string `json:"summary,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Version *string `json:"version,omitempty"`
}
// Webhook defines model for Webhook.
@@ -695,6 +699,12 @@ type SetExternalActionsJSONBody struct {
Value *[]ExternalAction `json:"value,omitempty"`
}
// SetFaviconMultipartBody defines parameters for SetFavicon.
type SetFaviconMultipartBody struct {
// Favicon Favicon file (PNG or ICO, max 200KB)
Favicon *openapi_types.File `json:"favicon,omitempty"`
}
// SetBrowserNotificationConfigurationJSONBody defines parameters for SetBrowserNotificationConfiguration.
type SetBrowserNotificationConfigurationJSONBody struct {
Value *BrowserNotificationConfiguration `json:"value,omitempty"`
@@ -945,6 +955,9 @@ type SetDisableSearchIndexingJSONRequestBody = AdminConfigValue
// SetExternalActionsJSONRequestBody defines body for SetExternalActions for application/json ContentType.
type SetExternalActionsJSONRequestBody SetExternalActionsJSONBody
// SetFaviconMultipartRequestBody defines body for SetFavicon for multipart/form-data ContentType.
type SetFaviconMultipartRequestBody SetFaviconMultipartBody
// SetFederationBlockDomainsJSONRequestBody defines body for SetFederationBlockDomains for application/json ContentType.
type SetFederationBlockDomainsJSONRequestBody = AdminConfigValue
@@ -182,6 +182,15 @@ type ServerInterface interface {
// Update external action links
// (POST /admin/config/externalactions)
SetExternalActions(w http.ResponseWriter, r *http.Request)
// Reset favicon to default
// (DELETE /admin/config/favicon)
ResetFavicon(w http.ResponseWriter, r *http.Request)
// (OPTIONS /admin/config/favicon)
SetFaviconOptions(w http.ResponseWriter, r *http.Request)
// Upload custom favicon
// (POST /admin/config/favicon)
SetFavicon(w http.ResponseWriter, r *http.Request)
// (OPTIONS /admin/config/federation/blockdomains)
SetFederationBlockDomainsOptions(w http.ResponseWriter, r *http.Request)
@@ -970,6 +979,23 @@ func (_ Unimplemented) SetExternalActions(w http.ResponseWriter, r *http.Request
w.WriteHeader(http.StatusNotImplemented)
}
// Reset favicon to default
// (DELETE /admin/config/favicon)
func (_ Unimplemented) ResetFavicon(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// (OPTIONS /admin/config/favicon)
func (_ Unimplemented) SetFaviconOptions(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// Upload custom favicon
// (POST /admin/config/favicon)
func (_ Unimplemented) SetFavicon(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}
// (OPTIONS /admin/config/federation/blockdomains)
func (_ Unimplemented) SetFederationBlockDomainsOptions(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
@@ -2758,6 +2784,57 @@ func (siw *ServerInterfaceWrapper) SetExternalActions(w http.ResponseWriter, r *
handler.ServeHTTP(w, r)
}
// ResetFavicon operation middleware
func (siw *ServerInterfaceWrapper) ResetFavicon(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, BasicAuthScopes, []string{})
r = r.WithContext(ctx)
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.ResetFavicon(w, r)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetFaviconOptions operation middleware
func (siw *ServerInterfaceWrapper) SetFaviconOptions(w http.ResponseWriter, r *http.Request) {
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.SetFaviconOptions(w, r)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetFavicon operation middleware
func (siw *ServerInterfaceWrapper) SetFavicon(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, BasicAuthScopes, []string{})
r = r.WithContext(ctx)
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.SetFavicon(w, r)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
// SetFederationBlockDomainsOptions operation middleware
func (siw *ServerInterfaceWrapper) SetFederationBlockDomainsOptions(w http.ResponseWriter, r *http.Request) {
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -5852,6 +5929,15 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl
r.Group(func(r chi.Router) {
r.Post(options.BaseURL+"/admin/config/externalactions", wrapper.SetExternalActions)
})
r.Group(func(r chi.Router) {
r.Delete(options.BaseURL+"/admin/config/favicon", wrapper.ResetFavicon)
})
r.Group(func(r chi.Router) {
r.Options(options.BaseURL+"/admin/config/favicon", wrapper.SetFaviconOptions)
})
r.Group(func(r chi.Router) {
r.Post(options.BaseURL+"/admin/config/favicon", wrapper.SetFavicon)
})
r.Group(func(r chi.Router) {
r.Options(options.BaseURL+"/admin/config/federation/blockdomains", wrapper.SetFederationBlockDomainsOptions)
})