mirror of
https://github.com/glanceapp/glance.git
synced 2025-02-16 18:32:26 +01:00
Add extension widget
This commit is contained in:
parent
b3be60acf6
commit
342ef90cbe
@ -36,6 +36,7 @@ var (
|
|||||||
TwitchChannelsTemplate = compileTemplate("twitch-channels.html", "widget-base.html")
|
TwitchChannelsTemplate = compileTemplate("twitch-channels.html", "widget-base.html")
|
||||||
RepositoryTemplate = compileTemplate("repository.html", "widget-base.html")
|
RepositoryTemplate = compileTemplate("repository.html", "widget-base.html")
|
||||||
SearchTemplate = compileTemplate("search.html", "widget-base.html")
|
SearchTemplate = compileTemplate("search.html", "widget-base.html")
|
||||||
|
ExtensionTemplate = compileTemplate("extension.html", "widget-base.html")
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalTemplateFunctions = template.FuncMap{
|
var globalTemplateFunctions = template.FuncMap{
|
||||||
|
5
internal/assets/templates/extension.html
Normal file
5
internal/assets/templates/extension.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
|
{{ define "widget-content" }}
|
||||||
|
{{ .Extension.Content }}
|
||||||
|
{{ end }}
|
97
internal/feed/extension.go
Normal file
97
internal/feed/extension.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package feed
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExtensionType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtensionContentHTML ExtensionType = iota
|
||||||
|
ExtensionContentUnknown = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var ExtensionStringToType = map[string]ExtensionType{
|
||||||
|
"html": ExtensionContentHTML,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExtensionHeaderTitle = "Widget-Title"
|
||||||
|
ExtensionHeaderContentType = "Widget-Content-Type"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExtensionRequestOptions struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Parameters map[string]string `yaml:"parameters"`
|
||||||
|
AllowHtml bool `yaml:"allow-potentially-dangerous-html"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Extension struct {
|
||||||
|
Title string
|
||||||
|
Content template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertExtensionContent(options ExtensionRequestOptions, content []byte, contentType ExtensionType) template.HTML {
|
||||||
|
switch contentType {
|
||||||
|
case ExtensionContentHTML:
|
||||||
|
if options.AllowHtml {
|
||||||
|
return template.HTML(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return template.HTML(html.EscapeString(string(content)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchExtension(options ExtensionRequestOptions) (Extension, error) {
|
||||||
|
request, _ := http.NewRequest("GET", options.URL, nil)
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
|
||||||
|
for key, value := range options.Parameters {
|
||||||
|
query.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
response, err := http.DefaultClient.Do(request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed fetching extension", "error", err, "url", options.URL)
|
||||||
|
return Extension{}, fmt.Errorf("%w: request failed: %w", ErrNoContent, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed reading response body of extension", "error", err, "url", options.URL)
|
||||||
|
return Extension{}, fmt.Errorf("%w: could not read body: %w", ErrNoContent, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := Extension{}
|
||||||
|
|
||||||
|
if response.Header.Get(ExtensionHeaderTitle) == "" {
|
||||||
|
extension.Title = "Extension"
|
||||||
|
} else {
|
||||||
|
extension.Title = response.Header.Get(ExtensionHeaderTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, ok := ExtensionStringToType[response.Header.Get(ExtensionHeaderContentType)]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
contentType = ExtensionContentUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
extension.Content = convertExtensionContent(options, body, contentType)
|
||||||
|
|
||||||
|
return extension, nil
|
||||||
|
}
|
59
internal/widget/extension.go
Normal file
59
internal/widget/extension.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package widget
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"html/template"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/glanceapp/glance/internal/assets"
|
||||||
|
"github.com/glanceapp/glance/internal/feed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Extension struct {
|
||||||
|
widgetBase `yaml:",inline"`
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Parameters map[string]string `yaml:"parameters"`
|
||||||
|
AllowHtml bool `yaml:"allow-potentially-dangerous-html"`
|
||||||
|
Extension feed.Extension `yaml:"-"`
|
||||||
|
cachedHTML template.HTML `yaml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *Extension) Initialize() error {
|
||||||
|
widget.withTitle("Extension").withCacheDuration(time.Minute * 30)
|
||||||
|
|
||||||
|
if widget.URL == "" {
|
||||||
|
return errors.New("no extension URL specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := url.Parse(widget.URL)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *Extension) Update(ctx context.Context) {
|
||||||
|
extension, err := feed.FetchExtension(feed.ExtensionRequestOptions{
|
||||||
|
URL: widget.URL,
|
||||||
|
Parameters: widget.Parameters,
|
||||||
|
AllowHtml: widget.AllowHtml,
|
||||||
|
})
|
||||||
|
|
||||||
|
widget.canContinueUpdateAfterHandlingErr(err)
|
||||||
|
|
||||||
|
widget.Extension = extension
|
||||||
|
|
||||||
|
if extension.Title != "" {
|
||||||
|
widget.Title = extension.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.cachedHTML = widget.render(widget, assets.ExtensionTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (widget *Extension) Render() template.HTML {
|
||||||
|
return widget.cachedHTML
|
||||||
|
}
|
@ -53,6 +53,8 @@ func New(widgetType string) (Widget, error) {
|
|||||||
return &Repository{}, nil
|
return &Repository{}, nil
|
||||||
case "search":
|
case "search":
|
||||||
return &Search{}, nil
|
return &Search{}, nil
|
||||||
|
case "extension":
|
||||||
|
return &Extension{}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown widget type: %s", widgetType)
|
return nil, fmt.Errorf("unknown widget type: %s", widgetType)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user