Ctrl K
ComunidadGuías…

plugins_core.go

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

Archivo original ↗Preguntar a KiBot
package main

import (
	"errors"
	"fmt"
	"net/http"
	"slices"

	"github.com/Luzifer/go_helpers/backoff"
	"github.com/Luzifer/go_helpers/fieldcollection"
	"github.com/gorilla/mux"
	"github.com/sirupsen/logrus"
	"gopkg.in/irc.v4"

	"github.com/Luzifer/twitch-bot/v3/internal/actors/announce"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/ban"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/clip"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/clipdetector"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/commercial"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/counter"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/delay"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/deleteactor"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/eventmod"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/filesay"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/linkdetector"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/linkprotect"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/logactor"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/marker"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/messagehook"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/modchannel"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/nuke"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/pin"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/punish"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/quotedb"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/raw"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/respond"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/shield"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/shoutout"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/spotify"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/stopexec"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/timeout"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/unpin"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/variables"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/vip"
	"github.com/Luzifer/twitch-bot/v3/internal/actors/whisper"
	"github.com/Luzifer/twitch-bot/v3/internal/apimodules/customevent"
	"github.com/Luzifer/twitch-bot/v3/internal/apimodules/kofi"
	"github.com/Luzifer/twitch-bot/v3/internal/apimodules/msgformat"
	"github.com/Luzifer/twitch-bot/v3/internal/apimodules/overlays"
	"github.com/Luzifer/twitch-bot/v3/internal/apimodules/raffle"
	"github.com/Luzifer/twitch-bot/v3/internal/service/access"
	"github.com/Luzifer/twitch-bot/v3/internal/template/api"
	"github.com/Luzifer/twitch-bot/v3/internal/template/date"
	"github.com/Luzifer/twitch-bot/v3/internal/template/numeric"
	"github.com/Luzifer/twitch-bot/v3/internal/template/random"
	"github.com/Luzifer/twitch-bot/v3/internal/template/slice"
	"github.com/Luzifer/twitch-bot/v3/internal/template/stringfns"
	"github.com/Luzifer/twitch-bot/v3/internal/template/subscriber"
	twitchfns "github.com/Luzifer/twitch-bot/v3/internal/template/twitch"
	"github.com/Luzifer/twitch-bot/v3/internal/template/userstate"
	"github.com/Luzifer/twitch-bot/v3/pkg/database"
	"github.com/Luzifer/twitch-bot/v3/pkg/event"
	"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
	"github.com/Luzifer/twitch-bot/v3/plugins"
)

const (
	defaultMaxBodyBytes  = 1 << 20 // 1 MiB
	ircHandleWaitRetries = 10
)

var (
	corePluginRegistrations = []plugins.RegisterFunc{
		// Actors
		announce.Register,
		ban.Register,
		clip.Register,
		clipdetector.Register,
		commercial.Register,
		counter.Register,
		delay.Register,
		deleteactor.Register,
		eventmod.Register,
		filesay.Register,
		linkdetector.Register,
		linkprotect.Register,
		logactor.Register,
		marker.Register,
		messagehook.Register,
		modchannel.Register,
		nuke.Register,
		pin.Register,
		punish.Register,
		quotedb.Register,
		raw.Register,
		respond.Register,
		shield.Register,
		shoutout.Register,
		stopexec.Register,
		timeout.Register,
		unpin.Register,
		variables.Register,
		vip.Register,
		whisper.Register,

		// Template functions
		api.Register,
		date.Register,
		numeric.Register,
		random.Register,
		slice.Register,
		spotify.Register,
		stringfns.Register,
		subscriber.Register,
		twitchfns.Register,
		userstate.Register,

		// API-only modules
		customevent.Register,
		kofi.Register,
		msgformat.Register,
		overlays.Register,
		raffle.Register,
	}
	knownModules []string
)

func initCorePlugins() error {
	args := getRegistrationArguments()
	for idx, rf := range corePluginRegistrations {
		if err := rf(args); err != nil {
			return fmt.Errorf("registering core plugin %d: %w", idx, err)
		}
	}
	return nil
}

