Ctrl K
ComunidadGuías…

configEditor_global.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"encoding/json"
	"net/http"
	"regexp"
	"time"

	"github.com/sirupsen/logrus"

	"github.com/Luzifer/twitch-bot/v3/plugins"
)

const (
	cronValidationReturnSegments = 3
	frontendNotifyTypeReload     = "configReload"
)

var frontendNotifyHooks = newHooker()

//nolint:funlen // Just contains a collection of objects
func registerEditorGlobalMethods() {
	for _, rd := range []plugins.HTTPRouteRegistrationArgs{
		{
			Description:  "Returns the documentation for available actions",
			HandlerFunc:  configEditorGlobalGetActions,
			Method:       http.MethodGet,
			Module:       moduleConfigEditor,
			Name:         "Get available actions",
			Path:         "/actions",
			ResponseType: plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description:  "Returns all available modules for auth",
			HandlerFunc:  configEditorGlobalGetModules,
			Method:       http.MethodGet,
			Module:       moduleConfigEditor,
			Name:         "Get available modules",
			Path:         "/modules",
			ResponseType: plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description: "Returns information about a Twitch user to properly display bot editors",
			HandlerFunc: configEditorGlobalGetUser,
			Method:      http.MethodGet,
			Module:      moduleConfigEditor,
			Name:        "Get user information",
			Path:        "/user",
			QueryParams: []plugins.HTTPRouteParamDocumentation{
				{
					Description: "The user to query the information for",
					Name:        "user",
					Required:    true,
					Type:        "string",
				},
			},
			RequiresEditorsAuth: true,
			ResponseType:        plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description:  "Subscribe for configuration changes",
			HandlerFunc:  configEditorGlobalSubscribe,
			Method:       http.MethodGet,
			Module:       moduleConfigEditor,
			Name:         "Websocket: Subscribe config changes",
			Path:         "/notify-config",
			ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
		},
		{
			Description: "Validate a cron expression and return the next executions",
			HandlerFunc: configEditorGlobalValidateCron,
			Method:      http.MethodPut,
			Module:      moduleConfigEditor,
			Name:        "Validate cron expression",
			Path:        "/validate-cron",
			QueryParams: []plugins.HTTPRouteParamDocumentation{
				{
					Description: "The cron expression to test",
					Name:        "cron",
					Required:    true,
					Type:        "string",
				},
				{
					Description: "Check cron with last execution of auto-message",
					Name:        "uuid",
					Required:    false,
					Type:        "string",
				},
			},
			ResponseType: plugins.HTTPRouteResponseTypeJSON,
		},
		{
			Description: "Validate a regular expression against the RE2 regex parser",
			HandlerFunc: configEditorGlobalValidateRegex,
			Method:      http.MethodPut,
			Module:      moduleConfigEditor,
			Name:        "Validate regular expression",
			Path:        "/validate-regex",
			QueryParams: []plugins.HTTPRouteParamDocumentation{
				{
					Description: "The regular expression to test",
					Name:        "regexp",
					Required:    true,
					Type:        "string",
				},
			},
			ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
		},
		{
			Description: "Validate a template expression against the built in template function library",
			HandlerFunc: configEditorGlobalValidateTemplate,
			Method:      http.MethodPut,
			Module:      moduleConfigEditor,
			Name:        "Validate template expression",
			Path:        "/validate-template",
			QueryParams: []plugins.HTTPRouteParamDocumentation{
				{
					Description: "The template expression to test",
					Name:        "template",
					Required:    true,
					Type:        "string",
				},
			},
			ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
		},
	} {
		if err := registerRoute(rd); err != nil {
			logrus.WithError(err).Fatal("Unable to register config editor route")
		}
	}
}

func configEditorGlobalGetActions(w http.ResponseWriter, _ *http.Request) {
	availableActorDocsLock.Lock()
	defer availableActorDocsLock.Unlock()

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(availableActorDocs); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorGlobalGetModules(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(knownModules); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorGlobalGetUser(w http.ResponseWriter, r *http.Request) {
	usr, err := twitchClient.GetUserInformation(r.Context(), r.FormValue("user")) //#nosec:G120 // Request body size is limited by API route registration middleware
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(usr); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorGlobalSubscribe(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		logrus.WithError(err).Error("Unable to initialize websocket")
		return
	}
	defer func() {
		if err := conn.Close(); err != nil {
			logrus.WithError(err).Trace("closing websocket connection")
		}
	}()

	var (
		frontendNotify = make(chan string, 1)
		pingTimer      = time.NewTicker(websocketPingInterval)
		unsubscribe    = frontendNotifyHooks.Register(func(payload any) { frontendNotify <- payload.(string) })
	)
	defer unsubscribe()

	type socketMsg struct {
		MsgType string `json:"msg_type"`
	}

	for {
		select {
		case msgType := <-frontendNotify:
			if err := conn.WriteJSON(socketMsg{
				MsgType: msgType,
			}); err != nil {
				logrus.WithError(err).Debug("Unable to send websocket notification")
				return
			}

		case <-pingTimer.C:
			if err := conn.WriteJSON(socketMsg{
				MsgType: "ping",
			}); err != nil {
				logrus.WithError(err).Debug("Unable to send websocket ping")
				return
			}
		}
	}
}

func configEditorGlobalValidateCron(w http.ResponseWriter, r *http.Request) {
	sched, err := cronParser.Parse(r.FormValue("cron")) //#nosec:G120 // Request body size is limited by API route registration middleware
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	var (
		lt  = time.Now()
		out []time.Time
	)

	if id := r.FormValue("uuid"); id != "" { //#nosec:G120 // Request body size is limited by API route registration middleware
		for _, a := range config.AutoMessages {
			if a.ID() != id {
				continue
			}
			lt = a.lastMessageSent
			break
		}
	}

	for range cronValidationReturnSegments {
		lt = sched.Next(lt)
		out = append(out, lt)
	}

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(out); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func configEditorGlobalValidateRegex(w http.ResponseWriter, r *http.Request) {
	if _, err := regexp.Compile(r.FormValue("regexp")); err != nil { //#nosec:G120 // Request body size is limited by API route registration middleware
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

func configEditorGlobalValidateTemplate(w http.ResponseWriter, r *http.Request) {
	if err := validateTemplate(r.FormValue("template")); err != nil { //#nosec:G120 // Request body size is limited by API route registration middleware
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}