Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Your New SJW Waifu 2020-06-09 12:56:44 -05:00
commit 79f6f7f32c
12 changed files with 293 additions and 135 deletions

View File

@ -109,8 +109,9 @@ func (cr *ChatRoom) Join(conn *chatConnection, data common.JoinData) (*Client, e
} else { } else {
client.Send(playingCommand) client.Send(playingCommand)
} }
cr.AddEventMsg(common.EvJoin, data.Name, data.Color) if !settings.LetThemLurk {
cr.AddEventMsg(common.EvJoin, data.Name, data.Color)
}
sendHiddenMessage(common.CdJoin, nil) sendHiddenMessage(common.CdJoin, nil)
sendHiddenMessage(common.CdEmote, common.Emotes) sendHiddenMessage(common.CdEmote, common.Emotes)
@ -135,7 +136,9 @@ func (cr *ChatRoom) Leave(name, color string) {
client.conn.Close() client.conn.Close()
cr.delClient(id) cr.delClient(id)
cr.AddEventMsg(common.EvLeave, name, color) if !settings.LetThemLurk {
cr.AddEventMsg(common.EvLeave, name, color)
}
common.LogChatf("[leave] %s %s\n", host, name) common.LogChatf("[leave] %s %s\n", host, name)
} }

View File

@ -18,32 +18,115 @@ func init() {
// the values in colors must be lowercase so it matches with the color input // 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 // this saves from having to call strings.ToLower(color) every time to check
var Colors = []string{ var Colors = []string{
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "aliceblue",
"beige", "bisque", "blanchedalmond", "blueviolet", "brown", "antiquewhite",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "aqua",
"cornflowerblue", "cornsilk", "crimson", "cyan", "darkcyan", "aquamarine",
"darkgoldenrod", "darkgray", "darkkhaki", "darkmagenta", "darkolivegreen", "azure",
"darkorange", "darkorchid", "darksalmon", "darkseagreen", "darkslateblue", "beige",
"darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "bisque",
"dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "blanchedalmond",
"fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "burlywood",
"gray", "greenyellow", "honeydew", "hotpink", "indigo", "cadetblue",
"ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "chartreuse",
"lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "chocolate",
"lightgrey", "lightgreen", "lightpink", "lightsalmon", "lightseagreen", "coral",
"lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime", "cornflowerblue",
"limegreen", "linen", "magenta", "mediumaquamarine", "mediumorchid", "cornsilk",
"mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "cyan",
"mediumvioletred", "mintcream", "mistyrose", "moccasin", "navajowhite", "darkcyan",
"oldlace", "olive", "olivedrab", "orange", "orangered", "darkgoldenrod",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "darkgray",
"papayawhip", "peachpuff", "peru", "pink", "plum", "darkkhaki",
"powderblue", "purple", "rebeccapurple", "red", "rosybrown", "darkorange",
"royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "darksalmon",
"seashell", "sienna", "silver", "skyblue", "slateblue", "darkseagreen",
"slategray", "snow", "springgreen", "steelblue", "tan", "darkturquoise",
"teal", "thistle", "tomato", "turquoise", "violet", "deeppink",
"wheat", "white", "whitesmoke", "yellow", "yellowgreen", "deepskyblue",
"dodgerblue",
"floralwhite",
"fuchsia",
"gainsboro",
"ghostwhite",
"gold",
"goldenrod",
"gray",
"greenyellow",
"honeydew",
"hotpink",
"ivory",
"khaki",
"lavender",
"lavenderblush",
"lawngreen",
"lemonchiffon",
"lightblue",
"lightcoral",
"lightcyan",
"lightgoldenrodyellow",
"lightgreen",
"lightgrey",
"lightpink",
"lightsalmon",
"lightseagreen",
"lightskyblue",
"lightslategray",
"lightsteelblue",
"lightyellow",
"lime",
"limegreen",
"linen",
"magenta",
"mediumaquamarine",
"mediumorchid",
"mediumpurple",
"mediumseagreen",
"mediumslateblue",
"mediumspringgreen",
"mediumturquoise",
"mintcream",
"mistyrose",
"moccasin",
"navajowhite",
"oldlace",
"olive",
"olivedrab",
"orange",
"orangered",
"orchid",
"palegoldenrod",
"palegreen",
"paleturquoise",
"palevioletred",
"papayawhip",
"peachpuff",
"peru",
"pink",
"plum",
"powderblue",
"red",
"rosybrown",
"salmon",
"sandybrown",
"seagreen",
"seashell",
"silver",
"skyblue",
"slategray",
"snow",
"springgreen",
"steelblue",
"tan",
"thistle",
"tomato",
"turquoise",
"violet",
"wheat",
"white",
"whitesmoke",
"yellow",
"yellowgreen",
} }
var ( var (

View File

@ -168,6 +168,7 @@ func getUserIDs(names []string) []TwitchUser {
log.Fatalln("Error generating new request:", err) log.Fatalln("Error generating new request:", err)
} }
request.Header.Set("Client-ID", settings.TwitchClientID) request.Header.Set("Client-ID", settings.TwitchClientID)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", settings.TwitchClientSecret))
client := http.Client{} client := http.Client{}
resp, err := client.Do(request) resp, err := client.Do(request)

View File

@ -328,7 +328,7 @@ func handleIndexTemplate(w http.ResponseWriter, r *http.Request) {
Video: true, Video: true,
Chat: true, Chat: true,
MessageHistoryCount: settings.MaxMessageCount, MessageHistoryCount: settings.MaxMessageCount,
Title: "Movie Night!", Title: settings.PageTitle,
} }
path := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/") path := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")