func registerRoute(route plugins.HTTPRouteRegistrationArgs) error {
	r := router.
		PathPrefix(fmt.Sprintf("/%s/", route.Module)).
		Subrouter()

	if !slices.Contains(knownModules, route.Module) {
		knownModules = append(knownModules, route.Module)
	}

	var hdl http.Handler = route.HandlerFunc

	// Apply fix for GoSec G120: Limit body size
	if route.MaxBodyBytes == 0 {
		route.MaxBodyBytes = defaultMaxBodyBytes
	}
	hdl = writeBodyLimitMiddleware(hdl, route.MaxBodyBytes)

	// Apply auth in case it's configured
	switch {
	case route.RequiresEditorsAuth:
		hdl = writeAuthMiddleware(hdl, moduleConfigEditor)
	case route.RequiresWriteAuth:
		hdl = writeAuthMiddleware(hdl, route.Module)
	}

	var muxRoute *mux.Route
	if route.IsPrefix {
		muxRoute = r.PathPrefix(route.Path).Handler(hdl)
	} else {
		muxRoute = r.Handle(route.Path, hdl)
	}

	if route.Method != "" {
		muxRoute.Methods(route.Method)
	}

	if !route.SkipDocumentation {
		if err := registerSwaggerRoute(route); err != nil {
			return fmt.Errorf("registering documentation: %w", err)
		}
	}

	return nil
}

func getRegistrationArguments() plugins.RegistrationArguments {
	return plugins.RegistrationArguments{
		FormatMessage:              formatMessage,
		FrontendNotify:             func(mt string) { frontendNotifyHooks.Ping(mt) },
		GetBaseURL:                 func() string { return cfg.BaseURL },
		GetDatabaseConnector:       func() database.Connector { return db },
		GetLogger:                  func(moduleName string) *logrus.Entry { return logrus.WithField("module", moduleName) },
		GetTwitchClient:            func() *twitch.Client { return twitchClient },
		HasAnyPermissionForChannel: accessService.HasAnyPermissionForChannel,
		HasPermissionForChannel:    accessService.HasPermissionsForChannel,
		RegisterActor:              registerAction,
		RegisterActorDocumentation: registerActorDocumentation,
		RegisterAPIRoute:           registerRoute,
		RegisterCron:               cronService.AddFunc,
		RegisterCopyDatabaseFunc:   registerDatabaseCopyFunc,
		RegisterEventHandler:       registerEventHandlers,
		RegisterMessageModFunc:     registerChatcommand,
		RegisterRawMessageHandler:  registerRawMessageHandler,
		RegisterTemplateFunction:   tplFuncs.Register,
		SendMessage:                sendMessage,
		ValidateToken:              authService.ValidateTokenFor,

		//nolint:staticcheck // Must be defined as long as the function is still supported
		CreateEvent: func(evt string, eventData *fieldcollection.FieldCollection) error {
			handleMessage(ircHdl.Client(), nil, &evt, eventData)
			return nil
		},

		CreateTypedEvent: func(evt event.Event) error {
			handleTypedMessage(ircHdl.Client(), nil, evt)
			return nil
		},

		GetModuleConfigForChannel: func(module, channel string) *fieldcollection.FieldCollection {
			return config.ModuleConfig.GetChannelConfig(module, channel)
		},

		GetTwitchClientForChannel: func(channel string) (*twitch.Client, error) {
			return accessService.GetTwitchClientForChannel(channel, access.ClientConfig{
				TwitchClient:       cfg.TwitchClient,
				TwitchClientSecret: cfg.TwitchClientSecret,
			})
		},
	}
}

func sendMessage(m *irc.Message) error {
	err := handleChatcommandModifications(m)
	switch {
	case err == nil:
		// There was no error, the message should be sent normally

	case errors.Is(err, plugins.ErrSkipSendingMessage):
		// One chatcommand handler cancelled sending the message
		// (probably because it was handled otherwise)
		return nil

	default:
		// Something in a chatcommand handler went wrong
		return fmt.Errorf("handling chat commands: %w", err)
	}

	if err = backoff.NewBackoff().WithMaxIterations(ircHandleWaitRetries).Retry(func() error {
		if ircHdl == nil {
			return errors.New("irc handle not available")
		}
		return nil
	}); err != nil {
		return fmt.Errorf("waiting for IRC connection: %w", err)
	}

	return ircHdl.SendMessage(m)
}

func writeBodyLimitMiddleware(h http.Handler, limit int64) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Body != nil {
			r.Body = http.MaxBytesReader(w, r.Body, limit)
		}

		h.ServeHTTP(w, r)
	})
}