This commit is contained in:
bel
2021-09-14 06:30:17 -06:00
commit 7ab1723a5e
327 changed files with 127104 additions and 0 deletions

22
MovieNight/static/base.html Executable file
View File

@@ -0,0 +1,22 @@
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<title>{{ .Title }}</title>
<link rel="stylesheet" type="text/css" href="/static/css/hack/hack.css">
<link rel="stylesheet" type="text/css" href="/static/css/site.css">
<script type="application/javascript" src="/static/js/jquery.js"></script>
<script type="application/javascript" src="/static/js/both.js"></script>
{{template "header" .}}
</head>
<body class="scrollbar">
<img id="remote" src="/static/img/remote.png" onclick="flipRemote();" />
<div id="devKeys"></div>
<div class="root">
{{template "body" .}}
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,34 @@
/*!
* Hack typeface https://github.com/source-foundry/Hack
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
*/
/* FONT PATHS
* -------------------------- */
@font-face {
font-family: 'Hack';
src: url('fonts/hack-regular-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular-subset.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic-webfont.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
}

View File

@@ -0,0 +1,34 @@
/*!
* Hack typeface https://github.com/source-foundry/Hack
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
*/
/* FONT PATHS
* -------------------------- */
@font-face {
font-family: 'Hack';
src: url('fonts/hack-regular-subset.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-regular-subset.woff?sha={{ ink }}') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bold-subset.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-bold-subset.woff?sha={{ ink }}') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-italic-subset.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-italic-webfont.woff?sha={{ ink }}') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bolditalic-subset.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-bolditalic-subset.woff?sha={{ ink }}') format('woff');
font-weight: 700;
font-style: italic;
}

View File

@@ -0,0 +1,34 @@
/*!
* Hack typeface https://github.com/source-foundry/Hack
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
*/
/* FONT PATHS
* -------------------------- */
@font-face {
font-family: 'Hack';
src: url('fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bold.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-italic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bolditalic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
}

View File

@@ -0,0 +1,34 @@
/*!
* Hack typeface https://github.com/source-foundry/Hack
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
*/
/* FONT PATHS
* -------------------------- */
@font-face {
font-family: 'Hack';
src: url('fonts/hack-regular.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-regular.woff?sha={{ ink }}') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bold.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-bold.woff?sha={{ ink }}') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-italic.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-italic.woff?sha={{ ink }}') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Hack';
src: url('fonts/hack-bolditalic.woff2?sha={{ ink }}') format('woff2'), url('fonts/hack-bolditalic.woff?sha={{ ink }}') format('woff');
font-weight: 700;
font-style: italic;
}

385
MovieNight/static/css/site.css Executable file
View File

