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) }) }