archive
This commit is contained in:
46
MovieNight/scrapedagain/main/.gitignore
vendored
Executable file
46
MovieNight/scrapedagain/main/.gitignore
vendored
Executable file
@@ -0,0 +1,46 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.aseprite
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# GoCode debug file
|
||||
debug
|
||||
|
||||
# Linux binary
|
||||
MovieNight
|
||||
|
||||
# Windows binary
|
||||
MovieNight.exe
|
||||
|
||||
# Darwin binary
|
||||
MovieNightDarwin
|
||||
|
||||
# Twitch channel info
|
||||
static/subscriber.json
|
||||
|
||||
# This file now holds the stream key. Don't include it.
|
||||
settings.json
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
|
||||
# Autobuilt wasm files
|
||||
static/main.wasm
|
||||
|
||||
# tags for vim
|
||||
tags
|
||||
|
||||
# channel and emote list from twitch
|
||||
subscribers.json
|
||||
10
MovieNight/scrapedagain/main/.travis.yml
Executable file
10
MovieNight/scrapedagain/main/.travis.yml
Executable file
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
|
||||
before_install:
|
||||
- make get
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
18
MovieNight/scrapedagain/main/Dockerfile
Executable file
18
MovieNight/scrapedagain/main/Dockerfile
Executable file
@@ -0,0 +1,18 @@
|
||||
FROM frolvlad/alpine-glibc:alpine-3.9_glibc-2.29
|
||||
|
||||
RUN apk update \
|
||||
&& apk add --no-cache \
|
||||
ca-certificates \
|
||||
ffmpeg \
|
||||
bash
|
||||
|
||||
RUN mkdir -p /var/log
|
||||
WORKDIR /main
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV GOPATH=""
|
||||
ENV MNT="/mnt/"
|
||||
ENTRYPOINT ["/main/MovieNight"]
|
||||
CMD []
|
||||
|
||||
53
MovieNight/scrapedagain/main/Makefile
Executable file
53
MovieNight/scrapedagain/main/Makefile
Executable file
@@ -0,0 +1,53 @@
|
||||
# If a different version of Go is installed (via `go get`) set the GO_VERSION
|
||||
# environment variable to that version. For example, setting it to "1.13.7"
|
||||
# will run `go1.13.7 build [...]` instead of `go build [...]`.
|
||||
#
|
||||
# For info on installing extra versions, see this page:
|
||||
# https://golang.org/doc/install#extra_versions
|
||||
|
||||
TAGS=
|
||||
|
||||
# Windows needs the .exe extension.
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXT=.exe
|
||||
endif
|
||||
|
||||
.PHONY: fmt vet get clean dev setdev test ServerMovieNight
|
||||
|
||||
all: fmt vet test MovieNight$(EXT) static/main.wasm settings.json
|
||||
|
||||
# Build the server deployment
|
||||
server: ServerMovieNight static/main.wasm
|
||||
|
||||
# Bulid used for deploying to my server.
|
||||
ServerMovieNight: *.go common/*.go
|
||||
GOOS=linux GOARCH=386 go$(GO_VERSION) build -o MovieNight $(TAGS)
|
||||
|
||||
setdev:
|
||||
$(eval export TAGS=-tags "dev")
|
||||
|
||||
dev: setdev all
|
||||
|
||||
MovieNight$(EXT): *.go common/*.go
|
||||
go$(GO_VERSION) build -o $@ $(TAGS)
|
||||
|
||||
static/main.wasm: wasm/*.go common/*.go
|
||||
GOOS=js GOARCH=wasm go$(GO_VERSION) build -o $@ $(TAGS) wasm/*.go
|
||||
|
||||
clean:
|
||||
-rm MovieNight$(EXT) ./static/main.wasm
|
||||
|
||||
fmt:
|
||||
gofmt -w .
|
||||
|
||||
vet:
|
||||
go$(GO_VERSION) vet $(TAGS) ./...
|
||||
GOOS=js GOARCH=wasm go$(GO_VERSION) vet $(TAGS) ./...
|
||||
|
||||
test:
|
||||
go$(GO_VERSION) test $(TAGS) ./...
|
||||
|
||||
# Do not put settings_example.json here as a prereq to avoid overwriting
|
||||
# the settings if the example is updated.
|
||||
settings.json:
|
||||
cp settings_example.json settings.json
|
||||
52
MovieNight/scrapedagain/main/connection.go
Executable file
52
MovieNight/scrapedagain/main/connection.go
Executable file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
|
||||
type chatConnection struct {
|
||||
*websocket.Conn
|
||||
mutex sync.RWMutex
|
||||
forwardedFor string
|
||||
clientName string
|
||||
}
|
||||
|
||||
func (cc *chatConnection) ReadData(data interface{}) error {
|
||||
cc.mutex.RLock()
|
||||
defer cc.mutex.RUnlock()
|
||||
|
||||
stats.msgInInc()
|
||||
return cc.ReadJSON(data)
|
||||
}
|
||||
|
||||
func (cc *chatConnection) WriteData(data interface{}) error {
|
||||
cc.mutex.Lock()
|
||||
defer cc.mutex.Unlock()
|
||||
|
||||
stats.msgOutInc()
|
||||
err := cc.WriteJSON(data)
|
||||
if err != nil {
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
common.LogDebugln("OpError: " + operr.Err.Error())
|
||||
}
|
||||
return fmt.Errorf("Error writing data to %s %s: %v", cc.clientName, cc.Host(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *chatConnection) Host() string {
|
||||
if len(cc.forwardedFor) > 0 {
|
||||
return cc.forwardedFor
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(cc.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return cc.RemoteAddr().String()
|
||||
}
|
||||
return host
|
||||
}
|
||||
239
MovieNight/scrapedagain/main/emotes.go
Executable file
239
MovieNight/scrapedagain/main/emotes.go
Executable file
@@ -0,0 +1,239 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
|
||||
const emoteDir = "./static/emotes/"
|
||||
|
||||
type TwitchUser struct {
|
||||
ID string
|
||||
Login string
|
||||
}
|
||||
|
||||
type EmoteInfo struct {
|
||||
ID int
|
||||
Code string
|
||||
}
|
||||
|
||||
func loadEmotes() error {
|
||||
//fmt.Println(processEmoteDir(emoteDir))
|
||||
newEmotes, err := processEmoteDir(emoteDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
common.Emotes = newEmotes
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processEmoteDir(path string) (common.EmotesMap, error) {
|
||||
dirInfo, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not open emoteDir:")
|
||||
}
|
||||
|
||||
subDirs := []string{}
|
||||
|
||||
for _, item := range dirInfo {
|
||||
// Get first level subdirs (eg, "twitch", "discord", etc)
|
||||
if item.IsDir() {
|
||||
subDirs = append(subDirs, item.Name())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
em := common.NewEmotesMap()
|
||||
// Find top level emotes
|
||||
em, err = findEmotes(path, em)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not findEmotes() in top level directory:")
|
||||
}
|
||||
|
||||
// Get second level subdirs (eg, "twitch", "zorchenhimer", etc)
|
||||
for _, dir := range subDirs {
|
||||
subd, err := ioutil.ReadDir(filepath.Join(path, dir))
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading dir %q: %v\n", subd, err)
|
||||
continue
|
||||
}
|
||||
for _, d := range subd {
|
||||
if d.IsDir() {
|
||||
//emotes = append(emotes, findEmotes(filepath.Join(path, dir, d.Name()))...)
|
||||
p := filepath.Join(path, dir, d.Name())
|
||||
em, err = findEmotes(p, em)
|
||||
if err != nil {
|
||||
fmt.Printf("Error finding emotes in %q: %v\n", p, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("processEmoteDir: %d\n", len(em))
|
||||
return em, nil
|
||||
}
|
||||
|
||||
func findEmotes(dir string, em common.EmotesMap) (common.EmotesMap, error) {
|
||||
//em := NewEmotesMap()
|
||||
|
||||
fmt.Printf("finding emotes in %q\n", dir)
|
||||
emotePNGs, err := filepath.Glob(filepath.Join(dir, "*.png"))
|
||||
if err != nil {
|
||||
return em, fmt.Errorf("unable to glob emote directory: %s\n", err)
|
||||
}
|
||||
fmt.Printf("%d emotePNGs\n", len(emotePNGs))
|
||||
|
||||
emoteGIFs, err := filepath.Glob(filepath.Join(dir, "*.gif"))
|
||||
if err != nil {
|
||||
return em, errors.Wrap(err, "unable to glob emote directory:")
|
||||
}
|
||||
fmt.Printf("%d emoteGIFs\n", len(emoteGIFs))
|
||||
|
||||
for _, file := range emotePNGs {
|
||||
em = em.Add(file)
|
||||
//emotes = append(emotes, common.Emote{FullPath: dir, Code: file})
|
||||
}
|
||||
|
||||
for _, file := range emoteGIFs {
|
||||
em = em.Add(file)
|
||||
}
|
||||
|
||||
return em, nil
|
||||
}
|
||||
|
||||
func getEmotes(names []string) error {
|
||||
users := getUserIDs(names)
|
||||
users = append(users, TwitchUser{ID: "0", Login: "twitch"})
|
||||
|
||||
for _, user := range users {
|
||||
emotes, cheers, err := getChannelEmotes(user.ID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get emote data for \"%s\"", user.ID)
|
||||
}
|
||||
|
||||
emoteUserDir := filepath.Join(emoteDir, "twitch", user.Login)
|
||||
if _, err := os.Stat(emoteUserDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(emoteUserDir, os.ModePerm)
|
||||
}
|
||||
|
||||
for _, emote := range emotes {
|
||||
if !strings.ContainsAny(emote.Code, `:;\[]|?&`) {
|
||||
filePath := filepath.Join(emoteUserDir, emote.Code+".png")
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
|
||||
return errors.Wrapf(err, "could not create emote file in path \"%s\":", filePath)
|
||||
}
|
||||
|
||||
err = downloadEmote(emote.ID, file)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not download emote %s:", emote.Code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for amount, sizes := range cheers {
|
||||
name := fmt.Sprintf("%sCheer%s.gif", user.Login, amount)
|
||||
filePath := filepath.Join(emoteUserDir, name)
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not create emote file in path \"%s\":", filePath)
|
||||
}
|
||||
|
||||
err = downloadCheerEmote(sizes["4"], file)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not download emote %s:", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserIDs(names []string) []TwitchUser {
|
||||
logins := strings.Join(names, "&login=")
|
||||
request, err := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/helix/users?login=%s", logins), nil)
|
||||
if err != nil {
|
||||
log.Fatalln("Error generating new request:", err)
|
||||
}
|
||||
request.Header.Set("Client-ID", settings.TwitchClientID)
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
log.Fatalln("Error sending request:", err)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
type userResponse struct {
|
||||
Data []TwitchUser
|
||||
}
|
||||
var data userResponse
|
||||
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
log.Fatalln("Error decoding data:", err)
|
||||
}
|
||||
|
||||
return data.Data
|
||||
}
|
||||
|
||||
func getChannelEmotes(ID string) ([]EmoteInfo, map[string]map[string]string, error) {
|
||||
resp, err := http.Get("https://api.twitchemotes.com/api/v4/channels/" + ID)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not get emotes")
|
||||
}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
||||
type EmoteResponse struct {
|
||||
Emotes []EmoteInfo
|
||||
Cheermotes map[string]map[string]string
|
||||
}
|
||||
var data EmoteResponse
|
||||
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not decode emotes")
|
||||
}
|
||||
|
||||
return data.Emotes, data.Cheermotes, nil
|
||||
}
|
||||
|
||||
func downloadEmote(ID int, file *os.File) error {
|
||||
resp, err := http.Get(fmt.Sprintf("https://static-cdn.jtvnw.net/emoticons/v1/%d/3.0", ID))
|
||||
if err != nil {
|
||||
return errors.Errorf("could not download emote file %s: %v", file.Name(), err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return errors.Errorf("could not save emote: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadCheerEmote(url string, file *os.File) error {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return errors.Errorf("could not download cheer file %s: %v", file.Name(), err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
_, err = io.Copy(file, resp.Body)
|
||||
if err != nil {
|
||||
return errors.Errorf("could not save cheer: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
48
MovieNight/scrapedagain/main/errors.go
Executable file
48
MovieNight/scrapedagain/main/errors.go
Executable file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func errorName(err error) string {
|
||||
return reflect.ValueOf(err).Type().Name()
|
||||
}
|
||||
|
||||
// UserNameError is a base error for errors that deal with user names
|
||||
type UserNameError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// UserFormatError is an error for when the name format does not match what is required
|
||||
type UserFormatError UserNameError
|
||||
|
||||
func (e UserFormatError) Error() string {
|
||||
return fmt.Sprintf("\"%s\", is in an invalid format", e.Name)
|
||||
}
|
||||
|
||||
// UserTakenError is an error for when a user tries to join with a name that is already taken
|
||||
type UserTakenError UserNameError
|
||||
|
||||
func (e UserTakenError) Error() string {
|
||||
return fmt.Sprintf("\"%s\", is already taken", e.Name)
|
||||
}
|
||||
|
||||
// BannedUserError is an error for when a user tries to join with a banned ip address
|
||||
type BannedUserError struct {
|
||||
Host, Name string
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (e BannedUserError) Error() string {
|
||||
return fmt.Sprintf("banned user tried to connect with IP %s: %s (banned with name(s) %s)", e.Host, e.Name, strings.Join(e.Names, ", "))
|
||||
}
|
||||
|
||||
func newBannedUserError(host, name string, names []string) BannedUserError {
|
||||
return BannedUserError{
|
||||
Host: host,
|
||||
Name: name,
|
||||
Names: names,
|
||||
}
|
||||
}
|
||||
BIN
MovieNight/scrapedagain/main/favicon.png
Executable file
BIN
MovieNight/scrapedagain/main/favicon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
23
MovieNight/scrapedagain/main/go.mod
Executable file
23
MovieNight/scrapedagain/main/go.mod
Executable file
@@ -0,0 +1,23 @@
|
||||
module github.com/zorchenhimer/MovieNight
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.12 // indirect
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20190412020601-c4267f5c421a // indirect
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
||||
github.com/gorilla/sessions v1.1.3
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 // indirect
|
||||
github.com/nareix/joy4 v0.0.0-20181022032202-3ddbc8f9d431
|
||||
github.com/ory/dockertest v3.3.4+incompatible // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.1 // indirect
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f // indirect
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d
|
||||
)
|
||||
102
MovieNight/scrapedagain/main/go.sum
Executable file
102
MovieNight/scrapedagain/main/go.sum
Executable file
@@ -0,0 +1,102 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY=
|
||||
github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/chromedp/cdproto v0.0.0-20190217000753-2d8e8962ceb2 h1:4Ck8YOuS0G3+0xMb80cDSff7QpUolhSc0PGyfagbcdA=
|
||||
github.com/chromedp/cdproto v0.0.0-20190217000753-2d8e8962ceb2/go.mod h1:xquOK9dIGFlLaIGI4c6IyfLI/Gz0LiYYuJtzhsUODgI=
|
||||
github.com/chromedp/cdproto v0.0.0-20190412020601-c4267f5c421a/go.mod h1:xquOK9dIGFlLaIGI4c6IyfLI/Gz0LiYYuJtzhsUODgI=
|
||||
github.com/chromedp/chromedp v0.1.3 h1:Nkqt42/7tvzg57mexc4LbM8nZbx7vSZ+eiUpeczGGL8=
|
||||
github.com/chromedp/chromedp v0.1.3/go.mod h1:ZahQlJx8YBfDtuFN80zn6P7fskSotBkdhgKDoLWFANk=
|
||||
github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac h1:PThQaO4yCvJzJBUW1XoFQxLotWRhvX2fgljJX8yrhFI=
|
||||
github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dennwc/dom v0.3.0 h1:u89+QvT1OPRSSTFf54o9RuK7C0Uoq2jFo4VCa4rnjtI=
|
||||
github.com/dennwc/dom v0.3.0/go.mod h1:/z5w9Stx19m8RUwolsmsqTs9rDxKgJO5T9UEumilgk4=
|
||||
github.com/dennwc/testproxy v1.0.1 h1:mQhNVWHPolTYjJrDZYKcugIplWRSlFAis6k/Zf1s0c0=
|
||||
github.com/dennwc/testproxy v1.0.1/go.mod h1:EHGV9tzWhMPLmEoVJ2KGyC149XqwKZwBDViCjhKD5d8=
|
||||
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
|
||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU=
|
||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307 h1:vl4eIlySbjertFaNwiMjXsGrFVK25aOWLq7n+3gh2ls=
|
||||
github.com/knq/sysutil v0.0.0-20181215143952-f05b59f0f307/go.mod h1:BjPj+aVjl9FW/cCGiF3nGh5v+9Gd3VCgBQbod/GlMaQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190221075403-6243d8e04c3f h1:B6PQkurxGG1rqEX96oE14gbj8bqvYC5dtks9r5uGmlE=
|
||||
github.com/mailru/easyjson v0.0.0-20190221075403-6243d8e04c3f/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/nareix/joy4 v0.0.0-20181022032202-3ddbc8f9d431 h1:nWhrOsCKdV6bivw03k7MROF2tYzCFGfYBYFrTEHyucs=
|
||||
github.com/nareix/joy4 v0.0.0-20181022032202-3ddbc8f9d431/go.mod h1:aFJ1ZwLjvHN4yEzE5Bkz8rD8/d8Vlj3UIuvz2yfET7I=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||
github.com/ory/dockertest v3.3.2+incompatible h1:uO+NcwH6GuFof/Uz8yzjNi1g0sGT5SLAJbdBvD8bUYc=
|
||||
github.com/ory/dockertest v3.3.2+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
||||
github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8 h1:YoY1wS6JYVRpIfFngRf2HHo9R9dAne3xbkGOQ5rJXjU=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
378
MovieNight/scrapedagain/main/handlers.go
Executable file
378
MovieNight/scrapedagain/main/handlers.go
Executable file
@@ -0,0 +1,378 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/nareix/joy4/av/avutil"
|
||||
"github.com/nareix/joy4/av/pubsub"
|
||||
"github.com/nareix/joy4/format/flv"
|
||||
"github.com/nareix/joy4/format/rtmp"
|
||||
)
|
||||
|
||||
var (
|
||||
// Read/Write mutex for rtmp stream
|
||||
l = NewSuperLock()
|
||||
|
||||
// Map of active streams
|
||||
channels = map[string]*Channel{}
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
que *pubsub.Queue
|
||||
}
|
||||
|
||||
type writeFlusher struct {
|
||||
httpflusher http.Flusher
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (self writeFlusher) Flush() error {
|
||||
self.httpflusher.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serving static files
|
||||
func wsStaticFiles(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/favicon.ico":
|
||||
http.ServeFile(w, r, "./favicon.png")
|
||||
return
|
||||
case "/justvideo":
|
||||
http.ServeFile(w, r, "./static/justvideo.html")
|
||||
return
|
||||
}
|
||||
|
||||
goodPath := r.URL.Path[8:len(r.URL.Path)]
|
||||
common.LogDebugf("[static] serving %q from folder ./static/\n", goodPath)
|
||||
|
||||
http.ServeFile(w, r, "./static/"+goodPath)
|
||||
}
|
||||
|
||||
func wsWasmFile(w http.ResponseWriter, r *http.Request) {
|
||||
if settings.NoCache {
|
||||
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
|
||||
}
|
||||
common.LogDebugln("[static] serving wasm file")
|
||||
http.ServeFile(w, r, "./static/main.wasm")
|
||||
}
|
||||
|
||||
func wsImages(w http.ResponseWriter, r *http.Request) {
|
||||
base := filepath.Base(r.URL.Path)
|
||||
common.LogDebugln("[img] ", base)
|
||||
http.ServeFile(w, r, "./static/img/"+base)
|
||||
}
|
||||
|
||||
func wsEmotes(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, path.Join("./static/", r.URL.Path))
|
||||
}
|
||||
|
||||
// Handling the websocket
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool { return true }, //not checking origin
|
||||
}
|
||||
|
||||
//this is also the handler for joining to the chat
|
||||
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("ws handler")
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
common.LogErrorln("Error upgrading to websocket:", err)
|
||||
return
|
||||
}
|
||||
|
||||
common.LogDebugln("Connection has been upgraded to websocket")
|
||||
|
||||
go func() {
|
||||
// Handle incomming messages
|
||||
for {
|
||||
var data common.ClientData
|
||||
err := conn.ReadJSON(&data)
|
||||
if err != nil { //if error then assuming that the connection is closed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
// returns if it's OK to proceed
|
||||
func checkRoomAccess(w http.ResponseWriter, r *http.Request) bool {
|
||||
session, err := sstore.Get(r, "moviesession")
|
||||
if err != nil {
|
||||
// Don't return as server error here, just make a new session.
|
||||
common.LogErrorf("Unable to get session for client %s: %v\n", r.RemoteAddr, err)
|
||||
}
|
||||
|
||||
if settings.RoomAccess == AccessPin {
|
||||
pin := session.Values["pin"]
|
||||
// No pin found in session
|
||||
if pin == nil || len(pin.(string)) == 0 {
|
||||
if r.Method == "POST" {
|
||||
// Check for correct pin
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
common.LogErrorf("Error parsing form")
|
||||
http.Error(w, "Unable to get session data", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
postPin := strings.TrimSpace(r.Form.Get("txtInput"))
|
||||
common.LogDebugf("Received pin: %s\n", postPin)
|
||||
if postPin == settings.RoomAccessPin {
|
||||
// Pin is correct. Save it to session and return true.
|
||||
session.Values["pin"] = settings.RoomAccessPin
|
||||
session.Save(r, w)
|
||||
return true
|
||||
}
|
||||
// Pin is incorrect.
|
||||
handlePinTemplate(w, r, "Incorrect PIN")
|
||||
return false
|
||||
}
|
||||
// nope. display pin entry and return
|
||||
handlePinTemplate(w, r, "")
|
||||
return false
|
||||
}
|
||||
|
||||
// Pin found in session, but it has changed since last time.
|
||||
if pin.(string) != settings.RoomAccessPin {
|
||||
// Clear out the old pin.
|
||||
session.Values["pin"] = nil
|
||||
session.Save(r, w)
|
||||
|
||||
// Prompt for new one.
|
||||
handlePinTemplate(w, r, "Pin has changed. Enter new PIN.")
|
||||
return false
|
||||
}
|
||||
|
||||
// Correct pin found in session
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: this.
|
||||
if settings.RoomAccess == AccessRequest {
|
||||
http.Error(w, "Requesting access not implemented yet", http.StatusNotImplemented)
|
||||
return false
|
||||
}
|
||||
|
||||
// Room is open.
|
||||
return true
|
||||
}
|
||||
|
||||
func handlePinTemplate(w http.ResponseWriter, r *http.Request, errorMessage string) {
|
||||
log.Println("handle pin temp")
|
||||
type Data struct {
|
||||
Title string
|
||||
SubmitText string
|
||||
Notice string
|
||||
}
|
||||
|
||||
if errorMessage == "" {
|
||||
errorMessage = "Please enter the PIN"
|
||||
}
|
||||
|
||||
data := Data{
|
||||
Title: "Enter Pin",
|
||||
SubmitText: "Submit Pin",
|
||||
Notice: errorMessage,
|
||||
}
|
||||
|
||||
err := common.ExecuteServerTemplate(w, "pin", data)
|
||||
if err != nil {
|
||||
common.LogErrorf("Error executing file, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handleHelpTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
func handleEmoteTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("handle emote temp")
|
||||
type Data struct {
|
||||
Title string
|
||||
Emotes map[string]string
|
||||
}
|
||||
|
||||
data := Data{
|
||||
Title: "Available Emotes",
|
||||
Emotes: common.Emotes,
|
||||
}
|
||||
|
||||
err := common.ExecuteServerTemplate(w, "emotes", data)
|
||||
if err != nil {
|
||||
common.LogErrorf("Error executing file, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePin(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("handle pin")
|
||||
session, err := sstore.Get(r, "moviesession")
|
||||
if err != nil {
|
||||
common.LogDebugf("Unable to get session: %v\n", err)
|
||||
}
|
||||
|
||||
val := session.Values["pin"]
|
||||
if val == nil {
|
||||
session.Values["pin"] = "1234"
|
||||
err := session.Save(r, w)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "unable to save session: %v", err)
|
||||
}
|
||||
fmt.Fprint(w, "Pin was not set")
|
||||
common.LogDebugln("pin was not set")
|
||||
} else {
|
||||
fmt.Fprintf(w, "pin set: %v", val)
|
||||
common.LogDebugf("pin is set: %v\n", val)
|
||||
}
|
||||
}
|
||||
|
||||
func handleIndexTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("handle ind temp")
|
||||
if settings.RoomAccess != AccessOpen {
|
||||
if !checkRoomAccess(w, r) {
|
||||
common.LogDebugln("Denied access")
|
||||
return
|
||||
}
|
||||
common.LogDebugln("Granted access")
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Video, Chat bool
|
||||
MessageHistoryCount int
|
||||
Title string
|
||||
}
|
||||
|
||||
data := Data{
|
||||
Video: true,
|
||||
Chat: true,
|
||||
MessageHistoryCount: settings.MaxMessageCount,
|
||||
Title: "Movie Night!",
|
||||
}
|
||||
|
||||
path := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")
|
||||
if path[0] == "video" {
|
||||
data.Chat = false
|
||||
data.Title += " - video"
|
||||
}
|
||||
|
||||
// Force browser to replace cache since file was not changed
|
||||
if settings.NoCache {
|
||||
w.Header().Set("Cache-Control", "no-cache, must-revalidate")
|
||||
}
|
||||
|
||||
err := common.ExecuteServerTemplate(w, "main", data)
|
||||
if err != nil {
|
||||
common.LogErrorf("Error executing file, %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePublish(conn *rtmp.Conn) {
|
||||
log.Println("handle publish")
|
||||
streams, _ := conn.Streams()
|
||||
|
||||
l.Lock()
|
||||
common.LogDebugln("request string->", conn.URL.RequestURI())
|
||||
urlParts := strings.Split(strings.Trim(conn.URL.RequestURI(), "/"), "/")
|
||||
common.LogDebugln("urlParts->", urlParts)
|
||||
|
||||
if len(urlParts) > 2 {
|
||||
common.LogErrorln("Extra garbage after stream key")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
if len(urlParts) != 2 {
|
||||
common.LogErrorln("Missing stream key")
|
||||
return
|
||||
}
|
||||
|
||||
if urlParts[1] != settings.GetStreamKey() {
|
||||
common.LogErrorln("Stream key is incorrect. Denying stream.")
|
||||
return //If key not match, deny stream
|
||||
}
|
||||
*/
|
||||
|
||||
streamPath := urlParts[0]
|
||||
ch := channels[streamPath]
|
||||
if ch == nil {
|
||||
ch = &Channel{}
|
||||
ch.que = pubsub.NewQueue()
|
||||
ch.que.WriteHeader(streams)
|
||||
channels[streamPath] = ch
|
||||
} else {
|
||||
ch = nil
|
||||
}
|
||||
l.Unlock()
|
||||
if ch == nil {
|
||||
common.LogErrorln("Unable to start stream, channel is nil.")
|
||||
return
|
||||
}
|
||||
|
||||
stats.startStream()
|
||||
|
||||
common.LogInfoln("Stream started")
|
||||
avutil.CopyPackets(ch.que, conn)
|
||||
common.LogInfoln("Stream finished")
|
||||
|
||||
stats.endStream()
|
||||
|
||||
l.Lock()
|
||||
delete(channels, streamPath)
|
||||
l.Unlock()
|
||||
ch.que.Close()
|
||||
}
|
||||
|
||||
func handlePlay(conn *rtmp.Conn) {
|
||||
log.Println("handle play")
|
||||
l.RLock()
|
||||
ch := channels[conn.URL.Path]
|
||||
l.RUnlock()
|
||||
|
||||
if ch != nil {
|
||||
cursor := ch.que.Latest()
|
||||
avutil.CopyFile(conn, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func handleDefault(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("handle def")
|
||||
l.RLock()
|
||||
ch := channels[strings.Trim(r.URL.Path, "/")]
|
||||
l.RUnlock()
|
||||
|
||||
if ch != nil {
|
||||
l.StartStream()
|
||||
defer l.StopStream()
|
||||
|
||||
w.Header().Set("Content-Type", "video/x-flv")
|
||||
w.Header().Set("Transfer-Encoding", "chunked")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.WriteHeader(200)
|
||||
flusher := w.(http.Flusher)
|
||||
flusher.Flush()
|
||||
|
||||
muxer := flv.NewMuxerWriteFlusher(writeFlusher{httpflusher: flusher, Writer: w})
|
||||
cursor := ch.que.Latest()
|
||||
|
||||
avutil.CopyFile(muxer, cursor)
|
||||
} else {
|
||||
if r.URL.Path != "/" {
|
||||
// not really an error for the server, but for the client.
|
||||
common.LogInfoln("[http 404] ", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
} else {
|
||||
handleIndexTemplate(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
169
MovieNight/scrapedagain/main/main.go
Executable file
169
MovieNight/scrapedagain/main/main.go
Executable file
@@ -0,0 +1,169 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/nareix/joy4/format"
|
||||
"github.com/nareix/joy4/format/rtmp"
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
|
||||
var (
|
||||
pullEmotes bool
|
||||
addr string
|
||||
sKey string
|
||||
stats = newStreamStats()
|
||||
)
|
||||
|
||||
func setupSettings() error {
|
||||
var err error
|
||||
settings, err = LoadSettings("settings.json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load settings: %s", err)
|
||||
}
|
||||
if len(settings.StreamKey) == 0 {
|
||||
return fmt.Errorf("Missing stream key is settings.json")
|
||||
}
|
||||
|
||||
sstore = sessions.NewCookieStore([]byte(settings.SessionKey))
|
||||
sstore.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: 60 * 60 * 24, // one day
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&addr, "l", "", "host:port of the MovieNight")
|
||||
flag.StringVar(&sKey, "k", "", "Stream key, to protect your stream")
|
||||
flag.BoolVar(&pullEmotes, "e", false, "Pull emotes")
|
||||
flag.Parse()
|
||||
|
||||
format.RegisterAll()
|
||||
|
||||
if err := setupSettings(); err != nil {
|
||||
fmt.Printf("Error loading settings: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if pullEmotes {
|
||||
common.LogInfoln("Pulling emotes")
|
||||
err := getEmotes(settings.ApprovedEmotes)
|
||||
if err != nil {
|
||||
common.LogErrorf("Error downloading emotes: %+v\n", err)
|
||||
common.LogErrorf("Error downloading emotes: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if err := common.InitTemplates(); err != nil {
|
||||
common.LogErrorln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
exit := make(chan bool)
|
||||
go handleInterrupt(exit)
|
||||
|
||||
if addr == "" {
|
||||
addr = settings.ListenAddress
|
||||
}
|
||||
|
||||
if addr[0] != ':' {
|
||||
addr = ":" + addr
|
||||
}
|
||||
|
||||
// A stream key was passed on the command line. Use it, but don't save
|
||||
// it over the stream key in the settings.json file.
|
||||
if sKey != "" {
|
||||
settings.SetTempKey(sKey)
|
||||
}
|
||||
|
||||
common.LogInfoln("Stream key: ", settings.GetStreamKey())
|
||||
common.LogInfoln("Admin password: ", settings.AdminPassword)
|
||||
common.LogInfoln("Listen and serve ", addr)
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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("/video", handleIndexTemplate)
|
||||
http.HandleFunc("/help", handleHelpTemplate)
|
||||
http.HandleFunc("/pin", handlePin)
|
||||
http.HandleFunc("/emotes", handleEmoteTemplate)
|
||||
|
||||
http.HandleFunc("/", handleDefault)
|
||||
|
||||
http.HandleFunc("/pls/restart", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/pls/restart/soft", http.StatusSeeOther)
|
||||
})
|
||||
|
||||
http.HandleFunc("/pls/restart/soft", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `I'm on the case. Give me 30 seconds. Love you <3`)
|
||||
go func() {
|
||||
killStream()
|
||||
l = NewSuperLock()
|
||||
}()
|
||||
})
|
||||
http.HandleFunc("/pls/restart/hard", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `I'm on the case. Give me 2 minutes. Love you <3`)
|
||||
go func() {
|
||||
rebootCam()
|
||||
time.Sleep(time.Second * 60)
|
||||
killStream()
|
||||
l = NewSuperLock()
|
||||
}()
|
||||
})
|
||||
|
||||
go rtsp()
|
||||
|
||||
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()
|
||||
}
|
||||
permaKillStream() // todo
|
||||
exit <- true
|
||||
}
|
||||
63
MovieNight/scrapedagain/main/notes.txt
Executable file
63
MovieNight/scrapedagain/main/notes.txt
Executable file
@@ -0,0 +1,63 @@
|
||||
== TODO
|
||||
|
||||
- break long words across lines
|
||||
|
||||
- mod commands
|
||||
- auth command to gain mod status
|
||||
- kick/mute/timeout
|
||||
- list users
|
||||
- purge chat
|
||||
- mods cannot kick/ban other mods or admin
|
||||
- only admin can kick/ban mods
|
||||
- admin revoke command with password
|
||||
- broadcast mod/unmod command results to mods and admins
|
||||
- fix /color for mods and admins
|
||||
|
||||
- "login" options
|
||||
- IP admin/mod?
|
||||
- save ip/name combo for reconnects?
|
||||
|
||||
- Move kick/ban core functionality into command instead of room?
|
||||
or to (server-side) client?
|
||||
|
||||
- add a Chatroom.FindUser(name) function
|
||||
|
||||
- rewrite Javascript to accept json data.
|
||||
- separate data into commands and chat
|
||||
- commands will just execute more JS (eg, changing title)
|
||||
- chat will append chat message
|
||||
- moves all styling to client
|
||||
|
||||
- rewrite javascript client in go webasm?
|
||||
|
||||
== Commands
|
||||
/color
|
||||
change user color
|
||||
/me
|
||||
italic chat message without leading colon. message is the same color as name.
|
||||
/count
|
||||
display the number of users in chat
|
||||
/w
|
||||
/whoami
|
||||
debugging command. prints name, mod, and admin status
|
||||
/auth
|
||||
authenticate to admin
|
||||
|
||||
= Mod commands
|
||||
/playing [title] [link]
|
||||
update title and link. clears title if no arguments
|
||||
/sv <message>
|
||||
server announcement message. it's red, with a red border, centered in chat.
|
||||
/kick
|
||||
kick user from chat
|
||||
/unmod
|
||||
unmod self only
|
||||
|
||||
= Admin commands
|
||||
/reloademotes
|
||||
reload emotes map
|
||||
/reloadplayer
|
||||
reloads the video player of everybody in chat
|
||||
/unmod <name>
|
||||
unmod a user
|
||||
/mod <name> mod a user
|
||||
62
MovieNight/scrapedagain/main/readme.md
Executable file
62
MovieNight/scrapedagain/main/readme.md
Executable file
@@ -0,0 +1,62 @@
|
||||
# MovieNight stream server
|
||||
|
||||
[](https://travis-ci.org/zorchenhimer/MovieNight)
|
||||
|
||||
This is a single-instance streaming server with chat. Originally written to
|
||||
replace Rabbit as the platform for watching movies with a group of people
|
||||
online.
|
||||
|
||||
## Build requirements
|
||||
|
||||
- Go 1.12 or newer
|
||||
- GNU Make
|
||||
|
||||
## Install
|
||||
|
||||
To just download and run:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/zorchenhimer/MovieNight
|
||||
$ cd MovieNight
|
||||
$ make
|
||||
$ ./MovieNight
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Now you can use OBS to push a stream to the server. Set the stream URL to
|
||||
|
||||
```text
|
||||
rtmp://your.domain.host/live
|
||||
```
|
||||
|
||||
and enter the stream key.
|
||||
|
||||
Now you can view the stream at
|
||||
|
||||
```text
|
||||
http://your.domain.host:8089/
|
||||
```
|
||||
|
||||
There is a video only version at
|
||||
|
||||
```text
|
||||
http://your.domain.host:8089/video
|
||||
```
|
||||
|
||||
and a chat only version at
|
||||
|
||||
```text
|
||||
http://your.domain.host:8089/chat
|
||||
```
|
||||
|
||||
The default listen port is `:8089`. It can be changed by providing a new port
|
||||
at startup:
|
||||
|
||||
```text
|
||||
Usage of .\MovieNight.exe:
|
||||
-k string
|
||||
Stream key, to protect your stream
|
||||
-l string
|
||||
host:port of the MovieNight (default ":8089")
|
||||
```
|
||||
131
MovieNight/scrapedagain/main/rtsp.go
Executable file
131
MovieNight/scrapedagain/main/rtsp.go
Executable file
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var rtspCmd *exec.Cmd
|
||||
var done bool
|
||||
|
||||
func rtsp() {
|
||||
install := exec.Command("bash", "-c", `
|
||||
if ! which ffmpeg; then
|
||||
apk add --no-cache ffmpeg \
|
||||
|| sudo apk add --no-cache ffmpeg \
|
||||
|| (apt update; apt -y install ffmpeg) \
|
||||
|| (apt -y update; apt -y install ffmpeg) \
|
||||
|| (sudo apt -y update; sudo apt -y install ffmpeg) \
|
||||
|| (apt-get update; apt-get -y install ffmpeg) \
|
||||
|| (apt-get -y update; apt-get -y install ffmpeg) \
|
||||
|| (sudo apt-get -y update; sudo apt-get -y install ffmpeg) \
|
||||
|| true
|
||||
fi
|
||||
if ! which ffmpeg; then
|
||||
exit 499
|
||||
fi
|
||||
`)
|
||||
if err := install.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for !done {
|
||||
rtspCmd = exec.Command("bash", "-c", `
|
||||
exec ffmpeg \
|
||||
-hide_banner \
|
||||
-loglevel quiet \
|
||||
-i rtsp://${RTSP_IP:-192.168.0.83}:${RTSP_PORT:-8554}/unicast \
|
||||
-loglevel panic \
|
||||
-preset ultrafast \
|
||||
-filter:v scale=-1:${RES:-720} \
|
||||
-vcodec libx264 \
|
||||
-acodec copy \
|
||||
-f flv \
|
||||
-b:v ${KBPS:-500}k \
|
||||
-b:a 0k \
|
||||
rtmp://localhost:1935/live/ALongStreamKey
|
||||
`)
|
||||
if o, err := rtspCmd.StdoutPipe(); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
go io.Copy(os.Stdout, o)
|
||||
}
|
||||
if o, err := rtspCmd.StderrPipe(); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
go io.Copy(os.Stderr, o)
|
||||
}
|
||||
log.Println("starting rtsp cmd", rtspCmd)
|
||||
if err := rtspCmd.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Second * 15)
|
||||
log.Println("starting stream initially")
|
||||
startStream()
|
||||
log.Println("stopping stream initially")
|
||||
stopStream()
|
||||
log.Println("waiting rtsp cmd")
|
||||
log.Println(rtspCmd.Wait())
|
||||
}
|
||||
}
|
||||
|
||||
func startStream() {
|
||||
signalStream(syscall.Signal(unix.SIGCONT))
|
||||
}
|
||||
|
||||
func stopStream() {
|
||||
signalStream(syscall.Signal(unix.SIGSTOP))
|
||||
}
|
||||
|
||||
func killStream() {
|
||||
signalStream(syscall.Signal(unix.SIGKILL))
|
||||
}
|
||||
|
||||
func permaKillStream() {
|
||||
done = true
|
||||
killStream()
|
||||
}
|
||||
|
||||
func signalStream(s syscall.Signal) {
|
||||
for rtspCmd == nil {
|
||||
log.Println("rtspCmdis nil")
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
for rtspCmd.Process == nil {
|
||||
log.Println("rtspCmd.Process is nil")
|
||||
time.Sleep(time.Second * 3)
|
||||
}
|
||||
rtspCmd.Process.Signal(os.Signal(s))
|
||||
}
|
||||
|
||||
func rebootCam() {
|
||||
c := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
|
||||
host := "192.168.0.83"
|
||||
if h, ok := os.LookupEnv("RTSP_IP"); ok {
|
||||
host = h
|
||||
}
|
||||
r, err := http.NewRequest("GET", "https://"+host+"/cgi-bin/action.cgi?cmd=reboot", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pass := "fwees123"
|
||||
if p, ok := os.LookupEnv("RTSP_PASS"); ok {
|
||||
pass = p
|
||||
}
|
||||
r.SetBasicAuth("root", pass)
|
||||
resp, err := c.Do(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
panic(resp.StatusCode)
|
||||
}
|
||||
}
|
||||
313
MovieNight/scrapedagain/main/settings.go
Executable file
313
MovieNight/scrapedagain/main/settings.go
Executable file
@@ -0,0 +1,313 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
|
||||
var settings *Settings
|
||||
var sstore *sessions.CookieStore
|
||||
|
||||
type Settings struct {
|
||||
// Non-Saved settings
|
||||
filename string
|
||||
cmdLineKey string // stream key from the command line
|
||||
|
||||
// Saved settings
|
||||
StreamStats bool
|
||||
MaxMessageCount int
|
||||
TitleLength int // maximum length of the title that can be set with the /playing
|
||||
AdminPassword string
|
||||
StreamKey string
|
||||
ListenAddress string
|
||||
ApprovedEmotes []string // list of channels that have been approved for emote use. Global emotes are always "approved".
|
||||
TwitchClientID string // client id from twitch developers portal
|
||||
SessionKey string // key for session data
|
||||
Bans []BanInfo
|
||||
LogLevel common.LogLevel
|
||||
LogFile string
|
||||
RoomAccess AccessMode
|
||||
RoomAccessPin string // The current pin
|
||||
NewPin bool // Auto generate a new pin on start. Overwrites RoomAccessPin if set.
|
||||
|
||||
// Rate limiting stuff, in seconds
|
||||
RateLimitChat time.Duration
|
||||
RateLimitNick time.Duration
|
||||
RateLimitColor time.Duration
|
||||
RateLimitAuth time.Duration
|
||||
RateLimitDuplicate time.Duration // Amount of seconds between allowed duplicate messages
|
||||
|
||||
// Send the NoCache header?
|
||||
NoCache bool
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type AccessMode string
|
||||
|
||||
const (
|
||||
AccessOpen AccessMode = "open"
|
||||
AccessPin AccessMode = "pin"
|
||||
AccessRequest AccessMode = "request"
|
||||
)
|
||||
|
||||
type BanInfo struct {
|
||||
IP string
|
||||
Names []string
|
||||
When time.Time
|
||||
}
|
||||
|
||||
func LoadSettings(filename string) (*Settings, error) {
|
||||
raw, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading file: %s", err)
|
||||
}
|
||||
|
||||
var s *Settings
|
||||
err = json.Unmarshal(raw, &s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling: %s", err)
|
||||
}
|
||||
s.filename = filename
|
||||
|
||||
if err = common.SetupLogging(s.LogLevel, s.LogFile); err != nil {
|
||||
return nil, fmt.Errorf("Unable to setup logger: %s", err)
|
||||
}
|
||||
|
||||
// have a default of 200
|
||||
if s.MaxMessageCount == 0 {
|
||||
s.MaxMessageCount = 300
|
||||
} else if s.MaxMessageCount < 0 {
|
||||
return s, fmt.Errorf("value for MaxMessageCount must be greater than 0, given %d", s.MaxMessageCount)
|
||||
}
|
||||
|
||||
s.AdminPassword, err = generatePass(time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate admin password: %s", err)
|
||||
}
|
||||
|
||||
if s.RateLimitChat == -1 {
|
||||
s.RateLimitChat = 0
|
||||
} else if s.RateLimitChat <= 0 {
|
||||
s.RateLimitChat = 1
|
||||
}
|
||||
|
||||
if s.RateLimitNick == -1 {
|
||||
s.RateLimitNick = 0
|
||||
} else if s.RateLimitNick <= 0 {
|
||||
s.RateLimitNick = 300
|
||||
}
|
||||
|
||||
if s.RateLimitColor == -1 {
|
||||
s.RateLimitColor = 0
|
||||
} else if s.RateLimitColor <= 0 {
|
||||
s.RateLimitColor = 60
|
||||
}
|
||||
|
||||
if s.RateLimitAuth == -1 {
|
||||
s.RateLimitAuth = 0
|
||||
} else if s.RateLimitAuth <= 0 {
|
||||
s.RateLimitAuth = 5
|
||||
}
|
||||
|
||||
if s.RateLimitDuplicate == -1 {
|
||||
s.RateLimitDuplicate = 0
|
||||
} else if s.RateLimitDuplicate <= 0 {
|
||||
s.RateLimitDuplicate = 30
|
||||
}
|
||||
|
||||
// Print this stuff before we multiply it by time.Second
|
||||
common.LogInfof("RateLimitChat: %v", s.RateLimitChat)
|
||||
common.LogInfof("RateLimitNick: %v", s.RateLimitNick)
|
||||
common.LogInfof("RateLimitColor: %v", s.RateLimitColor)
|
||||
common.LogInfof("RateLimitAuth: %v", s.RateLimitAuth)
|
||||
|
||||
if len(s.RoomAccess) == 0 {
|
||||
s.RoomAccess = AccessOpen
|
||||
}
|
||||
|
||||
if (s.RoomAccess != AccessOpen && len(s.RoomAccessPin) == 0) || s.NewPin {
|
||||
pin, err := s.generateNewPin()
|
||||
if err != nil {
|
||||
common.LogErrorf("Unable to generate new pin: %v", err)
|
||||
}
|
||||
common.LogInfof("New pin generated: %s", pin)
|
||||
}
|
||||
|
||||
// Don't use LogInfof() here. Log isn't setup yet when LoadSettings() is called from init().
|
||||
fmt.Printf("Settings reloaded. New admin password: %s\n", s.AdminPassword)
|
||||
|
||||
if s.TitleLength <= 0 {
|
||||
s.TitleLength = 50
|
||||
}
|
||||
|
||||
// Is this a good way to do this? Probably not...
|
||||
if len(s.SessionKey) == 0 {
|
||||
out := ""
|
||||
large := big.NewInt(int64(1 << 60))
|
||||
large = large.Add(large, large)
|
||||
for len(out) < 50 {
|
||||
num, err := rand.Int(rand.Reader, large)
|
||||
if err != nil {
|
||||
panic("Error generating session key: " + err.Error())
|
||||
}
|
||||
out = fmt.Sprintf("%s%X", out, num)
|
||||
}
|
||||
s.SessionKey = out
|
||||
}
|
||||
|
||||
// Save admin password to file
|
||||
if err = s.Save(); err != nil {
|
||||
return nil, fmt.Errorf("Unable to save settings: %s", err)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func generatePass(seed int64) (string, error) {
|
||||
out := ""
|
||||
for len(out) < 20 {
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(15)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
out = fmt.Sprintf("%s%X", out, num)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Settings) Save() error {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
return s.unlockedSave()
|
||||
}
|
||||
|
||||
// unlockedSave expects the calling function to lock the RWMutex
|
||||
func (s *Settings) unlockedSave() error {
|
||||
marshaled, err := json.MarshalIndent(s, "", "\t")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(s.filename, marshaled, 0777)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Settings) AddBan(host string, names []string) error {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
if host == "127.0.0.1" {
|
||||
return fmt.Errorf("Cannot add a ban for localhost.")
|
||||
}
|
||||
|
||||
b := BanInfo{
|
||||
Names: names,
|
||||
IP: host,
|
||||
When: time.Now(),
|
||||
}
|
||||
s.Bans = append(s.Bans, b)
|
||||
|
||||
common.LogInfof("[BAN] %q (%s) has been banned.\n", strings.Join(names, ", "), host)
|
||||
|
||||
return s.unlockedSave()
|
||||
}
|
||||
|
||||
func (s *Settings) RemoveBan(name string) error {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
name = strings.ToLower(name)
|
||||
newBans := []BanInfo{}
|
||||
for _, b := range s.Bans {
|
||||
for _, n := range b.Names {
|
||||
if n == name {
|
||||
common.LogInfof("[ban] Removed ban for %s [%s]\n", b.IP, n)
|
||||
} else {
|
||||
newBans = append(newBans, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Bans = newBans
|
||||
return s.unlockedSave()
|
||||
}
|
||||
|
||||
func (s *Settings) IsBanned(host string) (bool, []string) {
|
||||
defer s.lock.RUnlock()
|
||||
s.lock.RLock()
|
||||
|
||||
for _, b := range s.Bans {
|
||||
if b.IP == host {
|
||||
return true, b.Names
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Settings) SetTempKey(key string) {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
s.cmdLineKey = key
|
||||
}
|
||||
|
||||
func (s *Settings) GetStreamKey() string {
|
||||
defer s.lock.RUnlock()
|
||||
s.lock.RLock()
|
||||
|
||||
if len(s.cmdLineKey) > 0 {
|
||||
return s.cmdLineKey
|
||||
}
|
||||
return s.StreamKey
|
||||
}
|
||||
|
||||
func (s *Settings) generateNewPin() (string, error) {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
num, err := rand.Int(rand.Reader, big.NewInt(int64(9999)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.RoomAccessPin = fmt.Sprintf("%04d", num)
|
||||
if err = s.unlockedSave(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return s.RoomAccessPin, nil
|
||||
}
|
||||
|
||||
func (s *Settings) AddApprovedEmotes(channels []string) error {
|
||||
defer s.lock.Unlock()
|
||||
s.lock.Lock()
|
||||
|
||||
approved := map[string]int{}
|
||||
for _, e := range s.ApprovedEmotes {
|
||||
approved[e] = 1
|
||||
}
|
||||
|
||||
for _, name := range channels {
|
||||
approved[name] = 1
|
||||
}
|
||||
|
||||
filtered := []string{}
|
||||
for key, _ := range approved {
|
||||
filtered = append(filtered, key)
|
||||
}
|
||||
|
||||
s.ApprovedEmotes = filtered
|
||||
return s.unlockedSave()
|
||||
}
|
||||
18
MovieNight/scrapedagain/main/settings_example.json
Executable file
18
MovieNight/scrapedagain/main/settings_example.json
Executable file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"MaxMessageCount": 300,
|
||||
"TitleLength": 50,
|
||||
"AdminPassword": "",
|
||||
"Bans": [],
|
||||
"StreamKey": "ALongStreamKey",
|
||||
"ListenAddress": ":8089",
|
||||
"ApprovedEmotes": null,
|
||||
"Bans": [],
|
||||
"LogLevel": "debug",
|
||||
"LogFile": "thelog.log",
|
||||
"RateLimitChat": 1,
|
||||
"RateLimitNick": 300,
|
||||
"RateLimitColor": 60,
|
||||
"RateLimitAuth": 5,
|
||||
"RateLimitDuplicate": 30,
|
||||
"NoCache": false
|
||||
}
|
||||
85
MovieNight/scrapedagain/main/stats.go
Executable file
85
MovieNight/scrapedagain/main/stats.go
Executable file
@@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/zorchenhimer/MovieNight/common"
|
||||
)
|
||||
|
||||
type streamStats struct {
|
||||
messageIn int
|
||||
messageOut int
|
||||
maxUsers int
|
||||
start time.Time
|
||||
mutex sync.Mutex
|
||||
|
||||
streamStart time.Time
|
||||
streamLive bool // True if live
|
||||
}
|
||||
|
||||
func newStreamStats() streamStats {
|
||||
return streamStats{start: time.Now(), streamLive: false}
|
||||
}
|
||||
|
||||
func (s *streamStats) msgInInc() {
|
||||
s.mutex.Lock()
|
||||
s.messageIn++
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (s *streamStats) msgOutInc() {
|
||||
s.mutex.Lock()
|
||||
s.messageOut++
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (s *streamStats) updateMaxUsers(count int) {
|
||||
s.mutex.Lock()
|
||||
if count > s.maxUsers {
|
||||
s.maxUsers = count
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (s *streamStats) getMaxUsers() int {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
return s.maxUsers
|
||||
}
|
||||
|
||||
func (s *streamStats) Print() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
common.LogInfof("Messages In: %d\n", s.messageIn)
|
||||
common.LogInfof("Messages Out: %d\n", s.messageOut)
|
||||
common.LogInfof("Max users in chat: %d\n", s.maxUsers)
|
||||
common.LogInfof("Total Time: %s\n", time.Since(s.start))
|
||||
}
|
||||
|
||||
func (s *streamStats) startStream() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.streamLive = true
|
||||
s.streamStart = time.Now()
|
||||
}
|
||||
|
||||
func (s *streamStats) endStream() {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.streamLive = false
|
||||
}
|
||||
|
||||
func (s *streamStats) getStreamLength() time.Duration {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if !s.streamLive {
|
||||
return 0
|
||||
}
|
||||
return time.Since(s.streamStart)
|
||||
}
|
||||
78
MovieNight/scrapedagain/main/superlock.go
Executable file
78
MovieNight/scrapedagain/main/superlock.go
Executable file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SuperLock struct {
|
||||
self *sync.Mutex
|
||||
lock *sync.RWMutex
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
func NewSuperLock() *SuperLock {
|
||||
return &SuperLock{
|
||||
sem: make(chan struct{}, 100),
|
||||
self: &sync.Mutex{},
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (sl *SuperLock) RLock() {
|
||||
log.Println("sl.rlock...")
|
||||
sl.lock.RLock()
|
||||
}
|
||||
|
||||
func (sl *SuperLock) RUnlock() {
|
||||
log.Println("sl.runlock")
|
||||
sl.lock.RUnlock()
|
||||
}
|
||||
|
||||
func (sl *SuperLock) Lock() {
|
||||
log.Println("sl.lock...")
|
||||
sl.lock.Lock()
|
||||
}
|
||||
|
||||
func (sl *SuperLock) Unlock() {
|
||||
log.Println("sl.unlock")
|
||||
sl.lock.Unlock()
|
||||
}
|
||||
|
||||
func (sl *SuperLock) StartStream() {
|
||||
log.Println("sl.startstream")
|
||||
sl.self.Lock()
|
||||
defer sl.self.Unlock()
|
||||
|
||||
select {
|
||||
case sl.sem <- struct{}{}:
|
||||
case <-time.After(time.Second * 3):
|
||||
log.Println("timed out getting semaphore to start stream")
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("CONT STREAM", len(sl.sem))
|
||||
startStream()
|
||||
}
|
||||
|
||||
func (sl *SuperLock) StopStream() {
|
||||
log.Println("sl.stopstream")
|
||||
sl.self.Lock()
|
||||
defer sl.self.Unlock()
|
||||
|
||||
select {
|
||||
case <-sl.sem:
|
||||
case <-time.After(time.Second * 3):
|
||||
log.Println("timed out getting semaphore to stop stream")
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("STOP STREAM", len(sl.sem))
|
||||
if len(sl.sem) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("REALLY DO STOP STREAM", len(sl.sem))
|
||||
stopStream()
|
||||
}
|
||||
Reference in New Issue
Block a user