@@ -0,0 +1,385 @@
:root {
--var-border: 1px solid #606060;
--var-border-radius: 5px;
--var-message-color: #f4f4f4;
--var-link-color: #a9c8c3;
--var-contrast-color: #1bf7ec;
--var-background-color: #0F0F11;
--var-popout-color: #393940;
--var-max-height: 98vh;
--var-max-width: 98vw;
}
html {
background: var(--var-background-color);
font-family: "Hack";
font-size: 14px;
}
dt {
font-weight: bold;
}
body {
height: var(--var-max-height);
margin: 0px;
}
span.name {
font-weight: bold;
}
span.cmdme {
font-style: italic;
}
span.msg {
font-style: normal;
color: #cfccd1;
}
.msg a, .doornotice {
color: #cfccd1;
}
span.svmsg {
font-style: italic;
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;
}
.root {
max-width: var(--var-max-width);
max-height: var(--var-max-height);
height: var(--var-max-height);
width: var(--var-max-width);
margin: 0px 1vw;
}
.pretty-button {
padding: 5px 10px;
}
.button {
background: #232328;
color: var(--var-message-color);
font-size: 16px;
font-weight: bold;
border: none;
border-radius: var(--var-border-radius);
}
.button:hover {
background: #46464f;
}
.scrollbar {
/* Firefox compatable scrollbar settings */
scrollbar-color: #45009e transparent;
scrollbar-width: thin;
}
.scrollbar::-webkit-scrollbar {
width: 6px;
}
.scrollbar::-webkit-scrollbar-thumb {
background-color: #45009e;
}
.announcement {
font-weight: bold;
color: #ea6260;
text-align: center;
margin-top: 10px;
margin-bottom: 10px;
border-top: 3px solid red;
border-bottom: 3px solid red;
}
.helptext {
color: white;
}
#emotesbody {
color: var(--var-message-color);
}
.emotedef {
display: flex;
flex-direction: row;
}
.emotedef div {
padding: 5px;
}
.notice {
color: #595959;
font-size: 75%;
}
.command {
color: #B1B1B1;
}
.commanderror {
color: #e82222;
}
.notice,
.command,
.announcement {
display: block;
text-align: center;
}
.mention {
background: #1cf67ed9;
color: var(--var-background-color);
padding: 1px 2px;
border-radius: 4px;
}
.contrast {
color: var(--var-contrast-color);
}
.spoiler {
border-radius: 3px;
padding: 0px 3px;
}
.spoiler *,
.spoiler {
background: var(--var-popout-color);
color: var(--var-popout-color);
}
.spoiler-active {
background: var(--var-background-color);
color: aqua;
}
.range-div {
margin-bottom: 5px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
padding: 5px;
}
.range-div>input[type=button] {
flex: 2;
}
.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 {
position: relative;
top: 50%;
transform: translateY(-50%);
width: 100%;
}
#loadingFiles {
display: flex;
align-items: center;
justify-content: center;
}
#doorentry {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
}
#chatwindow {
display: none;
grid-template-rows: auto 1fr;
position: relative;
padding-top: 1vh;
max-height: calc(var(--var-max-height) - 1vh);
min-width: 300px;
}
#hidden {
display: none;
position: absolute;
top: 0px;
left: 0px;
right: 0px;
margin: 0px 5px;
padding: 3px;
background: var(--var-popout-color);
}
#hidden:hover,
#optionBox:hover~#hidden {
display: unset;
}
#optionBox,
#notifyBox {
color: var(--var-contrast-color);
font-weight: bold;
font-size: 12px;
text-align: center;
}
#accessRequest div {
margin-bottom: 5px;
}
#colorInputDiv {
font-size: 14px;
}
#colorInput {
border: 2px solid var(--var-message-color);
}
#playing {
color: #288a85;
font-size: x-Large;
}
#chatButtons {
margin: 5px;
}
#chatButtons button {
margin: 5px 0px;
}
#joinbox {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 10px;
}
#joinbox div {
margin: 5px auto;
}
#joinbox a {
text-decoration: none;
color: var(--var-link-color);
}
#joinbox a:hover {
text-decoration: underline;
}
#chat {
display: grid;
grid-template-rows: 1.5em min-content 1fr 6em 2.5em 1em;
grid-gap: 10px;
margin: 0px 5px;
overflow: auto;
font-size: 12px;
}
#messages {
min-height: 15em;
color: var(--var-message-color);
overflow-y: scroll;
border: var(--var-border);
}
#messages div {
padding: .1em .5em .15em .5em;
word-wrap: break-word;
}
#msgbox {
position: relative;
display: grid;
}
#msg {
background: transparent;
border: var(--var-border);
border-radius: var(--var-border-radius);
border-bottom-right-radius: 0px;
padding: 5px;
color: var(--var-message-color);
resize: none;
}
#suggestions {
background: #3b3b43;
position: absolute;
min-width: 10em;
max-height: 35em;
overflow-y: scroll;
border-radius: 5px 5px 0px 5px;
color: var(--var-message-color);
}
#suggestions>div {
display: flex;
align-items: center;
padding: 5px;
}
#suggestions>div>img {
margin-right: 1em;
}
#suggestions div.selectedName {
color: var(--var-contrast-color);
}
#helpbody {
color: #b1b1b1;
}
#colorName {
font-weight: bold;
background: var(--var-background-color);
padding: -10px;
}
#colorSubmit:disabled {
display: none;
}
#remote {
display: none;
position: absolute;
left: 0;
right: 0;
top: 0;
margin: 1em auto;
width: 50px;
z-index: 99;
}
#devKeys {
display: none;
position: absolute;
z-index: 99;
color: var(--var-contrast-color);
}

14
MovieNight/static/emotes.html Executable file
View File

@@ -0,0 +1,14 @@
{{define "header"}}
{{end}}
{{define "body"}}
<div id="emotesbody">
<h2>Available Emotes</h2>
{{range $k, $v := .Emotes}}
<div class="emotedef">
<div><img src="{{$v}}" /></div>
<div>{{$k}}</div>
</div>
{{end}}
</div>
{{end}}

3
MovieNight/static/emotes/.gitignore vendored Executable file
View File

@@ -0,0 +1,3 @@
# No images allowed, but keep the folder
*.gif
*.png

32
MovieNight/static/help.html Executable file
View File

