Merge branch 'master' into room-access-restrictions

# Conflicts:
#	main.go
This commit is contained in:
Zorchenhimer 2019-03-30 17:45:39 -04:00
commit 34d8fbf20c
8 changed files with 246 additions and 128 deletions

View File

@ -30,6 +30,8 @@ type ChatRoom struct {
modPasswords []string // single-use mod passwords
modPasswordsMtx sync.Mutex
isShutdown bool
}
//initializing the chatroom
@ -53,6 +55,11 @@ func newChatRoom() (*ChatRoom, error) {
}
func (cr *ChatRoom) JoinTemp(conn *chatConnection) (string, error) {
// Don't allow new joins when the server is closing.
if cr.isShutdown {
return "", fmt.Errorf("Server is shutting down")
}
defer cr.clientsMtx.Unlock()
cr.clientsMtx.Lock()
@ -77,6 +84,11 @@ func (cr *ChatRoom) JoinTemp(conn *chatConnection) (string, error) {
//registering a new client
//returns pointer to a Client, or Nil, if the name is already taken
func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
// Don't allow new joins when the server is closing.
if cr.isShutdown {
return nil, fmt.Errorf("Server is shutting down")
}
defer cr.clientsMtx.Unlock()
cr.clientsMtx.Lock()
@ -500,3 +512,26 @@ func (cr *ChatRoom) changeName(oldName, newName string, forced bool) error {
return fmt.Errorf("Client not found with name %q", oldName)
}
// Shutdown the chatroom. First, dissallow new joins by setting
// isShutdown, then close each client connection. This would be
// a good place to put a final command that gets sent to the client.
func (cr *ChatRoom) Shutdown() {
cr.isShutdown = true
common.LogInfoln("ChatRoom is shutting down.")
cr.clientsMtx.Lock()
defer cr.clientsMtx.Unlock()
for uuid, client := range cr.clients {
common.LogDebugf("Closing connection for %s", uuid)
client.conn.Close()
}
for uuid, conn := range cr.tempConn {
common.LogDebugf("Closing connection for temp %s", uuid)
conn.Close()
}
common.LogInfoln("ChatRoom Shutdown() finished")
}

View File

@ -7,39 +7,36 @@ import (
"github.com/lucasb-eyer/go-colorful"
)
// Colors holds all the valid html color names for MovieNight
// the values in colors must be lowercase so it matches with the color input
// this saves from having to call strings.ToLower(color) every time to check
var colors = []string{
var Colors = []string{
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
"beige", "bisque", "black", "blanchedalmond", "blue",
"blueviolet", "brown", "burlywood", "cadetblue", "chartreuse",
"chocolate", "coral", "cornflowerblue", "cornsilk", "crimson",
"cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
"darkgrey", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
"firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro",
"ghostwhite", "gold", "goldenrod", "gray", "grey",
"green", "greenyellow", "honeydew", "hotpink", "indianred",
"indigo", "ivory", "khaki", "lavender", "lavenderblush",
"lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
"lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen",
"magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
"beige", "bisque", "blanchedalmond", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral",
"cornflowerblue", "cornsilk", "crimson", "cyan", "darkcyan",
"darkgoldenrod", "darkgray", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darksalmon", "darkseagreen", "darkslateblue",
"darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue",
"dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen",
"fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod",
"gray", "greenyellow", "honeydew", "hotpink", "indigo",
"ivory", "khaki", "lavender", "lavenderblush", "lawngreen",
"lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
"lightgrey", "lightgreen", "lightpink", "lightsalmon", "lightseagreen",
"lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime",
"limegreen", "linen", "magenta", "mediumaquamarine", "mediumorchid",
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab",
"orange", "orangered", "orchid", "palegoldenrod", "palegreen",
"paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
"pink", "plum", "powderblue", "purple", "rebeccapurple",
"red", "rosybrown", "royalblue", "saddlebrown", "salmon",
"sandybrown", "seagreen", "seashell", "sienna", "silver",
"skyblue", "slateblue", "slategray", "slategrey", "snow",
"springgreen", "steelblue", "tan", "teal", "thistle",
"tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen",
"mediumvioletred", "mintcream", "mistyrose", "moccasin", "navajowhite",
"oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum",
"powderblue", "purple", "rebeccapurple", "red", "rosybrown",
"royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen",
"seashell", "sienna", "silver", "skyblue", "slateblue",
"slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet",
"wheat", "white", "whitesmoke", "yellow", "yellowgreen",
}
var (
@ -50,7 +47,7 @@ var (
// It also accepts hex codes in the form of #RGB and #RRGGBB
func IsValidColor(s string) bool {
s = strings.ToLower(s)
for _, c := range colors {
for _, c := range Colors {
if s == c {
return true
}

44
common/emotes_test.go Normal file
View File

@ -0,0 +1,44 @@
package common
import (
"os"
"testing"
)
var data_good = map[string]string{
"one": `<img src="/emotes/one.png" height="28px" title="one" />`,
"two": `<img src="/emotes/two.png" height="28px" title="two" />`,
"three": `<img src="/emotes/three.gif" height="28px" title="three" />`,
":one:": `<img src="/emotes/one.png" height="28px" title="one" />`,
":two:": `<img src="/emotes/two.png" height="28px" title="two" />`,
":three:": `<img src="/emotes/three.gif" height="28px" title="three" />`,
"[one]": `<img src="/emotes/one.png" height="28px" title="one" />`,
"[two]": `<img src="/emotes/two.png" height="28px" title="two" />`,
"[three]": `<img src="/emotes/three.gif" height="28px" title="three" />`,
":one: two [three]": `<img src="/emotes/one.png" height="28px" title="one" /> <img src="/emotes/two.png" height="28px" title="two" /> <img src="/emotes/three.gif" height="28px" title="three" />`,
"nope one what": `nope <img src="/emotes/one.png" height="28px" title="one" /> what`,
"nope :two: what": `nope <img src="/emotes/two.png" height="28px" title="two" /> what`,
"nope [three] what": `nope <img src="/emotes/three.gif" height="28px" title="three" /> what`,
}
func TestMain(m *testing.M) {
Emotes = map[string]string{
"one": "one.png",
"two": "two.png",
"three": "three.gif",
}
os.Exit(m.Run())
}
func TestEmotes_ParseEmotes(t *testing.T) {
for input, expected := range data_good {
got := ParseEmotes(input)
if got != expected {
t.Errorf("%s failed to parse into %q. Received: %q", input, expected, got)
}
}
}

136
main.go
View File

@ -1,11 +1,13 @@
package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"time"
"github.com/gorilla/sessions"
"github.com/nareix/joy4/format"
@ -14,9 +16,10 @@ import (
)
var (
addr string
sKey string
stats = newStreamStats()
addr string
sKey string
stats = newStreamStats()
chatServer *http.Server
)
func setupSettings() error {
@ -51,9 +54,6 @@ func main() {
os.Exit(1)
}
exit := make(chan bool)
go handleInterrupt(exit)
// Load emotes before starting server.
var err error
if chat, err = newChatRoom(); err != nil {
@ -77,54 +77,88 @@ func main() {
common.LogInfoln("RoomAccess: ", settings.RoomAccess)
common.LogInfoln("RoomAccessPin: ", settings.RoomAccessPin)
go startServer()
go startRmtpServer()
<-exit
}
func startRmtpServer() {
server := &rtmp.Server{
HandlePlay: handlePlay,
HandlePublish: handlePublish,
}
err := server.ListenAndServe()
if err != nil {
// If the server cannot start, don't pretend we can continue.
panic("Error trying to start rtmp server: " + err.Error())
// Define this here so we can set some timeouts and things.
chatServer := &http.Server{
Addr: addr,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
}
func startServer() {
// Chat websocket
http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/static/js/", wsStaticFiles)
http.HandleFunc("/static/css/", wsStaticFiles)
http.HandleFunc("/static/img/", wsImages)
http.HandleFunc("/static/main.wasm", wsWasmFile)
http.HandleFunc("/emotes/", wsEmotes)
http.HandleFunc("/favicon.ico", wsStaticFiles)
http.HandleFunc("/chat", handleIndexTemplate)
http.HandleFunc("/video", handleIndexTemplate)
http.HandleFunc("/help", handleHelpTemplate)
http.HandleFunc("/pin", handlePin)
http.HandleFunc("/", handleDefault)
err := http.ListenAndServe(addr, nil)
if err != nil {
// If the server cannot start, don't pretend we can continue.
panic("Error trying to start chat/http server: " + err.Error())
}
}
func handleInterrupt(exit chan bool) {
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt)
<-ch
common.LogInfoln("Closing server")
if settings.StreamStats {
stats.Print()
}
exit <- true
chatServer.RegisterOnShutdown(func() { chat.Shutdown() })
// rtmp.Server does not implement .RegisterOnShutdown()
//server.RegisterOnShutdown(func() { common.LogDebugln("server shutdown callback called.") })
// These have been moved back to annon functitons so I could use
// `server`, `chatServer`, and `exit` in them without needing to
// pass them as parameters.
// Signal handler
exit := make(chan bool)
go func() {
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt)
<-ch
common.LogInfoln("Closing server")
if settings.StreamStats {
stats.Print()
}
if err := chatServer.Shutdown(context.Background()); err != nil {
common.LogErrorf("Error shutting down chat server: %v", err)
}
common.LogInfoln("Shutdown() sent. Sending exit.")
exit <- true
}()
// Chat and HTTP server
go func() {
// Use a ServeMux here instead of the default, global,
// http handler. It's a good idea when we're starting more
// than one server.
mux := http.NewServeMux()
mux.HandleFunc("/ws", wsHandler)
mux.HandleFunc("/static/js/", wsStaticFiles)
mux.HandleFunc("/static/css/", wsStaticFiles)
mux.HandleFunc("/static/img/", wsImages)
mux.HandleFunc("/static/main.wasm", wsWasmFile)
mux.HandleFunc("/emotes/", wsEmotes)
mux.HandleFunc("/favicon.ico", wsStaticFiles)
mux.HandleFunc("/chat", handleIndexTemplate)
mux.HandleFunc("/video", handleIndexTemplate)
mux.HandleFunc("/help", handleHelpTemplate)
mux.HandleFunc("/pin", handlePin)
mux.HandleFunc("/", handleDefault)
chatServer.Handler = mux
err := chatServer.ListenAndServe()
// http.ErrServerClosed is returned when server.Shuddown()
// is called.
if err != http.ErrServerClosed {
// If the server cannot start, don't pretend we can continue.
panic("Error trying to start chat/http server: " + err.Error())
}
common.LogDebugln("ChatServer closed.")
}()
// RTMP server
go func() {
err := server.ListenAndServe()
// http.ErrServerClosed is returned when server.Shuddown()
// is called.
if err != http.ErrServerClosed {
// If the server cannot start, don't pretend we can continue.
panic("Error trying to start rtmp server: " + err.Error())
}
common.LogDebugln("RTMP server closed.")
}()
<-exit
}

View File

@ -42,6 +42,23 @@ span.svmsg {
color: #ea6260;
}
input[type=text] {
background: transparent;
border: var(--var-border);
border-radius: var(--var-border-radius);
color: var(--var-message-color);
padding: 5px;
font-weight: bold;
}
#colorInputDiv {
font-size: 14px;
}
#colorInput {
border: 2px solid var(--var-message-color);
}
.root {
max-width: var(--var-max-width);
max-height: var(--var-max-height);
@ -223,15 +240,6 @@ span.svmsg {
margin: 5px auto;
}
#name {
background: transparent;
border: var(--var-border);
border-radius: var(--var-border-radius);
color: var(--var-message-color);
padding: 5px;
font-weight: bold;
}
#chat {
display: grid;
grid-template-rows: 1.5em 2em 1fr 6em 2.5em 1em;
@ -295,6 +303,12 @@ span.svmsg {
color: #b1b1b1;
}
#colorName {
font-weight: bold;
background: var(--var-background-color);
padding: -10px;
}
#colorSubmit:disabled {
display: none;
}

View File

@ -158,6 +158,10 @@ function help() {
}
function showColors(show) {
if (show === undefined) {
show = $("#hiddencolor").css("display") === "none";
}
$("#hiddencolor").css("display", show ? "block" : "");
}
@ -188,11 +192,22 @@ function updateColor() {
function changeColor() {
if (isValidColor(colorAsHex())) {
sendMessage("/color " + colorAsHex());
showColors(false);
sendColor(colorAsHex());
}
}
function colorSelectChange() {
let val = $("#colorSelect").val()
if (val !== "") {
sendColor(val);
}
}
function sendColor(color) {
sendMessage("/color " + color);
showColors(false);
}
function setTimestamp(v) {
showTimestamp(v)
document.cookie = "timestamp=" + v
@ -268,37 +283,3 @@ window.addEventListener("load", () => {
// Make sure name is focused on start
$("#name").focus();
});
function pleaseremovethis() {
colors = ["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
"beige", "bisque", "black", "blanchedalmond", "blue",
"blueviolet", "brown", "burlywood", "cadetblue", "chartreuse",
"chocolate", "coral", "cornflowerblue", "cornsilk", "crimson",
"cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
"darkgrey", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
"firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro",
"ghostwhite", "gold", "goldenrod", "gray", "grey",
"green", "greenyellow", "honeydew", "hotpink", "indianred",
"indigo", "ivory", "khaki", "lavender", "lavenderblush",
"lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan",
"lightgoldenrodyellow", "lightgray", "lightgrey", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen",
"magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid",
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab",
"orange", "orangered", "orchid", "palegoldenrod", "palegreen",
"paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru",
"pink", "plum", "powderblue", "purple", "rebeccapurple",
"red", "rosybrown", "royalblue", "saddlebrown", "salmon",
"sandybrown", "seagreen", "seashell", "sienna", "silver",
"skyblue", "slateblue", "slategray", "slategrey", "snow",
"springgreen", "steelblue", "tan", "teal", "thistle",
"tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen",]
}

View File

@ -50,7 +50,7 @@
<div id="chatButtons">
<input type="button" class="button pretty-button" onclick="auth();" value="Auth" />
<input type="button" class="button pretty-button" onclick="help();" value="Help" />
<input type="button" class="button pretty-button" onclick="showColors(true);" value="Color" />
<input type="button" class="button pretty-button" onclick="showColors();" value="Color" />
<input type="button" class="button pretty-button" onclick="nick();" value="Nick" />
{{if .Video}}
<input type="button" class="button pretty-button" onclick="initPlayer();" value="Reload Player" />
@ -75,7 +75,10 @@
<input id="colorBlue" type="range" min="0" max="255" value="0" oninput="updateColor();" />
<span id="colorBlueLabel"></span>
</div>
<div id="colorName" class="range-div" style="font-weight: bold;">
<div class="range-div">
<select id="colorSelect" onchange="colorSelectChange();"></select>
</div>
<div id="colorName" class="range-div">
NAME
</div>
<div id="colorWarning" class="range-div contrast">

View File

@ -189,6 +189,16 @@ func main() {
js.Set("debugValues", js.CallbackOf(debugValues))
js.Set("showTimestamp", js.CallbackOf(showTimestamp))
go func() {
time.Sleep(time.Second * 1)
inner := `<option value=""></option>`
for _, c := range common.Colors {
inner += fmt.Sprintf(`<option value="%s">%s</option>\n`, c, c)
}
js.Get("colorSelect").Set("innerHTML", inner)
}()
// This is needed so the goroutine does not end
for {
// heatbeat to keep connection alive to deal with nginx