Merge branch 'master' into room-access-restrictions

# Conflicts:
#	chatcommands.go
#	common/chatcommands.go
#	connection.go
#	static/css/site.css
#	static/main.html
This commit is contained in:
Zorchenhimer 2019-03-23 17:03:28 -04:00
commit 7448876299
10 changed files with 257 additions and 127 deletions

View File

@ -26,7 +26,9 @@ var commands = &CommandControl{
common.CNMe.String(): Command{
HelpText: "Display an action message.",
Function: func(client *Client, args []string) string {
client.Me(strings.Join(args, " "))
if len(args) != 0 {
client.Me(strings.Join(args, " "))
}
return ""
},
},
@ -139,7 +141,7 @@ var commands = &CommandControl{
// Clear/hide title if sent with no arguments.
if len(args) == 0 {
cl.belongsTo.ClearPlaying()
return ""
return "Title cleared"
}
link := ""
title := ""

View File

@ -48,7 +48,7 @@ func newChatRoom() (*ChatRoom, error) {
fmt.Printf("Loaded %d emotes\n", num)
//the "heartbeat" for broadcasting messages
go cr.BroadCast()
go cr.Broadcast()
return cr, nil
}
@ -96,6 +96,7 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
}
}
conn.clientName = name
client := &Client{
name: name,
conn: conn,
@ -123,8 +124,8 @@ func (cr *ChatRoom) Join(name, uid string) (*Client, error) {
return client, nil
}
// TODO: fix this up a bit. kick and leave are the same, incorrect, error: "That name was already used!"
//leaving the chatroom
// TODO: fix this up a bit. kick and leave are the same, incorrect, error: "That
// name was already used!" leaving the chatroom
func (cr *ChatRoom) Leave(name, color string) {
defer cr.clientsMtx.Unlock()
cr.clientsMtx.Lock() //preventing simultaneous access to the `clients` map
@ -307,7 +308,7 @@ func (cr *ChatRoom) UserCount() int {
}
//broadcasting all the messages in the queue in one block
func (cr *ChatRoom) BroadCast() {
func (cr *ChatRoom) Broadcast() {
send := func(data common.ChatData, client *Client) {
err := client.SendChatData(data)
if err != nil {
@ -320,20 +321,24 @@ func (cr *ChatRoom) BroadCast() {
case msg := <-cr.queue:
cr.clientsMtx.Lock()
for _, client := range cr.clients {
send(msg, client)
go send(msg, client)
}
for _, conn := range cr.tempConn {
data, err := msg.ToJSON()
if err != nil {
fmt.Printf("Error converting ChatData to ChatDataJSON: %v\n", err)
// Break out early because if one conversion fails, they all will fail
break
}
err = conn.WriteData(data)
if err != nil {
fmt.Printf("Error writing data to connection: %v\n", err)
data, err := msg.ToJSON()
if err != nil {
fmt.Printf("Error converting ChatData to ChatDataJSON: %v\n", err)
} else {
for uuid, conn := range cr.tempConn {
go func(c *chatConnection, suid string) {
err = c.WriteData(data)
if err != nil {
fmt.Printf("Error writing data to connection: %v\n", err)
delete(cr.tempConn, suid)
}
}(conn, uuid)
}
}
cr.clientsMtx.Unlock()
case msg := <-cr.modqueue:
cr.clientsMtx.Lock()

View File

@ -3,50 +3,72 @@ package common
import (
"regexp"
"strings"
"github.com/lucasb-eyer/go-colorful"
)
// 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{
"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",
"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",
}
// IsValidColor takes a string s and compares it against a list of css color names.
// It also accepts hex codes in the form of #000 (RGB), to #00000000 (RRGGBBAA), with A
// being the alpha value
func IsValidColor(s string) bool {
s = strings.ToLower(s)
for _, c := range colors {
if strings.ToLower(c) == strings.ToLower(s) {
if s == c {
return true
}
}
return regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`).MatchString(s)
if regexp.MustCompile(`^#([0-9A-Fa-f]{3}){1,2}$`).MatchString(s) {
c, err := colorful.Hex(s)
if err != nil {
return false
}
total := c.R + c.G + c.B
return total > 0.7 && c.B/total < 0.7
}
return false
}
// RandomColor returns a hex color code
func RandomColor() string {
var color colorful.Color
for !IsValidColor(color.Hex()) {
color = colorful.FastHappyColor()
}
return color.Hex()
}

View File

@ -3,8 +3,6 @@ package common
// Misc utils
import (
"fmt"
"math/rand"
"regexp"
)
@ -16,14 +14,3 @@ func IsValidName(name string) bool {
return 3 <= len(name) && len(name) <= 36 &&
usernameRegex.MatchString(name) && !IsValidColor(name)
}
// RandomColor returns a hex color code
func RandomColor() string {
nums := []int32{}
for i := 0; i < 6; i++ {
nums = append(nums, rand.Int31n(15))
}
return fmt.Sprintf("#%X%X%X%X%X%X",
nums[0], nums[1], nums[2],
nums[3], nums[4], nums[5])
}

View File

@ -10,24 +10,30 @@ import (
type chatConnection struct {
*websocket.Conn
mutex sync.Mutex
mutex sync.RWMutex
forwardedFor string
clientName string
}
func (cc *chatConnection) ReadData(data interface{}) error {
defer cc.mutex.Unlock()
cc.mutex.Lock()
cc.mutex.RLock()
defer cc.mutex.RUnlock()
stats.msgInInc()
return cc.ReadJSON(data)
}
func (cc *chatConnection) WriteData(data interface{}) error {
defer cc.mutex.Unlock()
cc.mutex.Lock()
defer cc.mutex.Unlock()
stats.msgOutInc()
err := cc.WriteJSON(data)
if err != nil {
return fmt.Errorf("Error writing data to %s: %v", cc.Host(), err)
if operr, ok := err.(*net.OpError); ok {
fmt.Println("OpError: " + operr.Err.Error())
}
return fmt.Errorf("Error writing data to %s %s: %v", cc.clientName, cc.Host(), err)
}
return nil
}

View File

@ -56,12 +56,12 @@ func main() {
fmt.Println("Listen and serve ", addr)
go startServer()
go startRmptServer()
go startRmtpServer()
<-exit
}
func startRmptServer() {
func startRmtpServer() {
server := &rtmp.Server{
HandlePlay: handlePlay,
HandlePublish: handlePublish,

View File

@ -4,6 +4,7 @@
--var-message-color: #f4f4f4;
--var-contrast-color: #1bf7ec;
--var-background-color: #0F0F11;
--var-popout-color: #393940;
--var-max-height: 98vh;
--var-max-width: 98vw;
}
@ -111,23 +112,30 @@ span.svmsg {
color: #B1B1B1;
}
.access-grant {
font-weight: normal;
color: greenyellow;
.contrast {
color: var(--var-contrast-color);
}
.access-ignore {
font-weight: normal;
color: red;
.range-div {
margin-bottom: 5px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
padding: 5px;
}
.access-name {
color: #f7b11b;
.range-div>input[type=button] {
flex: 2;
}
.access-title {
color: #e5e0e5;
font-weight: bold;
.hiddendiv {
display: none;
color: var(--var-message-color);
background: var(--var-popout-color);
padding: 2em;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
#videoElement {
@ -154,7 +162,7 @@ span.svmsg {
right: 0px;
margin: 0px 5px;
padding: 3px;
background: #393940;
background: var(--var-popout-color);
}
#hidden:hover,
@ -262,4 +270,8 @@ span.svmsg {
#helpbody {
color: #b1b1b1;
}
#colorSubmit:disabled {
display: none;
}

View File

@ -4,6 +4,9 @@ function setPlaying(title, link) {
if (title !== "") {
$('#playing').text(title);
document.title = "Movie Night | " + title;
} else {
$('#playing').text("");
document.title = "Movie Night";
}
$('#playing').removeAttr('href');
@ -89,7 +92,11 @@ function join() {
}
function websocketSend(data) {
ws.send(data);
if (ws.readyState == ws.OPEN) {
ws.send(data);
} else {
console.log("did not send data because websocket is not open", data);
}
}
function sendChat() {
@ -126,13 +133,57 @@ function help() {
sendMessage("/help");
}
function showColors(show) {
$("#hiddencolor").css("display", show ? "block" : "");
}
function colorAsHex() {
let r = parseInt($("#colorRed").val()).toString(16).padStart(2, "0");
let g = parseInt($("#colorGreen").val()).toString(16).padStart(2, "0");
let b = parseInt($("#colorBlue").val()).toString(16).padStart(2, "0");
return `#${r}${g}${b}`
}
function updateColor() {
let r = $("#colorRed").val();
let g = $("#colorGreen").val();
let b = $("#colorBlue").val();
$("#colorRedLabel").text(r.padStart(3, "0"));
$("#colorGreenLabel").text(g.padStart(3, "0"));
$("#colorBlueLabel").text(b.padStart(3, "0"));
$("#colorName").css("color", `rgb(${r}, ${g}, ${b})`);
if (isValidColor(colorAsHex())) {
$("#colorWarning").text("");
} else {
$("#colorWarning").text("Unreadable Color");
}
}
function changeColor() {
if (isValidColor(colorAsHex())) {
sendMessage("/color " + colorAsHex());
showColors(false);
}
}
// Get the websocket setup in a function so it can be recalled
function setupWebSocket() {
ws = new WebSocket(getWsUri());
ws.onmessage = (m) => recieveMessage(m.data);
ws.onopen = (e) => console.log("Websocket Open:", e);
ws.onclose = () => closeChat();
ws.onerror = (e) => console.log("Websocket Error:", e);
ws.onopen = () => console.log("Websocket Open");
ws.onclose = () => {
closeChat();
setNotifyBox("The connection to the server has closed. Please refresh page to connect again.");
$("#joinbox").css("display", "none");
}
ws.onerror = (e) => {
console.log("Websocket Error:", e);
e.target.close();
}
}
function setupEvents() {
@ -159,10 +210,6 @@ function setupEvents() {
input: () => processMessage(),
});
$("#hiddenColorPicker").on({
change: () => sendMessage("/color " + $("#hiddenColorPicker").val()),
});
$("#send").on({
click: () => $("#msg").focus(),
});
@ -172,6 +219,12 @@ function setupEvents() {
).observe($("#suggestions")[0], { childList: true });
}
function defaultValues() {
$("#colorRed").val(0).trigger("input");
$("#colorGreen").val(0).trigger("input");
$("#colorBlue").val(0).trigger("input");
}
window.addEventListener("onresize", updateSuggestionCss);
window.addEventListener("load", () => {
@ -179,7 +232,42 @@ window.addEventListener("load", () => {
setupWebSocket();
startGo();
setupEvents();
defaultValues();
// 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

@ -1,80 +1,84 @@
{{define "header"}}
{{ if .Chat }}
{{if .Chat}}
<script type="application/javascript" src="/static/js/wasm_exec.js"></script>
<script type="application/javascript" src="/static/js/chat.js"></script>
<script>
maxMessageCount = {{ .MessageHistoryCount }}
maxMessageCount = {{.MessageHistoryCount }}
</script>
<style>
.root {
display: grid;
}
</style>
{{ end }}
{{end}}
{{ if .Video }}
{{if .Video}}
<script type="application/javascript" src="/static/js/flv.min.js"></script>
<script type="application/javascript" src="/static/js/video.js"></script>
{{ if not .Chat }}
{{if not .Chat}}
<style>
#videoElement {
height: 99vh;
}
</style>
{{ end }}
{{ end }}
{{end}}
{{end}}
{{ if and .Video .Chat }}
{{if and .Video .Chat}}
<style>
.root {
grid-template-columns: 5fr 1fr;
}
</style>
{{ end }}
{{end}}
{{end}}
{{define "body"}}
{{ if .Video }}
{{if .Video}}
<video id="videoElement" controls autoplay x5-video-player-type="h5" x5-video-player-fullscreen="true" playsinline
webkit-playsinline>
Your browser is too old and doesn't support HTML5 video.
</video>
{{ end }}
{{end}}
{{ if .Chat }}
{{if .Chat}}
<div id="chatwindow">
<div id="notifyBox"></div>
<div id="chat" style="display: none;">
<input id="hiddenColorPicker" type="color" hidden />
<div id="optionBox">Please hover to view options</div>
<div id="hidden">
<div id="chatButtons">
<button class="button pretty-button" onclick="auth();">Auth</button>
<button class="button pretty-button" onclick="help();">Help</button>
<button class="button pretty-button" onclick="$('#hiddenColorPicker').trigger('click');">Color</button>
<button class="button pretty-button" onclick="nick();">Nick</button>
{{ if .Video }}
<button class="button pretty-button" onclick="initPlayer();">Reload Player</button>
{{ end }}
<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="nick();" value="Nick" />
{{if .Video}}
<input type="button" class="button pretty-button" onclick="initPlayer();" value="Reload Player" />
{{end}}
</div>
<hr />
<div id="accessRequest">
<div class="access-title" noclear>Grant Access?</div>
<div>
<button class="button pretty-button access-grant"></button>
<button class="button pretty-button access-ignore"></button>
<span class="access-name">User1</span>
<div id="hiddencolor" class="hiddendiv">
<div class="range-div" style="background-image: linear-gradient(to right, transparent, red);">
<input id="colorRed" type="range" min="0" max="255" value="0" oninput="updateColor();" />
<span id="colorRedLabel"></span>
</div>
<div>
<button class="button pretty-button access-grant"></button>
<button class="button pretty-button access-ignore"></button>
<span class="access-name">User2</span>
<div class="range-div" style="background-image: linear-gradient(to right, transparent, green);">
<input id="colorGreen" type="range" min="0" max="255" value="0" oninput="updateColor();" />
<span id="colorGreenLabel"></span>
</div>
<div>
<button class="button pretty-button access-grant"></button>
<button class="button pretty-button access-ignore"></button>
<span class="access-name">User3</span>
<div class="range-div" style="background-image: linear-gradient(to right, transparent, blue);">
<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;">
NAME
</div>
<div id="colorWarning" class="range-div contrast">
</div>
<dvi class="range-div">
<input id="colorSubmit" type="button" class="button pretty-button" value="Select"
onclick="changeColor();" />
</dvi>
</div>
</div>
<a id="playing" target="_blank"></a>
@ -83,7 +87,7 @@
<div id="suggestions"></div>
<textarea id="msg"></textarea>
</div>
<button id="send" class="button" onclick="sendChat();">Send</button>
<input id="send" type="button" class="button" onclick="sendChat();" value="Send" />
<div>
<!-- This is an empty div so there can be an empty space below the send button -->
</div>
@ -91,10 +95,10 @@
<div id="joinbox">
<div style="color: #e5e0e5; text-align: center;">Please enter your name<br />to join the chat</div>
<div>
<input id="name" maxlength="36">
<button id="join" class="button pretty-button" onclick="join();">Join</button>
<input id="name" type="text" maxlength="36">
<input id="join" type="button" class="button pretty-button" onclick="join();" value="Join" />
</div>
</div>
</div>
{{ end }}
{{end}}
{{end}}

View File

@ -224,6 +224,10 @@ func recieve(v []js.Value) {
}
func websocketSend(msg string, dataType common.ClientDataType) error {
if strings.TrimSpace(msg) == "" {
return nil
}
data, err := json.Marshal(common.ClientData{
Type: dataType,
Message: msg,