mirror of
				https://github.com/owncast/owncast.git
				synced 2025-11-04 05:17:27 +08:00 
			
		
		
		
	* update message viz in db * create admin endpoint to update message visibility * convert UpdateMessageVisibility api to take in an array of IDs to change visibility on instead * Support requesting filtered or unfiltered chat messages * Handle UPDATE chat events on front and backend for toggling messages * Return entire message with UPDATE events * Remove the UPDATE message type * Revert "Remove the UPDATE message type" This reverts commit 3a83df3d492f7ecf2bab65e845aa2b0365d3a7f6. * update -> visibility update * completely remove messages when they turn hidden on VISIBILITY-UPDATEs, and insert them if they turn visible * Explicitly set visibility * Fix multi-id sql updates * increate scroll buffer a bit so chat scrolls when new large messages come in * Add automated test around chat moderation * Add new chat admin APIs to api spec * Commit updated API documentation Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Owncast <owncast@owncast.online>
		
			
				
	
	
		
			116 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package models
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/microcosm-cc/bluemonday"
 | 
						|
	"github.com/yuin/goldmark"
 | 
						|
	"github.com/yuin/goldmark/extension"
 | 
						|
	"github.com/yuin/goldmark/renderer/html"
 | 
						|
	"mvdan.cc/xurls"
 | 
						|
)
 | 
						|
 | 
						|
// ChatEvent represents a single chat message.
 | 
						|
type ChatEvent struct {
 | 
						|
	ClientID string `json:"-"`
 | 
						|
 | 
						|
	Author      string    `json:"author,omitempty"`
 | 
						|
	Body        string    `json:"body,omitempty"`
 | 
						|
	ID          string    `json:"id"`
 | 
						|
	MessageType string    `json:"type"`
 | 
						|
	Visible     bool      `json:"visible"`
 | 
						|
	Timestamp   time.Time `json:"timestamp,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// Valid checks to ensure the message is valid.
 | 
						|
func (m ChatEvent) Valid() bool {
 | 
						|
	return m.Author != "" && m.Body != "" && m.ID != ""
 | 
						|
}
 | 
						|
 | 
						|
// RenderAndSanitizeMessageBody will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
 | 
						|
// the message into something safe and renderable for clients.
 | 
						|
func (m *ChatEvent) RenderAndSanitizeMessageBody() {
 | 
						|
	raw := m.Body
 | 
						|
 | 
						|
	// Set the new, sanitized and rendered message body
 | 
						|
	m.Body = RenderAndSanitize(raw)
 | 
						|
}
 | 
						|
 | 
						|
// RenderAndSanitize will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
 | 
						|
// the message into something safe and renderable for clients.
 | 
						|
func RenderAndSanitize(raw string) string {
 | 
						|
	rendered := renderMarkdown(raw)
 | 
						|
	safe := sanitize(rendered)
 | 
						|
 | 
						|
	// Set the new, sanitized and rendered message body
 | 
						|
	return strings.TrimSpace(safe)
 | 
						|
}
 | 
						|
 | 
						|
func renderMarkdown(raw string) string {
 | 
						|
	markdown := goldmark.New(
 | 
						|
		goldmark.WithRendererOptions(
 | 
						|
			html.WithUnsafe(),
 | 
						|
		),
 | 
						|
		goldmark.WithExtensions(
 | 
						|
			extension.NewLinkify(
 | 
						|
				extension.WithLinkifyAllowedProtocols([][]byte{
 | 
						|
					[]byte("http:"),
 | 
						|
					[]byte("https:"),
 | 
						|
				}),
 | 
						|
				extension.WithLinkifyURLRegexp(
 | 
						|
					xurls.Strict,
 | 
						|
				),
 | 
						|
			),
 | 
						|
		),
 | 
						|
	)
 | 
						|
 | 
						|
	trimmed := strings.TrimSpace(raw)
 | 
						|
	var buf bytes.Buffer
 | 
						|
	if err := markdown.Convert([]byte(trimmed), &buf); err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return buf.String()
 | 
						|
}
 | 
						|
 | 
						|
func sanitize(raw string) string {
 | 
						|
	p := bluemonday.StrictPolicy()
 | 
						|
 | 
						|
	// Require URLs to be parseable by net/url.Parse
 | 
						|
	p.AllowStandardURLs()
 | 
						|
 | 
						|
	// Allow links
 | 
						|
	p.AllowAttrs("href").OnElements("a")
 | 
						|
 | 
						|
	// Force all URLs to have "noreferrer" in their rel attribute.
 | 
						|
	p.RequireNoReferrerOnLinks(true)
 | 
						|
 | 
						|
	// Links will get target="_blank" added to them.
 | 
						|
	p.AddTargetBlankToFullyQualifiedLinks(true)
 | 
						|
 | 
						|
	// Allow paragraphs
 | 
						|
	p.AllowElements("br")
 | 
						|
	p.AllowElements("p")
 | 
						|
 | 
						|
	// Allow img tags
 | 
						|
	p.AllowElements("img")
 | 
						|
	p.AllowAttrs("src").OnElements("img")
 | 
						|
	p.AllowAttrs("alt").OnElements("img")
 | 
						|
	p.AllowAttrs("title").OnElements("img")
 | 
						|
 | 
						|
	// Custom emoji have a class already specified.
 | 
						|
	// We should only allow classes on emoji, not *all* imgs.
 | 
						|
	// But TODO.
 | 
						|
	p.AllowAttrs("class").OnElements("img")
 | 
						|
 | 
						|
	// Allow bold
 | 
						|
	p.AllowElements("strong")
 | 
						|
 | 
						|
	// Allow emphasis
 | 
						|
	p.AllowElements("em")
 | 
						|
 | 
						|
	return p.Sanitize(raw)
 | 
						|
}
 |