@@ -0,0 +1,32 @@
{{define "header"}}
{{end}}
{{define "body"}}
<div id="helpbody">
<h2>Commands</h2>
<dl>
{{range $k, $v := .Commands}}
<dt>{{$k}}</dt>
<dd>{{$v}}</dd>
{{end}}
</dl>
{{if .ModCommands}}
<h2>Moderator</h2>
<dl>
{{range $k, $v := .ModCommands}}
<dt>{{$k}}</dt>
<dd>{{$v}}</dd>
{{end}}
</dl>
{{end}}
{{if .AdminCommands}}
<h2>Administrator</h2>
<dl>
{{range $k, $v := .AdminCommands}}
<dt>{{$k}}</dt>
<dd>{{$v}}</dd>
{{end}}
</dl>
{{end}}
</div>
{{end}}

BIN
MovieNight/static/img/admin.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

BIN
MovieNight/static/img/mod.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

BIN
MovieNight/static/img/remote.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

66
MovieNight/static/js/both.js Executable file
View File

@@ -0,0 +1,66 @@
/// <reference path="./jquery.js" />
let konamiCode = ["ArrowUp", "ArrowUp", "ArrowDown", "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowLeft", "ArrowRight", "b", "a"]
let lastKeys = []
let devKeys = false;
// Make this on all pages so video page also doesn't do this
$(document).on("keydown", function (e) {
lastKeys.push(e);
if (lastKeys.length > 10) {
lastKeys.shift();
}
if (devKeys) {
let modifiedLastKeys = []
lastKeys.forEach((e) => {
switch (e.key) {
case " ":
modifiedLastKeys.push(`Space - ${e.keyCode}`);
break;
default:
modifiedLastKeys.push(`${e.key} - ${e.keyCode}`);
break;
}
})
$("#devKeys").html(`'${modifiedLastKeys.join("', '")}'`);
}
if (e.which === 8 && !$(e.target).is("input, textarea")) {
e.preventDefault();
}
checkKonami(e);
});
function checkKonami(e) {
if (lastKeys.length === konamiCode.length) {
for (let i = 0; i < lastKeys.length; i++) {
if (lastKeys[i].key != konamiCode[i]) {
return;
}
}
$("#remote").css("display", "block");
}
}
function flipRemote() {
$("#remote").attr("src", "/static/img/remote_active.png");
setTimeout(() => {
$("#remote").attr("src", "/static/img/remote.png");
}, Math.round(Math.random() * 10000) + 1000);
}
function enableDebug() {
devKeys = true;
$("#devKeys").css("display", "block");
}
/*
// Just add a / above to uncomment the block
setTimeout(() => {
enableDebug();
alert("Comment this out. It shows the keys.");
}, 150);
//*/

288
MovieNight/static/js/chat.js Executable file
View File

