feat(chat): add websocket to update messages
This commit is contained in:
parent
cad4942e66
commit
5d16a83723
12 changed files with 148 additions and 68 deletions
|
|
@ -1,40 +1,39 @@
|
|||
const isChat = () => {
|
||||
return document.querySelector('#chat') !== null
|
||||
return getChat() !== null
|
||||
}
|
||||
|
||||
const getChat = () => {
|
||||
return document.querySelector('#chat')
|
||||
}
|
||||
|
||||
const updateChat = () => {
|
||||
fetch(document.location.href)
|
||||
.then((response) => response.text())
|
||||
.then((html) => {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(html, 'text/html')
|
||||
const nextMessages = doc.querySelectorAll('.message')
|
||||
const createWebSocketConnection = () => {
|
||||
const protocol = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
|
||||
const currentMessages = getChat().querySelectorAll('.message')
|
||||
const messagesContainer = getChat().querySelector('.messages')
|
||||
return new WebSocket(`${protocol}://${window.location.hostname}:${window.location.port}/ws/chat/messages`)
|
||||
}
|
||||
|
||||
nextMessages.forEach((nextMessage) => {
|
||||
let add = true
|
||||
|
||||
currentMessages.forEach((currentMessage) => {
|
||||
if (currentMessage.id === nextMessage.id) {
|
||||
add = false
|
||||
const runChat = () => {
|
||||
const ws = createWebSocketConnection()
|
||||
|
||||
ws.addEventListener('close', runChat)
|
||||
|
||||
ws.addEventListener('message', (event) => {
|
||||
const items = JSON.parse(event.data)
|
||||
|
||||
if (items == null || !items.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const container = getChat().querySelector('.messages')
|
||||
|
||||
items.forEach((item) => {
|
||||
const message = document.createElement('div')
|
||||
message.innerHTML = item
|
||||
|
||||
container.appendChild(message)
|
||||
})
|
||||
|
||||
if (add) {
|
||||
messagesContainer.appendChild(nextMessage)
|
||||
}
|
||||
})
|
||||
|
||||
messagesContainer.scrollTo(0, messagesContainer.scrollHeight)
|
||||
|
||||
window.setTimeout(updateChat, 500)
|
||||
})
|
||||
}
|
||||
|
||||
export {isChat, updateChat}
|
||||
export {isChat, runChat}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import '../scss/main.scss'
|
||||
import {isChat, updateChat} from './lib/chat.js'
|
||||
import {isChat, runChat} from './lib/chat.js'
|
||||
|
||||
if (isChat()) {
|
||||
updateChat()
|
||||
runChat()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import "~animate.css/animate.css";
|
||||
|
||||
:root {
|
||||
--color-owncast-user-0: #ff717b;
|
||||
--color-owncast-user-1: #f4e413;
|
||||
|
|
@ -22,13 +24,16 @@
|
|||
background: transparent;
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
height: calc(100vh - 10px);
|
||||
/* border: 5px solid #254779; */
|
||||
|
||||
.messages {
|
||||
max-height: calc(100vh - 20px);
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-self: end;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
width: 100%;
|
||||
|
||||
.message {
|
||||
background: #000000cc;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ sn=owncastwh
|
|||
st="Owncast Webhook"
|
||||
|
||||
while true; do
|
||||
screen -X -S "$sn" quit
|
||||
rm -f cmd/server/rice-box.go
|
||||
./node_modules/.bin/webpack
|
||||
screen -S "$sn" -d -m go run ./cmd/server/
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -16,6 +16,7 @@ require (
|
|||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -16,6 +16,8 @@ github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp
|
|||
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ type Config struct {
|
|||
Owncast struct {
|
||||
BaseUrl string
|
||||
}
|
||||
Test struct {
|
||||
Enable bool
|
||||
}
|
||||
Twitch struct {
|
||||
Enable bool
|
||||
Channel string
|
||||
|
|
@ -45,6 +48,7 @@ func (c *Config) Load(file string) {
|
|||
config.Server.WebhookSecret = cfg.Section("server").Key("webhook_secret").String()
|
||||
|
||||
config.Owncast.BaseUrl = cfg.Section("owncast").Key("base_url").String()
|
||||
config.Test.Enable = cfg.Section("test").Key("enable").MustBool(false)
|
||||
|
||||
config.Twitch.Enable = cfg.Section("twitch").Key("enable").MustBool(false)
|
||||
config.Twitch.Channel = cfg.Section("twitch").Key("channel").String()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ var messageStore *MessageStore
|
|||
const (
|
||||
MessageOriginOwncast MessageOrigin = iota
|
||||
MessageOriginTwitch
|
||||
MessageOriginTest
|
||||
)
|
||||
|
||||
type MessageStore struct {
|
||||
|
|
@ -21,6 +22,10 @@ func (s *MessageStore) All() []MessageInterface {
|
|||
return s.messages
|
||||
}
|
||||
|
||||
func (s *MessageStore) Clear() {
|
||||
s.messages = []MessageInterface{}
|
||||
}
|
||||
|
||||
func GetMessageStore() *MessageStore {
|
||||
if messageStore == nil {
|
||||
messageStore = new(MessageStore)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitnet.fr/deblan/owncast-webhook/assets"
|
||||
"gitnet.fr/deblan/owncast-webhook/internal/store"
|
||||
|
|
@ -11,6 +13,10 @@ import (
|
|||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
var (
|
||||
upgrader = websocket.Upgrader{}
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
}
|
||||
|
||||
|
|
@ -18,51 +24,50 @@ func New(e *echo.Echo) *Controller {
|
|||
c := Controller{}
|
||||
|
||||
e.GET("/chat/messages", c.Messages)
|
||||
e.GET("/ws/chat/messages", c.WebsocketMessages)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (ctrl *Controller) WebsocketMessages(c echo.Context) error {
|
||||
ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
storage := store.GetMessageStore()
|
||||
|
||||
for {
|
||||
messages := storage.All()
|
||||
|
||||
if len(messages) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
storage.Clear()
|
||||
|
||||
var items []string
|
||||
|
||||
for _, message := range messages {
|
||||
var buff bytes.Buffer
|
||||
msg := CreateMessageView(message)
|
||||
msg.Render(&buff)
|
||||
|
||||
items = append(items, buff.String())
|
||||
}
|
||||
|
||||
j, _ := json.Marshal(items)
|
||||
|
||||
ws.WriteMessage(websocket.TextMessage, j)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *Controller) Messages(c echo.Context) error {
|
||||
createMessage := func(message store.MessageInterface) Node {
|
||||
var containerStyle Node
|
||||
var userStyle Node
|
||||
var originIcon Node
|
||||
|
||||
switch message.Origin() {
|
||||
case store.MessageOriginOwncast:
|
||||
msg := message.(store.OwncastMessage)
|
||||
|
||||
containerStyle = StyleAttr(fmt.Sprintf(
|
||||
"border-color: var(--theme-color-users-%d)",
|
||||
msg.WebhookMessage.User.DisplayColor,
|
||||
))
|
||||
|
||||
userStyle = StyleAttr(fmt.Sprintf(
|
||||
"color: var(--theme-color-users-%d)",
|
||||
msg.WebhookMessage.User.DisplayColor,
|
||||
))
|
||||
|
||||
originIcon = Img(Src(assets.Asset("dist/img/owncast.png")), Class("message-origin"))
|
||||
case store.MessageOriginTwitch:
|
||||
originIcon = Img(Src(assets.Asset("dist/img/twitch.png")), Class("message-origin"))
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("message"),
|
||||
ID(message.ID()),
|
||||
containerStyle,
|
||||
Div(
|
||||
Class("message-user"),
|
||||
userStyle,
|
||||
Group([]Node{originIcon, Text(message.Author())}),
|
||||
),
|
||||
Div(
|
||||
Class("message-body"),
|
||||
Raw(message.Content()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
page := HTML5(HTML5Props{
|
||||
Title: "Chat",
|
||||
Language: "fr",
|
||||
|
|
@ -74,7 +79,7 @@ func (ctrl *Controller) Messages(c echo.Context) error {
|
|||
ID("chat"),
|
||||
Div(
|
||||
Class("messages"),
|
||||
Map(store.GetMessageStore().All(), createMessage),
|
||||
Map(store.GetMessageStore().All(), CreateMessageView),
|
||||
),
|
||||
Group(assets.EntrypointJs("main")),
|
||||
},
|
||||
|
|
|
|||
50
internal/web/controller/chat/utils.go
Normal file
50
internal/web/controller/chat/utils.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitnet.fr/deblan/owncast-webhook/assets"
|
||||
"gitnet.fr/deblan/owncast-webhook/internal/store"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func CreateMessageView(message store.MessageInterface) Node {
|
||||
var containerStyle Node
|
||||
var userStyle Node
|
||||
var originIcon Node
|
||||
|
||||
switch message.Origin() {
|
||||
case store.MessageOriginOwncast:
|
||||
msg := message.(store.OwncastMessage)
|
||||
|
||||
containerStyle = StyleAttr(fmt.Sprintf(
|
||||
"border-color: var(--theme-color-users-%d)",
|
||||
msg.WebhookMessage.User.DisplayColor,
|
||||
))
|
||||
|
||||
userStyle = StyleAttr(fmt.Sprintf(
|
||||
"color: var(--theme-color-users-%d)",
|
||||
msg.WebhookMessage.User.DisplayColor,
|
||||
))
|
||||
|
||||
originIcon = Img(Src(assets.Asset("dist/img/owncast.png")), Class("message-origin"))
|
||||
case store.MessageOriginTwitch:
|
||||
originIcon = Img(Src(assets.Asset("dist/img/twitch.png")), Class("message-origin"))
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("message animate__animated animate__fadeInRight"),
|
||||
ID(message.ID()),
|
||||
containerStyle,
|
||||
Div(
|
||||
Class("message-user"),
|
||||
userStyle,
|
||||
Group([]Node{originIcon, Text(message.Author())}),
|
||||
),
|
||||
Div(
|
||||
Class("message-body"),
|
||||
Raw(message.Content()),
|
||||
),
|
||||
)
|
||||
}
|
||||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
|||
"": {
|
||||
"dependencies": {
|
||||
"@symfony/webpack-encore": "github:symfony/webpack-encore",
|
||||
"animate.css": "^4.1.1",
|
||||
"raw-loader": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2734,6 +2735,12 @@
|
|||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/animate.css": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
|
||||
"integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@symfony/webpack-encore": "github:symfony/webpack-encore",
|
||||
"animate.css": "^4.1.1",
|
||||
"raw-loader": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue