mirror of
https://github.com/Alexander-D-Karpov/about.git
synced 2026-03-16 22:06:08 +03:00
339 lines
7.9 KiB
Go
339 lines
7.9 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"html/template"
|
|
"math/rand"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Alexander-D-Karpov/about/internal/storage"
|
|
"github.com/Alexander-D-Karpov/about/internal/stream"
|
|
)
|
|
|
|
type MemePlugin struct {
|
|
storage *storage.Storage
|
|
hub *stream.Hub
|
|
currentMeme *Meme
|
|
lastUpdate time.Time
|
|
rng *rand.Rand
|
|
shownMemes map[string]bool
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
type Meme struct {
|
|
Text string `json:"text"`
|
|
Image string `json:"image"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
Category string `json:"category"`
|
|
}
|
|
|
|
func NewMemePlugin(storage *storage.Storage, hub *stream.Hub) *MemePlugin {
|
|
source := rand.NewSource(time.Now().UnixNano())
|
|
plugin := &MemePlugin{
|
|
storage: storage,
|
|
hub: hub,
|
|
rng: rand.New(source),
|
|
shownMemes: make(map[string]bool),
|
|
}
|
|
|
|
plugin.selectRandomMeme()
|
|
plugin.lastUpdate = time.Now()
|
|
|
|
return plugin
|
|
}
|
|
|
|
func (p *MemePlugin) Name() string { return "meme" }
|
|
|
|
func (p *MemePlugin) Render(ctx context.Context) (string, error) {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
settings := config.Settings
|
|
|
|
showMeme := p.getConfigBool(settings, "ui.showMeme", true)
|
|
if !showMeme || p.currentMeme == nil {
|
|
return "", nil
|
|
}
|
|
|
|
sectionTitle := p.getConfigValue(settings, "ui.sectionTitle", "Random Meme")
|
|
|
|
tmpl := `
|
|
<section class="meme-section section plugin" id="meme-section" data-w="1">
|
|
<header class="plugin-header meme-header">
|
|
<h3 class="plugin-title">{{.SectionTitle}}</h3>
|
|
<button type="button" class="btn btn-sm meme-refresh-btn" onclick="refreshMeme()">🎲</button>
|
|
</header>
|
|
|
|
<div class="plugin__inner">
|
|
<div class="meme-content">
|
|
{{if eq .Meme.Type "image"}}
|
|
<div class="meme-image">
|
|
<img src="{{.Meme.Image}}" alt="{{.Meme.Text}}" loading="lazy">
|
|
{{if .Meme.Text}}<p class="meme-caption">{{.Meme.Text}}</p>{{end}}
|
|
</div>
|
|
{{else if eq .Meme.Type "gif"}}
|
|
<div class="meme-gif">
|
|
<img src="{{.Meme.Image}}" alt="{{.Meme.Text}}" loading="lazy">
|
|
{{if .Meme.Text}}<p class="meme-caption">{{.Meme.Text}}</p>{{end}}
|
|
</div>
|
|
{{else}}
|
|
<div class="meme-text">
|
|
<p class="meme-quote">{{.Meme.Text}}</p>
|
|
{{if .Meme.Source}}<p class="meme-source">— {{.Meme.Source}}</p>{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</section>`
|
|
|
|
data := struct {
|
|
SectionTitle string
|
|
Meme *Meme
|
|
}{
|
|
SectionTitle: sectionTitle,
|
|
Meme: p.currentMeme,
|
|
}
|
|
|
|
t, err := template.New("meme").Parse(tmpl)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf strings.Builder
|
|
if err := t.Execute(&buf, data); err != nil {
|
|
return "", err
|
|
}
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func (p *MemePlugin) UpdateData(ctx context.Context) error {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
settings := config.Settings
|
|
|
|
autoRefresh := p.getConfigBool(settings, "ui.autoRefresh", false)
|
|
refreshInterval := p.getConfigInt(settings, "ui.refreshInterval", 300)
|
|
|
|
if autoRefresh && time.Since(p.lastUpdate) > time.Duration(refreshInterval)*time.Second {
|
|
p.selectRandomMeme()
|
|
p.lastUpdate = time.Now()
|
|
|
|
if p.currentMeme != nil {
|
|
p.hub.Broadcast("meme_update", map[string]interface{}{
|
|
"meme": *p.currentMeme,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *MemePlugin) selectRandomMeme() {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
settings := config.Settings
|
|
|
|
memes, ok := settings["memes"].([]interface{})
|
|
if !ok || len(memes) == 0 {
|
|
memes = p.getDefaultMemes()
|
|
}
|
|
if len(memes) == 0 {
|
|
return
|
|
}
|
|
|
|
p.mutex.Lock()
|
|
defer p.mutex.Unlock()
|
|
|
|
unshownMemes := []interface{}{}
|
|
for _, meme := range memes {
|
|
memeMap, ok := meme.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
memeKey := fmt.Sprintf("%v-%v", memeMap["text"], memeMap["image"])
|
|
if !p.shownMemes[memeKey] {
|
|
unshownMemes = append(unshownMemes, meme)
|
|
}
|
|
}
|
|
|
|
if len(unshownMemes) == 0 {
|
|
p.shownMemes = make(map[string]bool)
|
|
unshownMemes = memes
|
|
}
|
|
|
|
memeIndex := p.rng.Intn(len(unshownMemes))
|
|
memeData := unshownMemes[memeIndex]
|
|
|
|
memeMap, ok := memeData.(map[string]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
memeKey := fmt.Sprintf("%v-%v", memeMap["text"], memeMap["image"])
|
|
p.shownMemes[memeKey] = true
|
|
|
|
p.currentMeme = &Meme{
|
|
Text: p.getStringFromMap(memeMap, "text", ""),
|
|
Image: p.getStringFromMap(memeMap, "image", ""),
|
|
Type: p.getStringFromMap(memeMap, "type", "image"),
|
|
Source: p.getStringFromMap(memeMap, "source", ""),
|
|
Category: p.getStringFromMap(memeMap, "category", "general"),
|
|
}
|
|
}
|
|
|
|
func (p *MemePlugin) RefreshMeme() *Meme {
|
|
p.selectRandomMeme()
|
|
p.lastUpdate = time.Now()
|
|
|
|
if p.currentMeme != nil {
|
|
p.hub.Broadcast("meme_update", map[string]interface{}{
|
|
"meme": *p.currentMeme,
|
|
"timestamp": time.Now().Unix(),
|
|
})
|
|
}
|
|
|
|
return p.currentMeme
|
|
}
|
|
|
|
func (p *MemePlugin) getDefaultMemes() []interface{} {
|
|
return []interface{}{
|
|
map[string]interface{}{"type": "image", "image": "/static/memes/test.webp", "text": "really cool", "category": "test"},
|
|
map[string]interface{}{"type": "image", "image": "/static/memes/test2.jpg", "text": "that says a lot about our society", "category": "test"},
|
|
}
|
|
}
|
|
|
|
func (p *MemePlugin) GetSettings() map[string]interface{} {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
return config.Settings
|
|
}
|
|
|
|
func (p *MemePlugin) SetSettings(settings map[string]interface{}) error {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
config.Settings = settings
|
|
|
|
if err := p.storage.SetPluginConfig(p.Name(), config); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.selectRandomMeme()
|
|
|
|
p.hub.Broadcast("plugin_update", map[string]interface{}{
|
|
"plugin": p.Name(),
|
|
"action": "settings_changed",
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (p *MemePlugin) getConfigValue(settings map[string]interface{}, key string, defaultValue string) string {
|
|
keys := strings.Split(key, ".")
|
|
current := settings
|
|
for i, k := range keys {
|
|
if i == len(keys)-1 {
|
|
if value, ok := current[k].(string); ok {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
next, ok := current[k].(map[string]interface{})
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
current = next
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *MemePlugin) getConfigBool(settings map[string]interface{}, key string, defaultValue bool) bool {
|
|
keys := strings.Split(key, ".")
|
|
current := settings
|
|
for i, k := range keys {
|
|
if i == len(keys)-1 {
|
|
if value, ok := current[k].(bool); ok {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
}
|
|
next, ok := current[k].(map[string]interface{})
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
current = next
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *MemePlugin) getConfigInt(settings map[string]interface{}, key string, defaultValue int) int {
|
|
keys := strings.Split(key, ".")
|
|
current := settings
|
|
for i, k := range keys {
|
|
if i == len(keys)-1 {
|
|
if v, ok := current[k].(float64); ok {
|
|
return int(v)
|
|
}
|
|
if v, ok := current[k].(int); ok {
|
|
return v
|
|
}
|
|
return defaultValue
|
|
}
|
|
next, ok := current[k].(map[string]interface{})
|
|
if !ok {
|
|
return defaultValue
|
|
}
|
|
current = next
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *MemePlugin) getStringFromMap(m map[string]interface{}, key string, defaultValue string) string {
|
|
if v, ok := m[key].(string); ok {
|
|
return v
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func (p *MemePlugin) RenderText(ctx context.Context) (string, error) {
|
|
if p.currentMeme == nil {
|
|
return "Meme: No meme available", nil
|
|
}
|
|
|
|
memeText := p.currentMeme.Text
|
|
if memeText == "" {
|
|
memeText = "Image meme"
|
|
}
|
|
|
|
if len(memeText) > 50 {
|
|
memeText = memeText[:47] + "..."
|
|
}
|
|
|
|
return fmt.Sprintf("Meme: %s", memeText), nil
|
|
}
|
|
|
|
func (p *MemePlugin) GetCurrentMeme() *Meme {
|
|
return p.currentMeme
|
|
}
|
|
|
|
func (p *MemePlugin) GetMetrics() map[string]interface{} {
|
|
config := p.storage.GetPluginConfig(p.Name())
|
|
memes, ok := config.Settings["memes"].([]interface{})
|
|
|
|
metrics := map[string]interface{}{
|
|
"total_memes": 0,
|
|
"shown_memes": 0,
|
|
"has_current": 0,
|
|
}
|
|
|
|
if ok {
|
|
metrics["total_memes"] = len(memes)
|
|
}
|
|
|
|
p.mutex.RLock()
|
|
metrics["shown_memes"] = len(p.shownMemes)
|
|
if p.currentMeme != nil {
|
|
metrics["has_current"] = 1
|
|
}
|
|
p.mutex.RUnlock()
|
|
|
|
return metrics
|
|
}
|