@@ -0,0 +1,288 @@
/// <reference path="./both.js" />
function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function deleteCookie(cname) {
document.cookie = `${cname}=;expires=Thu, 01 Jan 1970 00:00:01 GMT`
}
function setPlaying(title, link) {
if (title !== "") {
$('#playing').text(title);
document.title = "Movie Night | " + title;
} else {
$('#playing').text("");
document.title = "Movie Night";
}
$('#playing').removeAttr('href');
if (link !== "") {
$('#playing').attr('href', link);
}
}
function startGo() {
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
WebAssembly.instantiateStreaming(fetch("/static/main.wasm"), go.importObject).then((result) => {
go.run(result.instance);
}).then(() => {
$("#chatwindow").css("display", "grid");
$("#loadingFiles").css("display", "none");
}).catch((err) => {
console.error(err);
});
}
function getWsUri() {
port = window.location.port;
if (port == "") {
if (window.location.protocol == "http:")
port = "80"
else
port = "443"
}
port = ":" + port;
proto = "ws://"
if (location.protocol == "https:") {
proto = "wss://"
}
return proto + window.location.hostname + port + "/ws";
}
let maxMessageCount = 0
function appendMessages(msg) {
let msgs = $("#messages").find('div');
// let's just say that if the max count is less than 1, then the count is infinite
// the server side should take care of chaking max count ranges
if (msgs.length > maxMessageCount) {
msgs.first().remove();
}
$("#messages").append(msg);
$("#messages").children().last()[0].scrollIntoView({ block: "end" });
}
function purgeChat() {
$('#messages').empty()
}
inChat = false
function openChat() {
console.log("chat opening");
$("#joinbox").css("display", "none");
$("#chat").css("display", "grid");
$("#hidden").css("display", "")
$("#msg").val("");
$("#msg").focus();
inChat = true;
}
function closeChat() {
console.log("chat closing");
$("#joinbox").css("display", "");
$("#chat").css("display", "none");
$("#hidden").css("display", "none")
setNotifyBox("That name was already used!");
inChat = false;
}
function websocketSend(data) {
if (ws.readyState == ws.OPEN) {
ws.send(data);
} else {
console.log("did not send data because websocket is not open", data);
}
}
function sendChat() {
sendMessage($("#msg").val());
$("#msg").val("");
}
function updateSuggestionCss(m) {
if ($("#suggestions").children().length > 0) {
$("#suggestions").css("bottom", $("#msg").outerHeight(true) - 1 + "px");
$("#suggestions").css("display", "");
} else {
$("#suggestions").css("display", "none");
}
}
function updateSuggestionScroll() {
let item = $("#suggestions .selectedName");
if (item.length !== 0) {
item[0].scrollIntoView({ block: "center" });
}
}
function setNotifyBox(msg = "") {
$("#notifyBox").html(msg);
}
// Button Wrapper Functions
function auth() {
let pass = prompt("Enter pass");
if (pass != "" && pass !== null) {
sendMessage("/auth " + pass);
}
}
function nick() {
let nick = prompt("Enter new name");
if (nick != "" && nick !== null) {
sendMessage("/nick " + nick);
}
}
function help() {
sendMessage("/help");
}
function showColors(show) {
if (show === undefined) {
show = $("#hiddencolor").css("display") === "none";
}
$("#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())) {
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 + "; expires=Fri, 31 Dec 9999 23:59:59 GMT";
}
// 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 = () => 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() {
$("#name").on({
keypress: (e) => {
if (e.originalEvent.keyCode == 13) {
$("#join").trigger("click");
}
}
});
$("#msg").on({
keypress: (e) => {
if (e.originalEvent.keyCode == 13 && !e.originalEvent.shiftKey) {
$("#send").trigger("click");
e.preventDefault();
}
},
keydown: (e) => {
if (processMessageKey(e)) {
e.preventDefault();
}
},
input: () => processMessage(),
});
$("#send").on({
click: () => $("#msg").focus(),
});
var suggestionObserver = new MutationObserver(
(mutations) => mutations.forEach(updateSuggestionCss)
).observe($("#suggestions")[0], { childList: true });
}
function defaultValues() {
setTimeout(() => {
let timestamp = getCookie("timestamp")
if (timestamp !== "") {
showTimestamp(timestamp === "true")
}
}, 500);
}
window.addEventListener("onresize", updateSuggestionCss);
window.addEventListener("load", () => {
setNotifyBox();
setupWebSocket();
startGo();
setupEvents();
defaultValues();
// Make sure name is focused on start
$("#name").focus();
});

6
MovieNight/static/js/flv.min.js vendored Executable file

File diff suppressed because one or more lines are too long

4
MovieNight/static/js/jquery.js vendored Executable file

File diff suppressed because one or more lines are too long

15
MovieNight/static/js/ractive.min.js vendored Executable file

File diff suppressed because one or more lines are too long

17
MovieNight/static/js/video.js Executable file
View File

@@ -0,0 +1,17 @@
/// <reference path="./both.js" />
function initPlayer() {
if (flvjs.isSupported()) {
var videoElement = document.getElementById("videoElement");
var flvPlayer = flvjs.createPlayer({
type: "flv",
url: "/live"
});
flvPlayer.attachMediaElement(videoElement);
flvPlayer.load();
flvPlayer.play();
}
}
window.addEventListener("load", initPlayer);

533
MovieNight/static/js/wasm_exec.js Executable file
View File

