906 lines
21 KiB
Go
906 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gorilla/websocket"
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func initUser(channelID string, isModerator bool, isStreamer bool) {
|
|
_, err := conn.Exec("INSERT OR IGNORE INTO users (channelID, points, isModerator, isStreamer) VALUES (?, ?, ?, ?)", channelID, 0, isModerator, isStreamer)
|
|
if err != nil {
|
|
panic("Error inserting user: " + err.Error())
|
|
}
|
|
}
|
|
|
|
func getPoints(channelID string) int64 {
|
|
var points int64
|
|
err := conn.QueryRow("SELECT points FROM users WHERE channelID = ?", channelID).Scan(&points)
|
|
if err != nil {
|
|
panic("Error querying database: " + err.Error())
|
|
}
|
|
|
|
return points
|
|
}
|
|
|
|
func updatePoints(channelID string, points int64) {
|
|
_, err := conn.Exec("UPDATE users SET points = ? WHERE channelID = ?", points, channelID)
|
|
if err != nil {
|
|
panic("Error updating user points: " + err.Error())
|
|
}
|
|
}
|
|
|
|
func addPoints(channelID string, points int64) {
|
|
updatePoints(channelID, getPoints(channelID)+points)
|
|
}
|
|
|
|
func minusPoints(channelID string, points int64) {
|
|
updatePoints(channelID, getPoints(channelID)-points)
|
|
}
|
|
|
|
//goland:noinspection GoUnusedFunction
|
|
func userIsStreamer(channelID string) bool {
|
|
var isStreamer bool
|
|
err := conn.QueryRow("SELECT isStreamer FROM users WHERE channelID = ?", channelID).Scan(&isStreamer)
|
|
if err != nil {
|
|
panic("Error querying database: " + err.Error())
|
|
}
|
|
|
|
return isStreamer
|
|
}
|
|
|
|
func userIsModerator(channelID string) bool {
|
|
var isModerator bool
|
|
err := conn.QueryRow("SELECT isModerator FROM users WHERE channelID = ?", channelID).Scan(&isModerator)
|
|
if err != nil {
|
|
panic("Error querying database: " + err.Error())
|
|
}
|
|
|
|
return isModerator
|
|
}
|
|
|
|
type LiveChatMessages struct {
|
|
Items []Items `json:"items"`
|
|
}
|
|
|
|
type Items struct {
|
|
AutorDetails AuthorDetails `json:"authorDetails"`
|
|
Snippet Snippet `json:"snippet"`
|
|
}
|
|
|
|
type AuthorDetails struct {
|
|
DisplayName string `json:"displayName"`
|
|
ChannelID string `json:"channelId"`
|
|
IsChatOwner bool `json:"isChatOwner"`
|
|
IsChatModerator bool `json:"isChatModerator"`
|
|
}
|
|
|
|
type Snippet struct {
|
|
PublishedAt string `json:"publishedAt"`
|
|
TextMessageDetails TextMessageDetails `json:"textMessageDetails"`
|
|
}
|
|
|
|
type TextMessageDetails struct {
|
|
MessageText string `json:"messageText"`
|
|
}
|
|
|
|
func scrape(liveChatID string) {
|
|
for {
|
|
if streaming == true {
|
|
// Check for chat messages
|
|
println("Scary, we are expending a Google API credit (on stream)!")
|
|
|
|
response, err := http.Get("https://www.googleapis.com/youtube/v3/liveChat/messages?liveChatId=" + liveChatID + "&part=snippet,authorDetails&key=" + configFile.ApiKey)
|
|
if err != nil {
|
|
println("Error getting chat messages: " + err.Error() + ", trying again in 20 seconds")
|
|
time.Sleep(time.Second * 20)
|
|
continue
|
|
}
|
|
|
|
// Read the response
|
|
var responseJSON LiveChatMessages
|
|
err = json.NewDecoder(response.Body).Decode(&responseJSON)
|
|
if err != nil {
|
|
panic("Error decoding JSON: " + err.Error())
|
|
}
|
|
|
|
// Check the status code
|
|
if response.StatusCode != 200 {
|
|
println("Error getting chat messages: ", response.Status)
|
|
return
|
|
} else {
|
|
// Iterate through each live chat message
|
|
for _, item := range responseJSON.Items {
|
|
println("Processing message from ", item.AutorDetails.DisplayName)
|
|
println("Message: ", item.Snippet.TextMessageDetails.MessageText)
|
|
// Check if the message starts with !verify
|
|
if strings.HasPrefix(item.Snippet.TextMessageDetails.MessageText, "!link ") {
|
|
println("User ", item.AutorDetails.DisplayName, " is verifying")
|
|
// Get the channel ID
|
|
channelID := item.AutorDetails.ChannelID
|
|
|
|
// Check if the user is in pendingSessions
|
|
pendingSession, ok := pendingSessions[strings.TrimPrefix(item.Snippet.TextMessageDetails.MessageText, "!link ")]
|
|
if !ok {
|
|
// This user does not need to be verified
|
|
continue
|
|
}
|
|
|
|
// Add the user to the sessions map
|
|
sessions[pendingSession.IP] = channelID
|
|
initUser(channelID, item.AutorDetails.IsChatModerator, item.AutorDetails.IsChatOwner)
|
|
|
|
// Call the event callback
|
|
pendingSession.EventCallback()
|
|
|
|
// Remove the user from the pendingSessions map
|
|
delete(pendingSessions, strings.TrimPrefix("!verify ", item.Snippet.TextMessageDetails.MessageText))
|
|
} else {
|
|
earliestSentMessage, ok := unclaimedMessages[item.AutorDetails.ChannelID]
|
|
publishedTime, err := time.Parse(time.RFC3339Nano, item.Snippet.PublishedAt)
|
|
if err != nil {
|
|
println("Error parsing time: " + err.Error())
|
|
} else if !ok || publishedTime.Before(earliestSentMessage) {
|
|
if publishedTime.After(streamingSince) {
|
|
println("New message from ", item.AutorDetails.DisplayName)
|
|
unclaimedMessages[item.AutorDetails.ChannelID] = publishedTime
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the response body
|
|
err := response.Body.Close()
|
|
if err != nil {
|
|
println("Error closing response body: " + err.Error())
|
|
}
|
|
|
|
// Wait for the rate because Google likes to screw us over
|
|
time.Sleep(time.Second * time.Duration(configFile.Rate))
|
|
}
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func getUserSecondDifference(channelID string) (int64, bool) {
|
|
earliestSentMessage, ok := unclaimedMessages[channelID]
|
|
if !ok {
|
|
return 0, false
|
|
}
|
|
|
|
return int64(time.Now().Sub(earliestSentMessage).Seconds()), true
|
|
}
|
|
|
|
func getLiveChatID(videoID string) string {
|
|
response, err := http.Get("https://www.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=" + videoID + "&key=" + configFile.ApiKey)
|
|
if err != nil {
|
|
panic("Error getting live chat ID: " + err.Error())
|
|
}
|
|
|
|
var responseJSON map[string]interface{}
|
|
err = json.NewDecoder(response.Body).Decode(&responseJSON)
|
|
if err != nil {
|
|
panic("Error decoding JSON: " + err.Error())
|
|
}
|
|
|
|
println(responseJSON)
|
|
|
|
liveChatID := responseJSON["items"].([]interface{})[0].(map[string]interface{})["liveStreamingDetails"].(map[string]interface{})["activeLiveChatId"].(string)
|
|
|
|
return liveChatID
|
|
}
|
|
|
|
type PendingSession struct {
|
|
IP string
|
|
EventCallback func()
|
|
}
|
|
|
|
var (
|
|
conn *sql.DB
|
|
unclaimedMessages = make(map[string]time.Time)
|
|
sessions = make(map[string]string)
|
|
pendingSessions = make(map[string]PendingSession)
|
|
streamingSince time.Time
|
|
configFile Config
|
|
streaming bool
|
|
upgrade = websocket.Upgrader{
|
|
ReadBufferSize: 1024,
|
|
WriteBufferSize: 1024,
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
if r.Header.Get("Origin") == configFile.Origin {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
},
|
|
}
|
|
)
|
|
|
|
func giveBetPoints(correct int) {
|
|
for better, bet := range bets {
|
|
if bet.answer == correct {
|
|
addPoints(better, bet.amount*stakesMultiplier)
|
|
}
|
|
}
|
|
|
|
endBet()
|
|
}
|
|
|
|
func endBet() {
|
|
bettingOpen = false
|
|
question = ""
|
|
possibleAnswers = nil
|
|
timeToBet = time.Unix(0, 0)
|
|
bets = nil
|
|
}
|
|
|
|
func startBet(q string, a []string, d time.Time) {
|
|
bettingOpen = true
|
|
question = q
|
|
possibleAnswers = make(map[int]string)
|
|
for i, answer := range a {
|
|
possibleAnswers[i] = answer
|
|
}
|
|
timeToBet = d
|
|
bets = make(map[string]Bet)
|
|
}
|
|
|
|
var (
|
|
stakesMultiplier int64
|
|
bettingOpen bool
|
|
question string
|
|
possibleAnswers map[int]string
|
|
timeToBet time.Time
|
|
bets map[string]Bet
|
|
)
|
|
|
|
type Bet struct {
|
|
amount int64
|
|
answer int
|
|
}
|
|
|
|
type Config struct {
|
|
ApiKey string `json:"key"`
|
|
Origin string `json:"origin"`
|
|
Rate float64 `json:"rate"`
|
|
Multiplier float64 `json:"multiplier"`
|
|
}
|
|
|
|
func main() {
|
|
// Connect to the database
|
|
var err error
|
|
conn, err = sql.Open("sqlite", "database.db")
|
|
if err != nil {
|
|
panic("Error connecting to database: " + err.Error())
|
|
}
|
|
|
|
// Read in config.json
|
|
configBytes, err := os.ReadFile("config.json")
|
|
if err != nil {
|
|
panic("Error reading config.json: " + err.Error())
|
|
}
|
|
|
|
// Parse the JSON
|
|
err = json.Unmarshal(configBytes, &configFile)
|
|
if err != nil {
|
|
panic("Error parsing config.json: " + err.Error())
|
|
}
|
|
|
|
// Create the user table if it doesn't exist
|
|
_, err = conn.Exec("CREATE TABLE IF NOT EXISTS users (channelID TEXT NOT NULL UNIQUE, points INTEGER NOT NULL, isModerator BOOLEAN NOT NULL DEFAULT FALSE, isStreamer BOOLEAN NOT NULL DEFAULT FALSE)")
|
|
if err != nil {
|
|
panic("Error creating users table: " + err.Error())
|
|
}
|
|
|
|
// Add shounic by default
|
|
_, err = conn.Exec("INSERT OR IGNORE INTO users (channelID, points, isModerator, isStreamer) VALUES ('UCHlTEt24Yb4ylJFuWz7hXIw', 0, 1, 1)")
|
|
if err != nil {
|
|
panic("Error inserting shounic: " + err.Error())
|
|
}
|
|
|
|
// Set up the router
|
|
gin.SetMode(gin.ReleaseMode)
|
|
router := gin.New()
|
|
|
|
// Set up the routes
|
|
router.Static("/static", "./static")
|
|
router.LoadHTMLGlob("./templates/*")
|
|
|
|
// Ugh, why do we have to do this legally
|
|
router.GET("/delete", func(c *gin.Context) {
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Delete everything from every map with the user's channel ID
|
|
delete(unclaimedMessages, id)
|
|
_, err := conn.Exec("DELETE FROM users WHERE channelID = ?", id)
|
|
if err != nil {
|
|
panic("Error deleting user: " + err.Error())
|
|
}
|
|
|
|
// Delete all access tokens with the user's channel ID
|
|
for key, value := range sessions {
|
|
if value == id {
|
|
delete(sessions, key)
|
|
}
|
|
}
|
|
|
|
c.JSON(200, "Data deleted")
|
|
})
|
|
|
|
// Link the user's account
|
|
router.GET("/api/link", func(c *gin.Context) {
|
|
// Kick over to a WebSocket
|
|
conn, err := upgrade.Upgrade(c.Writer, c.Request, nil)
|
|
if err != nil {
|
|
println("Error upgrading connection: " + err.Error())
|
|
}
|
|
|
|
// Create a new nonce
|
|
nonce := make([]byte, 16)
|
|
_, err = rand.Read(nonce)
|
|
if err != nil {
|
|
println("Error generating nonce: " + err.Error())
|
|
}
|
|
|
|
// Base64 encode the nonce
|
|
nonceBase64 := base64.StdEncoding.EncodeToString(nonce)
|
|
|
|
// Set a read deadline
|
|
var read bool
|
|
go func() {
|
|
time.Sleep(time.Second * 5)
|
|
if !read {
|
|
err := conn.WriteJSON(gin.H{
|
|
"type": "error",
|
|
"error": "Did not respond to nonce",
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Send the nonce
|
|
err = conn.WriteJSON(gin.H{
|
|
"type": "nonce",
|
|
"nonce": nonceBase64,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, nonceResponse, err := conn.ReadMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
read = true
|
|
|
|
jsonResponse := make(map[string]interface{})
|
|
err = json.Unmarshal(nonceResponse, &jsonResponse)
|
|
if err != nil {
|
|
println("Error unmarshalling nonce response: " + err.Error())
|
|
}
|
|
|
|
heartbeatStop := make(chan struct{})
|
|
|
|
// If the response is ok, add the user to the pendingSessions map and start heartbeats
|
|
if jsonResponse["type"] == "success" {
|
|
pendingSessions[nonceBase64] = PendingSession{
|
|
IP: c.ClientIP(),
|
|
EventCallback: func() {
|
|
// Write back to the WebSocket
|
|
err = conn.WriteJSON(gin.H{"type": "success"})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = conn.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Tell the heartbeat loop to stop
|
|
close(heartbeatStop)
|
|
},
|
|
}
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-heartbeatStop:
|
|
return
|
|
case <-time.After(time.Second * 5):
|
|
err := conn.WriteJSON(gin.H{
|
|
"type": "ping",
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var read bool
|
|
|
|
go func() {
|
|
time.Sleep(time.Second * 5)
|
|
if !read {
|
|
err := conn.WriteJSON(gin.H{
|
|
"type": "error",
|
|
"error": "Did not respond to ping",
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, _, err = conn.ReadMessage()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
read = true
|
|
}
|
|
}
|
|
})
|
|
|
|
// Set up the betting route
|
|
router.POST("/api/bet", func(c *gin.Context) {
|
|
var data map[string]interface{}
|
|
err := c.BindJSON(&data)
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
// Check if betting is open
|
|
if bettingOpen {
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Get the user's bet amount
|
|
amountFloat, ok := data["amount"].(float64)
|
|
if !ok {
|
|
c.JSON(400, gin.H{"error": "Invalid bet"})
|
|
return
|
|
}
|
|
|
|
amount := int64(amountFloat)
|
|
|
|
// Check if the user has enough points
|
|
if getPoints(id) < amount {
|
|
c.JSON(400, gin.H{"error": "Not enough points"})
|
|
return
|
|
}
|
|
|
|
// Get the user's bet answer
|
|
answerFloat, ok := data["answer"].(float64)
|
|
if !ok {
|
|
c.JSON(400, gin.H{"error": "Invalid answer"})
|
|
return
|
|
}
|
|
|
|
answer := int(answerFloat)
|
|
|
|
// Check if the answer is valid
|
|
if _, ok := possibleAnswers[answer]; !ok {
|
|
c.JSON(400, gin.H{"error": "Invalid answer"})
|
|
return
|
|
}
|
|
|
|
// Add the bet to the bets map
|
|
bets[id] = Bet{
|
|
amount: amount,
|
|
answer: answer,
|
|
}
|
|
|
|
// Subtract the points from the user
|
|
minusPoints(id, amount)
|
|
|
|
c.JSON(200, gin.H{"message": "Bet placed"})
|
|
} else {
|
|
c.JSON(400, gin.H{"error": "Betting is closed"})
|
|
}
|
|
})
|
|
|
|
router.POST("/api/startBet", func(c *gin.Context) {
|
|
// Parse the JSON
|
|
var data map[string]interface{}
|
|
err := c.BindJSON(&data)
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Check if the user is a moderator
|
|
if !userIsModerator(id) {
|
|
c.JSON(403, gin.H{"error": "You must be a moderator to start a bet"})
|
|
return
|
|
}
|
|
|
|
// Check if betting is open
|
|
if bettingOpen {
|
|
c.JSON(206, gin.H{"error": "There is already a running bet"})
|
|
return
|
|
}
|
|
|
|
question := data["question"].(string)
|
|
possibleAnswersRaw := data["possible"].([]interface{})
|
|
var possibleAnswers []string
|
|
for _, answer := range possibleAnswersRaw {
|
|
possibleAnswers = append(possibleAnswers, answer.(string))
|
|
}
|
|
timeToBetFloat := data["timeToBet"].(float64)
|
|
timeToBet := time.Now().Add(time.Duration(timeToBetFloat) * time.Second)
|
|
|
|
startBet(question, possibleAnswers, timeToBet)
|
|
|
|
c.JSON(200, gin.H{"message": "Bet started"})
|
|
})
|
|
|
|
// Define the route for /api/getCurrentBet
|
|
router.GET("/api/getCurrentBet", func(c *gin.Context) {
|
|
if bettingOpen {
|
|
var possibleAnswersList []string
|
|
for _, answer := range possibleAnswers {
|
|
possibleAnswersList = append(possibleAnswersList, answer)
|
|
}
|
|
c.JSON(200, gin.H{
|
|
"question": question,
|
|
"possible": possibleAnswersList,
|
|
"timeToBet": timeToBet.Unix(),
|
|
})
|
|
return
|
|
} else {
|
|
c.JSON(206, gin.H{"error": "There is not a running bet"})
|
|
return
|
|
}
|
|
})
|
|
|
|
// Define the route for /api/endBet
|
|
router.POST("/api/endBet", func(c *gin.Context) {
|
|
// Parse the JSON
|
|
var data map[string]interface{}
|
|
err := c.BindJSON(&data)
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Check if the user is a moderator
|
|
if !userIsModerator(id) {
|
|
c.JSON(403, gin.H{"error": "You must be a moderator to end a bet"})
|
|
return
|
|
}
|
|
|
|
correctFloat, ok := data["correct"].(float64)
|
|
if !ok {
|
|
c.JSON(400, gin.H{"error": "Invalid correct answer"})
|
|
return
|
|
}
|
|
|
|
correct := int(correctFloat)
|
|
|
|
// Give the points to the correct betters
|
|
giveBetPoints(correct)
|
|
|
|
c.JSON(200, gin.H{"message": "Bet ended"})
|
|
})
|
|
|
|
// Define the route for /api/claimUnclaimedPoints
|
|
router.GET("/api/claimUnclaimedPoints", func(c *gin.Context) {
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Get the user's second difference
|
|
secondDifference, ok := getUserSecondDifference(id)
|
|
if !ok {
|
|
c.JSON(200, gin.H{"points": "0"})
|
|
return
|
|
}
|
|
|
|
// Clear the earliest sent message
|
|
delete(unclaimedMessages, id)
|
|
|
|
// Issue the points
|
|
addPoints(id, secondDifference*int64(configFile.Multiplier))
|
|
|
|
// Return the points
|
|
c.JSON(200, gin.H{"points": strconv.FormatInt(secondDifference, 10)})
|
|
})
|
|
|
|
// Define the route for /api/getUnclaimedPoints
|
|
router.GET("/api/getUnclaimedPoints", func(c *gin.Context) {
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Get the user's second difference
|
|
secondDifference, ok := getUserSecondDifference(id)
|
|
if !ok {
|
|
c.JSON(200, gin.H{"points": "0"})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"points": strconv.FormatInt(secondDifference, 10)})
|
|
})
|
|
|
|
// Define the route for /api/getPoints
|
|
router.GET("/api/getPoints", func(c *gin.Context) {
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"points": getPoints(id)})
|
|
})
|
|
|
|
// Define the route for /api/startStream
|
|
router.GET("/api/startStream", func(c *gin.Context) {
|
|
// Parse the JSON
|
|
var data map[string]interface{}
|
|
err := c.BindJSON(&data)
|
|
if err != nil {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
// Get the user's channel ID
|
|
id, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"error": "Invalid token"})
|
|
return
|
|
}
|
|
|
|
// Check if the user is a streamer
|
|
if !userIsModerator(id) {
|
|
c.JSON(403, gin.H{"error": "You must be a moderator to start a stream"})
|
|
return
|
|
}
|
|
|
|
// Set the live status
|
|
streaming = true
|
|
streamingSince = time.Now()
|
|
videoID, ok := data["videoID"].(string)
|
|
if !ok {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
// Start the chat message checker
|
|
println("Starting chat message checker: ", videoID)
|
|
|
|
go scrape(getLiveChatID(videoID))
|
|
|
|
// Return 200
|
|
c.JSON(200, gin.H{"message": "Stream started"})
|
|
})
|
|
|
|
// Define the route for /api/endStream
|
|
router.GET("/api/endStream", func(c *gin.Context) {
|
|
// Authenticate the user
|
|
accessToken := c.GetHeader("Authorization")
|
|
if accessToken == "" {
|
|
c.JSON(400, gin.H{"error": "No token provided"})
|
|
return
|
|
}
|
|
|
|
// Check if the user is a moderator
|
|
if !userIsModerator(accessToken) {
|
|
c.JSON(403, gin.H{"error": "You must be a moderator to end a stream"})
|
|
return
|
|
}
|
|
|
|
// Set the live status
|
|
streaming = false
|
|
|
|
// Clear all unclaimed points
|
|
for key := range unclaimedMessages {
|
|
delete(unclaimedMessages, key)
|
|
}
|
|
|
|
// Clear the bets
|
|
endBet()
|
|
|
|
// Return 200
|
|
c.JSON(200, gin.H{"message": "Stream ended"})
|
|
})
|
|
|
|
router.GET("/api/loggedIn", func(c *gin.Context) {
|
|
// Get the user's channel ID
|
|
_, ok := sessions[c.ClientIP()]
|
|
if !ok {
|
|
c.JSON(403, gin.H{"loggedIn": false})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"loggedIn": true})
|
|
})
|
|
|
|
// Now some static routes
|
|
router.GET("/", func(c *gin.Context) {
|
|
c.HTML(200, "index.html", gin.H{})
|
|
})
|
|
|
|
router.GET("/admin", func(c *gin.Context) {
|
|
c.HTML(200, "admin.html", gin.H{})
|
|
})
|
|
|
|
router.GET("/link", func(c *gin.Context) {
|
|
c.HTML(200, "link.html", gin.H{})
|
|
})
|
|
|
|
router.GET("/bet", func(c *gin.Context) {
|
|
c.HTML(200, "bet.html", gin.H{})
|
|
})
|
|
|
|
router.GET("/privacy", func(c *gin.Context) {
|
|
c.HTML(200, "privacy.html", gin.H{})
|
|
})
|
|
|
|
router.GET("/tos", func(c *gin.Context) {
|
|
c.HTML(200, "tos.html", gin.H{})
|
|
})
|
|
|
|
// Start the server
|
|
var address string
|
|
if len(os.Args) < 2 {
|
|
address = ":8080"
|
|
} else {
|
|
address = os.Args[1]
|
|
}
|
|
|
|
println("Start server on " + address)
|
|
|
|
go func() {
|
|
println("Shounic CMD started")
|
|
for {
|
|
var input string
|
|
println("Enter command: \n" +
|
|
"1. Stop server\n" +
|
|
"2. Start stream\n" +
|
|
"3. End stream\n" +
|
|
"4. Force login\n" +
|
|
"5. Force logout\n" +
|
|
"6. Get user's IP")
|
|
|
|
print("> ")
|
|
_, err := fmt.Scanln(&input)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
switch input {
|
|
case "1":
|
|
println("OK: Stopping server")
|
|
os.Exit(0)
|
|
case "2":
|
|
if !streaming {
|
|
streaming = true
|
|
var videoID string
|
|
print("Enter video ID: ")
|
|
_, err := fmt.Scanln(&videoID)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
go scrape(getLiveChatID(videoID))
|
|
println("OK: Stream started")
|
|
} else {
|
|
println("ERR: Stream already started")
|
|
}
|
|
case "3":
|
|
streaming = false
|
|
println("OK: Stream ended")
|
|
case "4":
|
|
print("Enter channel ID: ")
|
|
var channelID string
|
|
_, err := fmt.Scanln(&channelID)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
print("Enter IP: ")
|
|
var ip string
|
|
_, err = fmt.Scanln(&ip)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
print("Is moderator? (y/n): ")
|
|
var isModerator string
|
|
_, err = fmt.Scanln(&isModerator)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
print("Is streamer? (y/n): ")
|
|
var isStreamer string
|
|
_, err = fmt.Scanln(&isStreamer)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
sessions[ip] = channelID
|
|
initUser(channelID, isModerator == "y", isStreamer == "y")
|
|
println("OK: Forced login")
|
|
case "5":
|
|
print("Enter IP: ")
|
|
var ip string
|
|
_, err := fmt.Scanln(&ip)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
delete(sessions, ip)
|
|
println("OK: Forced logout")
|
|
case "6":
|
|
print("Enter channel ID: ")
|
|
var channelID string
|
|
_, err := fmt.Scanln(&channelID)
|
|
if err != nil {
|
|
println("Error reading input: " + err.Error())
|
|
}
|
|
|
|
for key, value := range sessions {
|
|
if value == channelID {
|
|
println("IP: " + key)
|
|
}
|
|
}
|
|
|
|
println("OK: Got user's IP")
|
|
default:
|
|
println("ERR: Invalid command")
|
|
}
|
|
}
|
|
}()
|
|
|
|
err = router.Run(address)
|
|
if err != nil {
|
|
panic("Error starting server: " + err.Error())
|
|
}
|
|
}
|