127
readme.md
View File

@ -1,3 +1,19 @@
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
- [MovieNight stream server](#movienight-stream-server)
- [Build requirements](#build-requirements)
- [Older Go Versions](#older-go-versions)
- [Compile and install](#compile-and-install)
- [Docker build](#docker-build)
- [Building the Container](#building-the-container)
- [Running the Container](#running-the-container)
- [docker-compose](#docker-compose)
- [Notes for Running Using docker-compose](#notes-for-running-using-docker-compose)
- [Usage](#usage)
- [Configuration](#configuration)
<!-- markdown-toc end -->
# MovieNight stream server # MovieNight stream server
[![Build status](https://api.travis-ci.org/zorchenhimer/MovieNight.svg?branch=master)](https://travis-ci.org/zorchenhimer/MovieNight) [![Build status](https://api.travis-ci.org/zorchenhimer/MovieNight.svg?branch=master)](https://travis-ci.org/zorchenhimer/MovieNight)
@ -20,7 +36,7 @@ Once you have that setup add an enviromnent variable named `GO_VERSION` and
set it to the version you installed (eg, `1.14.1`). The Makefile will now use set it to the version you installed (eg, `1.14.1`). The Makefile will now use
the newer version. the newer version.
## Install ### Compile and install
To just download and run: To just download and run:
@ -31,6 +47,43 @@ $ make
$ ./MovieNight $ ./MovieNight
``` ```
### Docker build
MovieNight provides a Dockerfile and a docker-compose file to run MovieNight using Docker.
#### Building the Container
Install Docker, clone the repository and build:
```shell
docker build -t movienight .
```
#### Running the Container
Run the image once it's built:
```shell
docker run -d -p 8089:8089 -p 1935:1935 [-v ./settings.json:/config/settings.json] movienight
```
Explanation:
- **-d** runs the container in the background.
- **-p 8089:8089** maps the MovieNight web interface to port 8089 on the server.
- **-p 1935:1935** maps the RTMP port for OBS to port 1935 (default RTMP port) on the server.
- **-v ./settings.json:/config/settings.json** maps the file *settings.json* into the container. [OPTIONAL]
#### docker-compose
docker-compose will automatically build the image, no need to build it manually.
Install Docker and docker-compose, clone the repository and change into the directory *./docker*. Then run:
```shell
docker-compose up -d
```
This docker-compose file will create a volume called *movienight-config* and automatically add the standard *settings.json* file to it. It also maps port 8089 and 1935 to the same ports of the host.
#### Notes for Running Using docker-compose
The container needs to be restarted to apply any changes you make to *settings.json*.
## Usage ## Usage
Now you can use OBS to push a stream to the server. Set the stream URL to Now you can use OBS to push a stream to the server. Set the stream URL to
@ -70,40 +123,42 @@ Usage of .\MovieNight.exe:
host:port of the MovieNight (default ":8089") host:port of the MovieNight (default ":8089")
``` ```
## Docker ## Configuration
MovieNight provides a Dockerfile and a docker-compose file to run MovieNight using Docker.
### Dockerfile MovieNights configuration is controlled by `settings.json`:
#### Building the Container
Install Docker, clone the repository and build:
```shell - `AdminPassword`: users can enter `/auth <value>` into chat to grant themselves
docker build -t movienight . admin privileges. This value is automatically regenerated unless
``` `RegenAdminPass` is false.
- `ApprovedEmotes`: list of Twitch users whose emotes can be imported into
#### Running the Container MovieNight. Using `/addemotes <username>` in chat will add to this list.
Run the image once it's built: - `Bans`: list of banned users.
- `LetThemLurk`: if false, announces when a user enters and leaves chat.
```shell - `ListenAddress`: the port that MovieNight listens on, formatted as `:8089`.
docker run -d -p 8089:8089 -p 1935:1935 [-v ./settings.json:/config/settings.json] movienight - `LogFile`: the path of the MovieNight logfile, relative to the executable.
``` - `LogLevel`: the log level, defaults to `debug`.
- `MaxMessageCount`: the number of messages displayed in the chat window.
Explanation: - `NewPin`: if true, regenerates `RoomAccessPin` when the server starts.
- **-d** runs the container in the background. - `PageTitle`: The base string used in the `<title>` element of the page. When
- **-p 8089:8089** maps the MovieNight web interface to port 8089 on the server. the stream title is set with `/playing`, it is appended; e.g., `Movie Night | The Man Who Killed Hitler and Then the Bigfoot`
- **-p 1935:1935** maps the RTMP port for OBS to port 1935 (default RTMP port) on the server. - `RegenAdminPass`: if true, regenerates `AdminPassword` when the server starts.
- **-v ./settings.json:/config/settings.json** maps the file *settings.json* into the container. [OPTIONAL] - `RoomAccess`: the access policy of the chat room; this is managed by the
application and should not be edited manually.
### docker-compose - `RoomAccessPin`: if set, serves as the password required to enter the chatroom.
docker-compose will automatically build the image, no need to build it manually. - `SessionKey`: key used for storing session data (cookies etc.)
- `StreamKey`: the key that OBS will use to connect to MovieNight.
Install Docker and docker-compose, clone the repository and change into the directory *./docker*. Then run: - `StreamStats`: if true, prints statistics for the stream on server shutdown.
- `TitleLength`: the maximum allowed length for the stream title (set with `/playing`).
```shell - `TwitchClientID`: OAuth client ID for the Twitch API, used for fetching emotes
docker-compose up -d - `TwitchClientSecret`: OAuth client secret for the Twitch API; [can be generated locally with curl](https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow).
``` - `WrappedEmotesOnly`: if true, requires that emote codes be wrapped in colons
or brackets; e.g., `:PogChamp:`
This docker-compose file will create a volume called *movienight-config* and automatically add the standard *settings.json* file to it. It also maps port 8089 and 1935 to the same ports of the host. - `RateLimitChat`: the number of seconds between each message a non-privileged
user can post in chat.
#### Notes for Running Using docker-compose - `RateLimitNick`: the number of seconds before a user can change their nick again.
The container needs to be restarted to apply any changes you make to *settings.json*. - `RakeLimitColor`: the number of seconds before a user can change their color again.
- `RateLimitAuth`: the number of seconds between each allowed auth attempt
- `RateLimitDuplicate`: the numeber of seconds before a user can post a
duplicate message.
- `NoCache`: if true, set `Cache-Control: no-cache, must-revalidate` in the HTTP
header, to prevent caching responses.

View File

@ -23,24 +23,26 @@ type Settings struct {
cmdLineKey string // stream key from the command line cmdLineKey string // stream key from the command line
// Saved settings // Saved settings
StreamStats bool AdminPassword string
MaxMessageCount int ApprovedEmotes []string // list of channels that have been approved for emote use. Global emotes are always "approved".
TitleLength int // maximum length of the title that can be set with the /playing Bans []BanInfo
AdminPassword string LetThemLurk bool // whether or not to announce users joining/leaving chat
RegenAdminPass bool // regenerate admin password on start? ListenAddress string
StreamKey string LogFile string
ListenAddress string LogLevel common.LogLevel
ApprovedEmotes []string // list of channels that have been approved for emote use. Global emotes are always "approved". MaxMessageCount int
TwitchClientID string // client id from twitch developers portal NewPin bool // Auto generate a new pin on start. Overwrites RoomAccessPin if set.
SessionKey string // key for session data PageTitle string // primary value for the page <title> element
Bans []BanInfo RegenAdminPass bool // regenerate admin password on start?
LogLevel common.LogLevel RoomAccess AccessMode
LogFile string RoomAccessPin string // The current pin
RoomAccess AccessMode SessionKey string // key for session data
RoomAccessPin string // The current pin StreamKey string
NewPin bool // Auto generate a new pin on start. Overwrites RoomAccessPin if set. StreamStats bool
TitleLength int // maximum length of the title that can be set with the /playing
WrappedEmotesOnly bool // only allow "wrapped" emotes. eg :Kappa: and [Kappa] but not Kappa TwitchClientID string // client id from twitch developers portal
TwitchClientSecret string // OAuth from twitch developers portal: https://dev.twitch.tv/docs/authentication/getting-tokens-oauth#oauth-client-credentials-flow
WrappedEmotesOnly bool // only allow "wrapped" emotes. eg :Kappa: and [Kappa] but not Kappa
// Rate limiting stuff, in seconds // Rate limiting stuff, in seconds
RateLimitChat time.Duration RateLimitChat time.Duration

View File

@ -1,19 +1,23 @@
{ {
"MaxMessageCount": 300,
"TitleLength": 50,
"AdminPassword": "", "AdminPassword": "",
"RegenAdminPass": true,
"Bans": [],
"StreamKey": "ALongStreamKey",
"ListenAddress": ":8089",
"ApprovedEmotes": null, "ApprovedEmotes": null,
"LogLevel": "debug", "Bans": [],
"LetThemLurk": false,
"ListenAddress": ":8089",
"LogFile": "thelog.log", "LogFile": "thelog.log",
"RateLimitChat": 1, "LogLevel": "debug",
"RateLimitNick": 300, "MaxMessageCount": 300,
"RateLimitColor": 60,
"RateLimitAuth": 5,
"RateLimitDuplicate": 30,
"NoCache": false, "NoCache": false,
"PageTitle": "Movie Night",
"RateLimitAuth": 5,
"RateLimitChat": 1,
"RateLimitColor": 60,
"RateLimitDuplicate": 30,
"RateLimitNick": 300,
"RegenAdminPass": true,
"StreamKey": "ALongStreamKey",
"TitleLength": 50,
"TwitchClientID": "",
"TwitchClientSecret": "",
"WrappedEmotesOnly": false "WrappedEmotesOnly": false
} }

View File

@ -15,9 +15,7 @@
<body class="scrollbar"> <body class="scrollbar">
<img id="remote" src="/static/img/remote.png" onclick="flipRemote();" /> <img id="remote" src="/static/img/remote.png" onclick="flipRemote();" />
<div id="devKeys"></div> <div id="devKeys"></div>
<div class="root"> {{template "body" .}}
{{template "body" .}}
</div>
</body> </body>
</html> </html>

View File

@ -56,12 +56,13 @@ input[type=text] {
font-weight: bold; font-weight: bold;
} }
.root { .grid-root {
max-width: var(--var-max-width); display: grid;
max-height: var(--var-max-height); height: var(--var-max-height);
height: var(--var-max-height); margin: 0px 1vw;
width: var(--var-max-width); max-height: var(--var-max-height);
margin: 0px 1vw; max-width: var(--var-max-width);
width: var(--var-max-width);
} }
.pretty-button { .pretty-button {
@ -110,16 +111,17 @@ input[type=text] {
} }
#emotesbody { #emotesbody {
color: var(--var-message-color); color: var(--var-message-color);
} }
.emotedef { .emotedef {
display: flex; float: left;
flex-direction: row; padding: 5px 5px 15px 5px;
} }
.emotedef div { .emotedef img {
padding: 5px; height: 112px;
width: 112px;
} }
.notice { .notice {
@ -200,7 +202,7 @@ input[type=text] {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: absolute; position: absolute;
background-color: rgb(0, 0, 0, 0.75); background-color: rgba(0, 0, 0, 0.75);
z-index: 3; z-index: 3;
text-align: center; text-align: center;
vertical-align: middle; vertical-align: middle;
@ -400,3 +402,21 @@ input[type=text] {
z-index: 99; z-index: 99;
color: var(--var-contrast-color); color: var(--var-contrast-color);
} }
/* ------------------- */
/* CONDITIONAL CLASSES */
/* ------------------- */
/* ------------- */
/* COMBINED MODE */
/* ------------- */
.combined {
grid-template-columns: 5fr 1fr;
}
/* --------------- */
/* VIDEO ONLY MODE */
/* --------------- */
.video-only #videoElement {
height: 99vh;
}

View File

@ -29,4 +29,4 @@
</dl> </dl>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}

View File

@ -23,10 +23,10 @@ function deleteCookie(cname) {
function setPlaying(title, link) { function setPlaying(title, link) {
if (title !== "") { if (title !== "") {
$('#playing').text(title); $('#playing').text(title);
document.title = "Movie Night | " + title; document.title = pageTitle + " | " + title;
} else { } else {
$('#playing').text(""); $('#playing').text("");
document.title = "Movie Night"; document.title = pageTitle;
} }
$('#playing').removeAttr('href'); $('#playing').removeAttr('href');

View File

@ -1,39 +1,31 @@
{{define "header"}} {{define "header"}}
<script>pageTitle = {{ .Title }}</script>
{{if .Chat}} {{if .Chat}}
<script type="application/javascript" src="/static/js/wasm_exec.js"></script> <script type="application/javascript" src="/static/js/wasm_exec.js"></script>
<script type="application/javascript" src="/static/js/chat.js"></script> <script type="application/javascript" src="/static/js/chat.js"></script>
<script> <script>
maxMessageCount = {{.MessageHistoryCount }} maxMessageCount = {{.MessageHistoryCount }}
</script> </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/flv.min.js"></script>
<script type="application/javascript" src="/static/js/video.js"></script> <script type="application/javascript" src="/static/js/video.js"></script>
{{if not .Chat}}
<style>
#videoElement {
height: 99vh;
}
</style>
{{end}}
{{end}} {{end}}
{{if and .Video .Chat}} {{/* end define header */}}
<style>
.root {
grid-template-columns: 5fr 1fr;
}
</style>
{{end}}
{{end}} {{end}}
{{define "body"}} {{define "body"}}
{{ if and .Chat .Video }}
<div class="grid-root combined">
{{ else if .Video }}
<div class="grid-root video-only">
{{ else }}
<div class="grid-root">
{{ end }}
{{if .Video}} {{if .Video}}
<div id="videoWrapper"> <div id="videoWrapper">
<div id="videoOverlay"> <div id="videoOverlay">
@ -124,4 +116,4 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{end}} {{end}}