From 87ee66588c3ee48c428a58120a90efca77024d15 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 6 Oct 2025 09:01:18 +0100 Subject: [PATCH 1/3] form opts loading from api Signed-off-by: Ben --- bot/button/handlers/multipanel.go | 14 ++++- bot/button/handlers/panel.go | 91 ++++++++++++++++++--------- bot/integrations/form_integrations.go | 54 ++++++++++++++++ go.mod | 2 +- go.sum | 2 - 5 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 bot/integrations/form_integrations.go diff --git a/bot/button/handlers/multipanel.go b/bot/button/handlers/multipanel.go index d8d32bb..153db69 100644 --- a/bot/button/handlers/multipanel.go +++ b/bot/button/handlers/multipanel.go @@ -86,10 +86,22 @@ func (h *MultiPanelHandler) Execute(ctx *context.SelectMenuContext) { return } + inputApiConfigs, err := dbclient.Client.FormInputApiConfig.GetByFormId(ctx, form.Id) + if err != nil { + ctx.HandleError(err) + return + } + + inputApiHeaders, err := dbclient.Client.FormInputApiHeaders.GetAllByGuild(ctx, ctx.GuildId()) + if err != nil { + ctx.HandleError(err) + return + } + if len(inputs) == 0 { // Don't open a blank form _, _ = logic.OpenTicket(ctx.Context, ctx, &panel, panel.Title, nil) } else { - modal := buildForm(panel, form, inputs, inputOptions) + modal := buildForm(ctx.UserId(), panel, form, inputs, inputOptions, inputApiConfigs, inputApiHeaders) ctx.Modal(modal) } } diff --git a/bot/button/handlers/panel.go b/bot/button/handlers/panel.go index a800958..67b1f5d 100644 --- a/bot/button/handlers/panel.go +++ b/bot/button/handlers/panel.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" + ct "context" + "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/database" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -15,6 +17,7 @@ import ( "github.com/TicketsBot-cloud/worker/bot/constants" "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" + "github.com/TicketsBot-cloud/worker/bot/integrations" "github.com/TicketsBot-cloud/worker/bot/logic" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" @@ -46,26 +49,6 @@ func (h *PanelHandler) Execute(ctx *context.ButtonContext) { return } - // Check support hours - hasSupportHours, err := dbclient.Client.PanelSupportHours.HasSupportHours(ctx, panel.PanelId) - if err != nil { - ctx.HandleError(err) - return - } - - if hasSupportHours { - isActive, err := dbclient.Client.PanelSupportHours.IsActiveNow(ctx, panel.PanelId) - if err != nil { - ctx.HandleError(err) - return - } - - if !isActive { - ctx.Reply(customisation.Red, i18n.Error, i18n.MessageOutsideSupportHours) - return - } - } - // blacklist check blacklisted, err := ctx.IsBlacklisted(ctx) if err != nil { @@ -104,10 +87,22 @@ func (h *PanelHandler) Execute(ctx *context.ButtonContext) { return } + inputApiConfigs, err := dbclient.Client.FormInputApiConfig.GetByFormId(ctx, form.Id) + if err != nil { + ctx.HandleError(err) + return + } + + inputApiHeaders, err := dbclient.Client.FormInputApiHeaders.GetAllByGuild(ctx, ctx.GuildId()) + if err != nil { + ctx.HandleError(err) + return + } + if len(inputs) == 0 { // Don't open a blank form _, _ = logic.OpenTicket(ctx.Context, ctx, &panel, panel.Title, nil) } else { - modal := buildForm(panel, form, inputs, inputOptions) + modal := buildForm(ctx.UserId(), panel, form, inputs, inputOptions, inputApiConfigs, inputApiHeaders) ctx.Modal(modal) } } @@ -116,7 +111,7 @@ func (h *PanelHandler) Execute(ctx *context.ButtonContext) { } } -func buildForm(panel database.Panel, form database.Form, inputs []database.FormInput, inputOptions map[int][]database.FormInputOption) button.ResponseModal { +func buildForm(userId uint64, panel database.Panel, form database.Form, inputs []database.FormInput, inputOptions map[int][]database.FormInputOption, inputApiConfigs []database.FormInputApiConfig, inputApiHeaders map[int][]database.FormInputApiHeader) button.ResponseModal { components := make([]component.Component, len(inputs)) for i, input := range inputs { var minLength, maxLength *int @@ -132,6 +127,21 @@ func buildForm(panel database.Panel, form database.Form, inputs []database.FormI } var innerComponent component.Component + var apiConfig *database.FormInputApiConfig + var apiHeaders []database.FormInputApiHeader + + for _, conf := range inputApiConfigs { + if conf.FormInputId == input.Id { + apiConfig = &conf + break + } + } + + if apiConfig != nil { + if headers, ok := inputApiHeaders[apiConfig.Id]; ok { + apiHeaders = headers + } + } options, ok := inputOptions[input.Id] if !ok { @@ -141,14 +151,39 @@ func buildForm(panel database.Panel, form database.Form, inputs []database.FormI switch input.Type { // String Select case int(component.ComponentSelectMenu): - opts := make([]component.SelectOption, len(options)) - for j, option := range options { - opts[j] = component.SelectOption{ - Label: option.Label, - Value: option.Value, - Description: option.Description, + ctx := ct.Background() + + opts := make([]component.SelectOption, 0) + + if apiConfig != nil { + apiOpts, err := integrations.FetchInputOptions(ctx, userId, *apiConfig, apiHeaders) + if err != nil { + fmt.Println(err) + } + + opts = make([]component.SelectOption, len(apiOpts)) + + for j, option := range apiOpts { + if j >= 25 { // Discord max + break + } + opts[j] = component.SelectOption{ + Label: option.Label, + Value: option.Value, + Description: option.Description, + } + } + } else { + opts = make([]component.SelectOption, len(options)) + for j, option := range options { + opts[j] = component.SelectOption{ + Label: option.Label, + Value: option.Value, + Description: option.Description, + } } } + isRequired := input.MinLength != nil && *input.MinLength > 0 innerComponent = component.BuildSelectMenu(component.SelectMenu{ CustomId: input.CustomId, diff --git a/bot/integrations/form_integrations.go b/bot/integrations/form_integrations.go new file mode 100644 index 0000000..0acfa77 --- /dev/null +++ b/bot/integrations/form_integrations.go @@ -0,0 +1,54 @@ +package integrations + +import ( + "bytes" + "context" + "encoding/json" + "strconv" + "strings" + + "github.com/TicketsBot-cloud/database" +) + +type Option struct { + Label string `json:"label"` + Value string `json:"value"` + Description *string `json:"description,omitempty"` +} + +func FetchInputOptions( + ctx context.Context, + userId uint64, + apiConfig database.FormInputApiConfig, + apiHeaders []database.FormInputApiHeader, +) ([]Option, error) { + url := strings.ReplaceAll(apiConfig.EndpointUrl, "%user_id%", strconv.FormatUint(userId, 10)) + + // Apply headers + headerMap := make(map[string]string, len(apiHeaders)) + + for _, header := range apiHeaders { + if isHeaderBlacklisted(header.HeaderName) { + continue + } + + value := header.HeaderValue + value = strings.ReplaceAll(value, "%user_id%", strconv.FormatUint(userId, 10)) + headerMap[header.HeaderName] = value + } + + res, err := SecureProxy.DoRequest(ctx, apiConfig.Method, url, headerMap, nil) + if err != nil { + return nil, err + } + + decoder := json.NewDecoder(bytes.NewBuffer(res)) + decoder.UseNumber() + + var jsonBody []Option + if err := decoder.Decode(&jsonBody); err != nil { + return nil, err + } + + return jsonBody, nil +} diff --git a/go.mod b/go.mod index 10b01cf..1110725 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 toolchain go1.24.2 -//replace github.com/TicketsBot-cloud/database => ../database +replace github.com/TicketsBot-cloud/database => ../database //replace github.com/TicketsBot-cloud/gdl => ../gdl diff --git a/go.sum b/go.sum index 3725262..67ef797 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,6 @@ github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc h1 github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= github.com/TicketsBot-cloud/common v0.0.0-20250509064208-a2d357175463 h1:ZO2kw9lpsy4umoWFmAt9WKVFb3WvU5oSif2Yw4K7Lj4= github.com/TicketsBot-cloud/common v0.0.0-20250509064208-a2d357175463/go.mod h1:PL5j/omFvU0NeyTKCESmOF+3GscaEuM0aqmI4yFcCFY= -github.com/TicketsBot-cloud/database v0.0.0-20251005195038-fd63a423fcb7 h1:TK9QTv3LeuuMcZMP/MtPsdjPazkj8MmkCRS8AU+idRk= -github.com/TicketsBot-cloud/database v0.0.0-20251005195038-fd63a423fcb7/go.mod h1:LPDEn9e5wccH7rq/pUlVcL3UhyLnnwdM2dhj0tp/ljo= github.com/TicketsBot-cloud/gdl v0.0.0-20250917180424-569348f7a55b h1:k4kIIhpD3m0tx8Sz8gefZH8DBbuAwbeJJdV0iRr/x5Y= github.com/TicketsBot-cloud/gdl v0.0.0-20250917180424-569348f7a55b/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot-cloud/logarchiver v0.0.0-20250514201320-d5141071a6eb h1:BapforRlvTfWP8MX8DTsxVM40oDgQorJVo/cnNGTaKU= From cdebae9d98847acc24cfe020edf02ed63a4a8cb0 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 26 Oct 2025 18:31:05 +0000 Subject: [PATCH 2/3] form api inputs Signed-off-by: Ben --- bot/button/handlers/panel.go | 5 ++ bot/button/responsemodal.go | 6 +- .../context/applicationcommandcontext.go | 9 +++ bot/command/context/autoclosecontext.go | 9 +++ bot/command/context/buttoncontext.go | 9 +++ bot/command/context/dashboardcontext.go | 9 +++ .../context/messagecomponentextensions.go | 17 +++++ bot/command/context/modalcontext.go | 9 +++ bot/command/context/panelcontext.go | 9 +++ bot/command/context/selectmenucontext.go | 9 +++ bot/command/impl/admin/admindebug.go | 4 +- bot/command/impl/settings/premium.go | 1 - bot/command/impl/statistics/statsserver.go | 4 +- bot/command/impl/statistics/statsuser.go | 4 +- bot/command/registry/commandcontext.go | 2 + cmd/worker/main.go | 6 ++ event/httplisten.go | 17 +++++ experiments/experiments.go | 68 ------------------- go.mod | 8 ++- go.sum | 6 +- 20 files changed, 130 insertions(+), 81 deletions(-) delete mode 100644 experiments/experiments.go diff --git a/bot/button/handlers/panel.go b/bot/button/handlers/panel.go index 67b1f5d..d44a98c 100644 --- a/bot/button/handlers/panel.go +++ b/bot/button/handlers/panel.go @@ -184,6 +184,10 @@ func buildForm(userId uint64, panel database.Panel, form database.Form, inputs [ } } + if maxLength != nil && *maxLength > len(opts) { + *maxLength = len(opts) + } + isRequired := input.MinLength != nil && *input.MinLength > 0 innerComponent = component.BuildSelectMenu(component.SelectMenu{ CustomId: input.CustomId, @@ -192,6 +196,7 @@ func buildForm(userId uint64, panel database.Panel, form database.Form, inputs [ MaxValues: maxLength, Required: utils.Ptr(isRequired), }) + // Input Text case 4: innerComponent = component.BuildInputText(component.InputText{ diff --git a/bot/button/responsemodal.go b/bot/button/responsemodal.go index 7876d00..321bdf1 100644 --- a/bot/button/responsemodal.go +++ b/bot/button/responsemodal.go @@ -2,7 +2,9 @@ package button import ( "errors" + "fmt" + "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/gdl/objects/interaction" "github.com/TicketsBot-cloud/worker" ) @@ -20,5 +22,7 @@ func (r ResponseModal) Build() interface{} { } func (r ResponseModal) HandleDeferred(interactionData interaction.InteractionMetadata, worker *worker.Context) error { - return errors.New("cannot defer modal response") + err := errors.New("cannot defer modal response") + sentry.Error(fmt.Errorf("failed to send deferred modal response with custom_id %s: %w", r.Data.CustomId, err)) + return err } diff --git a/bot/command/context/applicationcommandcontext.go b/bot/command/context/applicationcommandcontext.go index 07e08aa..a8d68a4 100644 --- a/bot/command/context/applicationcommandcontext.go +++ b/bot/command/context/applicationcommandcontext.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -176,6 +177,14 @@ func (c *SlashCommandContext) IsBlacklisted(ctx context.Context) (bool, error) { return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), utils.ValueOrZero(c.Interaction.Member), permLevel) } +func (c *SlashCommandContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} + /// InteractionContext functions func (c *SlashCommandContext) InteractionMetadata() interaction.InteractionMetadata { diff --git a/bot/command/context/autoclosecontext.go b/bot/command/context/autoclosecontext.go index d5cdb85..b1f99e6 100644 --- a/bot/command/context/autoclosecontext.go +++ b/bot/command/context/autoclosecontext.go @@ -3,6 +3,7 @@ package context import ( "context" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/gdl/objects/channel" @@ -133,3 +134,11 @@ func (c *AutoCloseContext) IsBlacklisted(ctx context.Context) (bool, error) { // if the command is not executed in a guild return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), member, permLevel) } + +func (c *AutoCloseContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} diff --git a/bot/command/context/buttoncontext.go b/bot/command/context/buttoncontext.go index bd7c7c2..841df19 100644 --- a/bot/command/context/buttoncontext.go +++ b/bot/command/context/buttoncontext.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -202,6 +203,14 @@ func (c *ButtonContext) IsBlacklisted(ctx context.Context) (bool, error) { return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), utils.ValueOrZero(c.Interaction.Member), permLevel) } +func (c *ButtonContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} + /// InteractionContext functions func (c *ButtonContext) InteractionMetadata() interaction.InteractionMetadata { diff --git a/bot/command/context/dashboardcontext.go b/bot/command/context/dashboardcontext.go index d8a54a1..518df58 100644 --- a/bot/command/context/dashboardcontext.go +++ b/bot/command/context/dashboardcontext.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -183,3 +184,11 @@ func (c *DashboardContext) IsBlacklisted(ctx context.Context) (bool, error) { // if the command is not executed in a guild return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), member, permLevel) } + +func (c *DashboardContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} diff --git a/bot/command/context/messagecomponentextensions.go b/bot/command/context/messagecomponentextensions.go index 1fe611d..d4ac608 100644 --- a/bot/command/context/messagecomponentextensions.go +++ b/bot/command/context/messagecomponentextensions.go @@ -2,6 +2,8 @@ package context import ( "context" + "encoding/json" + "fmt" "github.com/TicketsBot-cloud/common/sentry" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -13,6 +15,7 @@ import ( "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/utils" "github.com/TicketsBot-cloud/worker/i18n" + "github.com/sirupsen/logrus" "go.uber.org/atomic" ) @@ -38,6 +41,20 @@ func NewMessageComponentExtensions( } func (e *MessageComponentExtensions) Modal(res button.ResponseModal) { + if res.Data.CustomId == "" { + sentry.ErrorWithContext(fmt.Errorf("modal has empty custom_id"), e.ctx.ToErrorContext()) + } + if res.Data.Title == "" { + sentry.ErrorWithContext(fmt.Errorf("modal has empty title"), e.ctx.ToErrorContext()) + } + if len(res.Data.Components) == 0 { + sentry.ErrorWithContext(fmt.Errorf("modal has no components"), e.ctx.ToErrorContext()) + } + + modalJSON, _ := json.Marshal(res.Build()) + logrus.Infof("sending modal - custom_id: %s, title: %s, components: %d, json: %s", + res.Data.CustomId, res.Data.Title, len(res.Data.Components), string(modalJSON)) + e.hasReplied.Store(true) e.responseChannel <- res } diff --git a/bot/command/context/modalcontext.go b/bot/command/context/modalcontext.go index 2c8de6a..da67bfc 100644 --- a/bot/command/context/modalcontext.go +++ b/bot/command/context/modalcontext.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -226,6 +227,14 @@ func (c *ModalContext) IsBlacklisted(ctx context.Context) (bool, error) { return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), utils.ValueOrZero(c.Interaction.Member), permLevel) } +func (c *ModalContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} + /// InteractionContext functions func (c *ModalContext) InteractionMetadata() interaction.InteractionMetadata { diff --git a/bot/command/context/panelcontext.go b/bot/command/context/panelcontext.go index caf5871..976d279 100644 --- a/bot/command/context/panelcontext.go +++ b/bot/command/context/panelcontext.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -189,3 +190,11 @@ func (c *PanelContext) IsBlacklisted(ctx context.Context) (bool, error) { // if the command is not executed in a guild return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), member, permLevel) } + +func (c *PanelContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} diff --git a/bot/command/context/selectmenucontext.go b/bot/command/context/selectmenucontext.go index 8336a02..41feedf 100644 --- a/bot/command/context/selectmenucontext.go +++ b/bot/command/context/selectmenucontext.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/common/sentry" @@ -202,6 +203,14 @@ func (c *SelectMenuContext) IsBlacklisted(ctx context.Context) (bool, error) { return utils.IsBlacklisted(ctx, c.GuildId(), c.UserId(), utils.ValueOrZero(c.Interaction.Member), permLevel) } +func (c *SelectMenuContext) HasFeature(feature experiments.Experiment) bool { + manager := experiments.GetGlobalManager() + if manager == nil { + return false + } + return manager.HasFeature(c, c.GuildId(), feature) +} + /// InteractionContext functions func (c *SelectMenuContext) InteractionMetadata() interaction.InteractionMetadata { diff --git a/bot/command/impl/admin/admindebug.go b/bot/command/impl/admin/admindebug.go index 6f6f785..bb089e1 100644 --- a/bot/command/impl/admin/admindebug.go +++ b/bot/command/impl/admin/admindebug.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/experiments" "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/gdl/objects/application" @@ -17,7 +18,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" "github.com/TicketsBot-cloud/worker/bot/utils" - "github.com/TicketsBot-cloud/worker/experiments" "github.com/TicketsBot-cloud/worker/i18n" ) @@ -101,7 +101,7 @@ func (AdminDebugCommand) Execute(ctx registry.CommandContext, raw string) { for i := range experiments.List { feature := experiments.List[i] - if experiments.HasFeature(ctx, guild.Id, feature) { + if ctx.HasFeature(feature) { featuresEnabled = append(featuresEnabled, string(feature)) } } diff --git a/bot/command/impl/settings/premium.go b/bot/command/impl/settings/premium.go index 516b1d5..a426f8b 100644 --- a/bot/command/impl/settings/premium.go +++ b/bot/command/impl/settings/premium.go @@ -130,7 +130,6 @@ func (PremiumCommand) Execute(ctx registry.CommandContext) { }, ), Placeholder: ctx.GetMessage(i18n.MessagePremiumMethodSelector), - Disabled: false, }), ), component.BuildActionRow( diff --git a/bot/command/impl/statistics/statsserver.go b/bot/command/impl/statistics/statsserver.go index 7955c12..c0fb1f8 100644 --- a/bot/command/impl/statistics/statsserver.go +++ b/bot/command/impl/statistics/statsserver.go @@ -7,6 +7,7 @@ import ( "time" "github.com/TicketsBot-cloud/analytics-client" + "github.com/TicketsBot-cloud/common/experiments" "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/gdl/objects/channel/embed" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -16,7 +17,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" "github.com/TicketsBot-cloud/worker/bot/utils" - "github.com/TicketsBot-cloud/worker/experiments" "github.com/TicketsBot-cloud/worker/i18n" "github.com/getsentry/sentry-go" "github.com/jedib0t/go-pretty/v6/table" @@ -146,7 +146,7 @@ func (StatsServerCommand) Execute(ctx registry.CommandContext) { span = sentry.StartSpan(span.Context(), "Send Message") - if experiments.HasFeature(ctx, ctx.GuildId(), experiments.COMPONENTS_V2_STATISTICS) { + if ctx.HasFeature(experiments.COMPONENTS_V2_STATISTICS) { guildData, err := ctx.Guild() if err != nil { ctx.HandleError(err) diff --git a/bot/command/impl/statistics/statsuser.go b/bot/command/impl/statistics/statsuser.go index f6f46e6..cb39b64 100644 --- a/bot/command/impl/statistics/statsuser.go +++ b/bot/command/impl/statistics/statsuser.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/TicketsBot-cloud/common/experiments" "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/gdl/objects/channel/embed" "github.com/TicketsBot-cloud/gdl/objects/interaction" @@ -15,7 +16,6 @@ import ( "github.com/TicketsBot-cloud/worker/bot/customisation" "github.com/TicketsBot-cloud/worker/bot/dbclient" "github.com/TicketsBot-cloud/worker/bot/utils" - "github.com/TicketsBot-cloud/worker/experiments" "github.com/TicketsBot-cloud/worker/i18n" "github.com/getsentry/sentry-go" "golang.org/x/sync/errgroup" @@ -278,7 +278,7 @@ func (StatsUserCommand) Execute(ctx registry.CommandContext, userId uint64) { span := sentry.StartSpan(span.Context(), "Reply") - if experiments.HasFeature(ctx, ctx.GuildId(), experiments.COMPONENTS_V2_STATISTICS) { + if ctx.HasFeature(experiments.COMPONENTS_V2_STATISTICS) { userData, err := ctx.Worker().GetUser(userId) if err != nil { ctx.HandleError(err) diff --git a/bot/command/registry/commandcontext.go b/bot/command/registry/commandcontext.go index c3a88b2..d124a2b 100644 --- a/bot/command/registry/commandcontext.go +++ b/bot/command/registry/commandcontext.go @@ -1,6 +1,7 @@ package registry import ( + "github.com/TicketsBot-cloud/common/experiments" permcache "github.com/TicketsBot-cloud/common/permission" "github.com/TicketsBot-cloud/common/premium" "github.com/TicketsBot-cloud/database" @@ -65,6 +66,7 @@ type CommandContext interface { Settings() (database.Settings, error) IsBlacklisted(ctx context.Context) (bool, error) + HasFeature(feature experiments.Experiment) bool } type InteractionContext interface { diff --git a/cmd/worker/main.go b/cmd/worker/main.go index 1caefd3..6f18913 100644 --- a/cmd/worker/main.go +++ b/cmd/worker/main.go @@ -12,6 +12,7 @@ import ( "cloud.google.com/go/profiler" "github.com/TicketsBot-cloud/archiverclient" + "github.com/TicketsBot-cloud/common/experiments" "github.com/TicketsBot-cloud/common/model" "github.com/TicketsBot-cloud/common/observability" "github.com/TicketsBot-cloud/common/premium" @@ -92,6 +93,11 @@ func main() { dbclient.Connect(logger.With(zap.String("service", "database"))) logger.Info("Connected to DB") + logger.Info("Initializing experiments manager") + expManager := experiments.NewManager(redis.Client, dbclient.Client) + experiments.SetGlobalManager(expManager) + logger.Info("Initialized experiments manager") + logger.Info("Loading i18n files") i18n.Init() logger.Info("Loaded i18n files") diff --git a/event/httplisten.go b/event/httplisten.go index 84625e2..a4d2a27 100644 --- a/event/httplisten.go +++ b/event/httplisten.go @@ -169,6 +169,23 @@ func interactionHandler(redis *redis.Client, cache *cache.PgCache) func(*gin.Con ctx.JSON(200, res) ctx.Writer.Flush() case data := <-responseCh: + if data.Type() == button.ResponseTypeModal { + if resp, ok := data.(button.ResponseModal); ok { + modalJSON, _ := json.Marshal(data.Build()) + logrus.Infof("responding with modal to message component interaction (custom_id: %s, title: %s, components: %d, json: %s)", + resp.Data.CustomId, resp.Data.Title, len(resp.Data.Components), string(modalJSON)) + + if resp.Data.CustomId == "" { + sentry.Error(fmt.Errorf("modal response has empty custom_id")) + } + if resp.Data.Title == "" { + sentry.Error(fmt.Errorf("modal response has empty title")) + } + if len(resp.Data.Components) == 0 { + sentry.Error(fmt.Errorf("modal response has no components")) + } + } + } ctx.JSON(200, data.Build()) ctx.Writer.Flush() } diff --git a/experiments/experiments.go b/experiments/experiments.go deleted file mode 100644 index 9c49123..0000000 --- a/experiments/experiments.go +++ /dev/null @@ -1,68 +0,0 @@ -package experiments - -import ( - "context" - "os" - "slices" - "strconv" - "strings" - - "github.com/TicketsBot-cloud/worker/bot/dbclient" - "github.com/TicketsBot-cloud/worker/bot/redis" -) - -type Experiment string - -const ( - COMPONENTS_V2_STATISTICS Experiment = "COMPONENTS_V2_STATISTICS" -) - -var List = []Experiment{ - COMPONENTS_V2_STATISTICS, -} - -func HasFeature(ctx context.Context, guildId uint64, experiment Experiment) bool { - if os.Getenv("ENABLE_ALL_EXPERIMENTS") == "true" { - return true - } - - experimentServers := strings.Split(os.Getenv("EXPERIMENT_SERVERS"), ",") - if slices.Contains(experimentServers, strconv.FormatUint(guildId, 10)) { - return true - } - - rolloutPercentage := 0 - - redisPercentage, err := redis.GetExperimentRolloutPercentage(ctx, strings.ToLower(string(experiment))) - if err == nil { - rolloutPercentage = redisPercentage - } else { - if err == redis.ErrNil { - // Key does not exist, check database - dbExperiment, dbErr := dbclient.Client.Experiment.GetByName(ctx, string(experiment)) - if dbErr != nil || dbExperiment == nil { - // If we can't find it in the database, default to 0% - rolloutPercentage = 0 - } else { - rolloutPercentage = dbExperiment.RolloutPercentage - - // Cache in Redis for future use - _ = redis.SetExperimentRolloutPercentage(ctx, strings.ToLower(string(experiment)), rolloutPercentage) - } - } else { - rolloutPercentage = 0 - } - } - - // If rollout is 100%, everyone is in the experiment - if rolloutPercentage == 100 { - return true - } - - // If rollout is 0%, no one is in the experiment - if rolloutPercentage == 0 { - return false - } - - return guildId%100 <= uint64(rolloutPercentage) -} diff --git a/go.mod b/go.mod index 9b2b8ec..2eaf7a5 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.24.0 toolchain go1.24.2 -replace github.com/TicketsBot-cloud/database => ../database +//replace github.com/TicketsBot-cloud/database => ../database + +//replace github.com/TicketsBot-cloud/common => ../common //replace github.com/TicketsBot-cloud/gdl => ../gdl @@ -16,8 +18,8 @@ require ( cloud.google.com/go/profiler v0.4.2 github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc - github.com/TicketsBot-cloud/common v0.0.0-20250509064208-a2d357175463 - github.com/TicketsBot-cloud/database v0.0.0-20251005195038-fd63a423fcb7 + github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90 + github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642 github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd github.com/caarlos0/env/v10 v10.0.0 github.com/elliotchance/orderedmap v1.8.0 diff --git a/go.sum b/go.sum index 050035e..5afbd53 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,10 @@ github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c/go.mod h1:zecIz09jVDSHyhV6NYgTko0NEN0QJGiZbzcxHRjQLzc= github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc h1:2EFgb6gQMedsO7YkqUPH1zY8+L6i3sGuV/YAv0gPqhQ= github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= -github.com/TicketsBot-cloud/common v0.0.0-20250509064208-a2d357175463 h1:ZO2kw9lpsy4umoWFmAt9WKVFb3WvU5oSif2Yw4K7Lj4= -github.com/TicketsBot-cloud/common v0.0.0-20250509064208-a2d357175463/go.mod h1:PL5j/omFvU0NeyTKCESmOF+3GscaEuM0aqmI4yFcCFY= +github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90 h1:gv7uVneGf22eTwT976iWpCPWA6yPY51nfWPadcDreWI= +github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90/go.mod h1:uRra7EnPtmd7Og9dLZ7SYgb0AwODeBnd5qSaDdWcjtU= +github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642 h1:dTF/3+ebJfLJrq/MM7mJoo7uHdo1iec+IHnFZhVEd90= +github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642/go.mod h1:LPDEn9e5wccH7rq/pUlVcL3UhyLnnwdM2dhj0tp/ljo= github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd h1:C4GzdEYarK2V81oAarvRW2RA2wzRGRydCAzBKtb/lGU= github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot-cloud/logarchiver v0.0.0-20250514201320-d5141071a6eb h1:BapforRlvTfWP8MX8DTsxVM40oDgQorJVo/cnNGTaKU= From fe255245cb667ba0bdf3b4732fe74d4a5c62df0d Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 26 Oct 2025 18:45:08 +0000 Subject: [PATCH 3/3] bump db Signed-off-by: Ben --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 2eaf7a5..d328579 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/TicketsBot-cloud/analytics-client v0.0.0-20250604180646-6606dfc8fc8c github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90 - github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642 + github.com/TicketsBot-cloud/database v0.0.0-20251026184346-1c1920978196 github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd github.com/caarlos0/env/v10 v10.0.0 github.com/elliotchance/orderedmap v1.8.0 @@ -40,9 +40,9 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/net v0.43.0 + golang.org/x/net v0.45.0 golang.org/x/sync v0.17.0 - golang.org/x/tools v0.36.0 + golang.org/x/tools v0.37.0 gopkg.in/alexcesaro/statsd.v2 v2.0.0 ) @@ -137,12 +137,12 @@ require ( go.opentelemetry.io/otel/trace v1.36.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.17.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/mod v0.27.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/api v0.232.0 // indirect google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect diff --git a/go.sum b/go.sum index 5afbd53..3933fb8 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc h1 github.com/TicketsBot-cloud/archiverclient v0.0.0-20250514201416-cf23f65eb3fc/go.mod h1:Mux1bEPpOHwRw1wo6Fa6qJLJH9Erk9qv1yAIfLi1Wmw= github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90 h1:gv7uVneGf22eTwT976iWpCPWA6yPY51nfWPadcDreWI= github.com/TicketsBot-cloud/common v0.0.0-20251026182733-99fa0dc31d90/go.mod h1:uRra7EnPtmd7Og9dLZ7SYgb0AwODeBnd5qSaDdWcjtU= -github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642 h1:dTF/3+ebJfLJrq/MM7mJoo7uHdo1iec+IHnFZhVEd90= -github.com/TicketsBot-cloud/database v0.0.0-20251018211325-3d2b9b4ad642/go.mod h1:LPDEn9e5wccH7rq/pUlVcL3UhyLnnwdM2dhj0tp/ljo= +github.com/TicketsBot-cloud/database v0.0.0-20251026184346-1c1920978196 h1:nfYvmqQ+btzW7Hy90KEaLmaCY4pqDL+6bgl9vwkYJh8= +github.com/TicketsBot-cloud/database v0.0.0-20251026184346-1c1920978196/go.mod h1:LPDEn9e5wccH7rq/pUlVcL3UhyLnnwdM2dhj0tp/ljo= github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd h1:C4GzdEYarK2V81oAarvRW2RA2wzRGRydCAzBKtb/lGU= github.com/TicketsBot-cloud/gdl v0.0.0-20251007163257-7e59b92d02dd/go.mod h1:CdwBR2egPtxUXjD2CgC9ZwfuB8dz9HPePM8nuG6dt7Y= github.com/TicketsBot-cloud/logarchiver v0.0.0-20250514201320-d5141071a6eb h1:BapforRlvTfWP8MX8DTsxVM40oDgQorJVo/cnNGTaKU= @@ -447,8 +447,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -458,8 +458,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -472,8 +472,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -505,16 +505,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -524,8 +524,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -541,8 +541,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=