@@ -0,0 +1,533 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
global.fs = require("fs");
}
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
throw new Error("not implemented");
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
open(path, flags, mode, callback) {
const err = new Error("not implemented");
err.code = "ENOSYS";
callback(err);
},
read(fd, buffer, offset, length, position, callback) {
const err = new Error("not implemented");
err.code = "ENOSYS";
callback(err);
},
fsync(fd, callback) {
callback(null);
},
};
}
if (!global.crypto) {
const nodeCrypto = require("crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder) {
global.TextEncoder = require("util").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("util").TextDecoder;
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
global.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const mem = () => {
// The buffer may change when requesting more memory.
return new DataView(this._inst.exports.mem.buffer);
}
const setInt64 = (addr, v) => {
mem().setUint32(addr + 0, v, true);
mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const getInt64 = (addr) => {
const low = mem().getUint32(addr + 0, true);
const high = mem().getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = mem().getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = mem().getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number") {
if (isNaN(v)) {
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 0, true);
return;
}
if (v === 0) {
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 1, true);
return;
}
mem().setFloat64(addr, v, true);
return;
}
switch (v) {
case undefined:
mem().setFloat64(addr, 0, true);
return;
case null:
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 2, true);
return;
case true:
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 3, true);
return;
case false:
mem().setUint32(addr + 4, nanHead, true);
mem().setUint32(addr, 4, true);
return;
}
let ref = this._refs.get(v);
if (ref === undefined) {
ref = this._values.length;
this._values.push(v);
this._refs.set(v, ref);
}
let typeFlag = 0;
switch (typeof v) {
case "string":
typeFlag = 1;
break;
case "symbol":
typeFlag = 2;
break;
case "function":
typeFlag = 3;
break;
}
mem().setUint32(addr + 4, nanHead | typeFlag, true);
mem().setUint32(addr, ref, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
go: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
const code = mem().getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._refs;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = mem().getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func nanotime() int64
"runtime.nanotime": (sp) => {
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": (sp) => {
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
));
mem().setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
const id = mem().getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
crypto.getRandomValues(loadSlice(sp + 8));
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 56, result);
mem().setUint8(sp + 64, 1);
} catch (err) {
storeValue(sp + 56, err);
mem().setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 40, result);
mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
mem().setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp(); // see comment above
storeValue(sp + 40, result);
mem().setUint8(sp + 48, 1);
} catch (err) {
storeValue(sp + 40, err);
mem().setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array)) {
mem().setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
mem().setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array)) {
mem().setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
mem().setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
this._inst = instance;
this._values = [ // TODO: garbage collection
NaN,
0,
null,
true,
false,
global,
this,
];
this._refs = new Map();
this.exited = false;
const mem = new DataView(this._inst.exports.mem.buffer)
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
const keys = Object.keys(this.env).sort();
argvPtrs.push(keys.length);
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
const argv = offset;
argvPtrs.forEach((ptr) => {
mem.setUint32(offset, ptr, true);
mem.setUint32(offset + 4, 0, true);
offset += 8;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length < 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", (code) => { // Node.js exits if no event handler is pending
if (code === 0 && !go.exited) {
// deadlock, make Go print error and stack traces
go._pendingEvent = { id: 0 };
go._resume();
}
});
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();

122
MovieNight/static/main.html Executable file
View File

@@ -0,0 +1,122 @@
{{define "header"}}
{{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 }}
</script>
<style>
.root {
display: grid;
}
</style>
{{end}}
{{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}}
<style>
#videoElement {
height: 99vh;
}
</style>
{{end}}
{{end}}
{{if and .Video .Chat}}
<style>
.root {
grid-template-columns: 5fr 1fr;
}
</style>
{{end}}
{{end}}
{{define "body"}}
{{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}}
{{if .Chat}}
<div id="loadingFiles" class="contrast">
Files Loading
</div>
<div id="chatwindow">
<div id="notifyBox"></div>
<div id="chat" style="display: none;">
<div id="optionBox">Please hover to view options</div>
<div id="hidden">
<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();" 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 />
<label class="contrast">
<input type="checkbox" checked="false" onchange="setTimestamp(this.checked);" />
Show Timestamp
</label>
<hr />
<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 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 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 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">
</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>
<div id="messages" class="scrollbar"></div>
<div id="msgbox">
<div id="suggestions" class="scrollbar" style="display: none;"></div>
<textarea id="msg"></textarea>
</div>
<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>
</div>
<div id="joinbox">
<div style="color: #e5e0e5; text-align: center;">Please enter your name<br />to join the chat</div>
<div>
<input id="name" type="text" maxlength="36">
<input id="join" type="button" class="button pretty-button" onclick="join();" value="Join" />
{{if .Video}}
<div style="display: flex; flex-direction: column; align-items: center;">
<a href="/chat">Chat Only</a>
<a href="/video">Video Only</a>
<a href="/video" onclick="window.open('/chat', '_blank');">Both Separatly</a>
</div>
{{end}}
</div>
</div>
</div>
{{end}}
{{end}}

11
MovieNight/static/thedoor.html Executable file
View File

@@ -0,0 +1,11 @@
{{define "header"}}{{end}}
{{define "body"}}
<div id="doorentry">
{{if .Notice}}<div class="doornotice">{{.Notice}}</div>{{end}}
<form action="/" method="post">
<input type="text" name="txtInput" /><br />
<input type="submit" value="{{.SubmitText}}" class="button pretty-button" />
</form>
</div>
{{end}}