Files
StreamGoose/src/server.go
T
2024-09-14 14:41:59 +03:00

133 lines
3.9 KiB
Go

/*
StreamGoose main server/UI launcher — Go reference implementation
Created by Luxferre in 2024, released into public domain
*/
package main
import (
"os"
"fmt"
"log"
"strings"
"encoding/json"
"net/http"
"io/ioutil"
/* external */
"centrifuge.hectabit.org/HectaBit/webview_go" /* for UI launch */
)
/* main chat message structure */
type ChatMessage struct {
Id uint64 `json:"id"` /* message id (auto-filled) */
Type string `json:"type"` /* message type string */
Timestamp string `json:"timestamp"` /* timestamp string (formatted) */
Username string `json:"username"` /* username */
Text string `json:"text"` /* message text */
Icon string `json:"icon"` /* optional message icon URI */
}
var global_msg_id uint64 = 0 /* track global message ID */
var message_transport chan ChatMessage /* global message channel */
var bro webview.WebView /* global browser instance handle */
/* escape HTML entities in JS parameters */
func esc(s string) string {
s = strings.ReplaceAll(s, "&", "&")
s = strings.ReplaceAll(s, "'", "'")
s = strings.ReplaceAll(s, "\"", """)
s = strings.ReplaceAll(s, "<", "&lt;")
s = strings.ReplaceAll(s, ">", "&gt;")
return s
}
/* chat message appender thread */
func appendChatMessages() {
var msg ChatMessage
for {
msg = <-message_transport /* receive it from the channel */
/* populate the running id */
global_msg_id += 1
msg.Id = global_msg_id
/* directly call the append function on the JS side */
fmt_str := "gooseAppendMessage('%d', '%s', '%s', '%s', '%s', '%s')"
bro.Eval(fmt.Sprintf(fmt_str, msg.Id, esc(msg.Type), esc(msg.Timestamp), esc(msg.Username), esc(msg.Text), esc(msg.Icon)))
}
}
/* POST /new handler (for external backends) */
func msgAddHandler(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
var msg ChatMessage /* create a new message */
err := decoder.Decode(&msg) /* fill in the message structure */
if err != nil {
log.Println("Error decoding POST request:", err)
return
}
/* post it to the transport channel */
message_transport <- msg
w.Write([]byte("{\"status\": 1}")) /* respond with a status */
}
/* entry point */
func main() {
config_path := "config.json"
if len(os.Args) > 1 {
config_path = os.Args[1]
}
content, err := ioutil.ReadFile(config_path)
if err != nil {
log.Fatal("Error when opening config file: ", err)
}
var config map[string]interface{}
err = json.Unmarshal(content, &config)
if err != nil {
log.Fatal("Error during Unmarshal(): ", err)
}
/* create the message transport channel */
message_transport = make(chan ChatMessage)
/* target server listening address */
target_addr := fmt.Sprintf("%s:%d", config["server_listen_ip"].(string), int(config["server_port"].(float64)))
go appendChatMessages() /* start the appender thread */
go func() { /* start the server thread */
/* serve the POST /new */
http.HandleFunc("/new", msgAddHandler)
/* serve the webroot directory */
fs := http.FileServer(http.Dir("./webroot"))
http.Handle("/", fs)
log.Println(fmt.Sprintf("Listening on %s\n", target_addr))
err = http.ListenAndServe(target_addr, nil)
if err != nil {
log.Fatal(err)
}
}()
if _, ok := config["tw_backend_enabled"]; ok && config["tw_backend_enabled"].(bool) == true {
go twitch_run(config) /* start the Twitch backend */
}
if _, ok := config["yt_backend_enabled"]; ok && config["yt_backend_enabled"].(bool) == true {
go youtube_run(config) /* start the YouTube backend */
}
/* start the GUI */
debug := false
if _, ok := config["debug"]; ok && config["debug"].(bool) == true {
debug = true
}
bro = webview.New(debug) /* create a browser instance */
bro.SetSize(320, 720, webview.HintNone)
defer bro.Destroy()
bro.SetTitle("StreamGoose v0.0.1")
bro.Navigate("http://" + target_addr)
bro.Run()
}