diff --git a/.gitignore b/.gitignore index 9451b20d65..12121446b6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ cmd/yagpdb/shutdown_logs cmd/yagpdb/safebrowsing_db cmd/yagpdb/static/video/* +cmd/capturepanics/capturepanics +cmd/shardorchestrator/shardorchestrator + # We don't want filthy pre-configured config files here ! >:O # The graw agent file *.agent diff --git a/automod/automod_bot.go b/automod/automod_bot.go index 2b9622337d..2771154a4b 100644 --- a/automod/automod_bot.go +++ b/automod/automod_bot.go @@ -7,7 +7,7 @@ import ( "sort" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/automod/models" "github.com/jonas747/yagpdb/bot" @@ -50,15 +50,15 @@ func (p *Plugin) checkMessage(evt *eventsystem.EventData, msg *discordgo.Message return true } - cs := bot.State.Channel(true, msg.ChannelID) - if cs == nil || cs.Guild == nil { + cs := evt.GS.GetChannel(msg.ChannelID) + if cs == nil { return true } - ms := dstate.MSFromDGoMember(cs.Guild, msg.Member) + ms := dstate.MemberStateFromMember(msg.Member) stripped := "" - return !p.CheckTriggers(nil, ms, msg, cs, func(trig *ParsedPart) (activated bool, err error) { + return !p.CheckTriggers(nil, evt.GS, ms, msg, cs, func(trig *ParsedPart) (activated bool, err error) { if stripped == "" { stripped = PrepareMessageForWordCheck(msg.Content) } @@ -68,7 +68,7 @@ func (p *Plugin) checkMessage(evt *eventsystem.EventData, msg *discordgo.Message return } - return cast.CheckMessage(ms, cs, msg, stripped, trig.ParsedSettings) + return cast.CheckMessage(&TriggerContext{GS: evt.GS, MS: ms, Data: trig.ParsedSettings}, cs, msg, stripped) }) } @@ -83,7 +83,7 @@ func (p *Plugin) checkViolationTriggers(ctxData *TriggeredRuleData, violationNam return } - rulesets, err := p.FetchGuildRulesets(ctxData.GS) + rulesets, err := p.FetchGuildRulesets(ctxData.GS.ID) if err != nil { logger.WithError(err).WithField("guild", ctxData.GS.ID).Error("failed fetching guild rulesets") return @@ -94,7 +94,7 @@ func (p *Plugin) checkViolationTriggers(ctxData *TriggeredRuleData, violationNam } // retrieve users violations - userViolations, err := models.AutomodViolations(qm.Where("guild_id = ? AND user_id = ? AND name = ?", ctxData.GS.ID, ctxData.MS.ID, violationName)).AllG(context.Background()) + userViolations, err := models.AutomodViolations(qm.Where("guild_id = ? AND user_id = ? AND name = ?", ctxData.GS.ID, ctxData.MS.User.ID, violationName)).AllG(context.Background()) if err != nil { logger.WithError(err).Error("automod failed retrieving user violations") return @@ -195,8 +195,8 @@ func (p *Plugin) checkViolationTriggers(ctxData *TriggeredRuleData, violationNam func (p *Plugin) handleGuildMemberUpdate(evt *eventsystem.EventData) { evtData := evt.GuildMemberUpdate() - ms := dstate.MSFromDGoMember(evt.GS, evtData.Member) - if ms.Nick == "" { + ms := dstate.MemberStateFromMember(evtData.Member) + if ms.Member.Nick == "" { return } @@ -206,51 +206,67 @@ func (p *Plugin) handleGuildMemberUpdate(evt *eventsystem.EventData) { func (p *Plugin) handleGuildMemberJoin(evt *eventsystem.EventData) { evtData := evt.GuildMemberAdd() - ms := dstate.MSFromDGoMember(evt.GS, evtData.Member) + ms := dstate.MemberStateFromMember(evtData.Member) p.checkJoin(ms) p.checkUsername(ms) } func (p *Plugin) checkNickname(ms *dstate.MemberState) { - p.CheckTriggers(nil, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { + gs := bot.State.GetGuild(ms.GuildID) + if gs == nil { + return + } + + p.CheckTriggers(nil, gs, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { cast, ok := trig.Part.(NicknameListener) if !ok { return false, nil } - return cast.CheckNickname(ms, trig.ParsedSettings) + return cast.CheckNickname(&TriggerContext{GS: gs, MS: ms, Data: trig.ParsedSettings}) }) } func (p *Plugin) checkUsername(ms *dstate.MemberState) { - p.CheckTriggers(nil, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { + gs := bot.State.GetGuild(ms.GuildID) + if gs == nil { + return + } + + p.CheckTriggers(nil, gs, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { cast, ok := trig.Part.(UsernameListener) if !ok { return false, nil } - return cast.CheckUsername(ms, trig.ParsedSettings) + return cast.CheckUsername(&TriggerContext{GS: gs, MS: ms, Data: trig.ParsedSettings}) }) } func (p *Plugin) checkJoin(ms *dstate.MemberState) { - p.CheckTriggers(nil, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { + gs := bot.State.GetGuild(ms.GuildID) + if gs == nil { + return + } + + p.CheckTriggers(nil, gs, ms, nil, nil, func(trig *ParsedPart) (activated bool, err error) { cast, ok := trig.Part.(JoinListener) if !ok { return false, nil } - return cast.CheckJoin(ms, trig.ParsedSettings) + return cast.CheckJoin(&TriggerContext{GS: gs, MS: ms, Data: trig.ParsedSettings}) }) } -func (p *Plugin) CheckTriggers(rulesets []*ParsedRuleset, ms *dstate.MemberState, msg *discordgo.Message, cs *dstate.ChannelState, checkF func(trp *ParsedPart) (activated bool, err error)) bool { +func (p *Plugin) CheckTriggers(rulesets []*ParsedRuleset, gs *dstate.GuildSet, ms *dstate.MemberState, msg *discordgo.Message, cs *dstate.ChannelState, checkF func(trp *ParsedPart) (activated bool, err error)) bool { + if rulesets == nil { var err error - rulesets, err = p.FetchGuildRulesets(ms.Guild) + rulesets, err = p.FetchGuildRulesets(gs.ID) if err != nil { - logger.WithError(err).WithField("guild", ms.Guild.ID).Error("failed fetching triggers") + logger.WithError(err).WithField("guild", msg.GuildID).Error("failed fetching triggers") return false } @@ -269,7 +285,7 @@ func (p *Plugin) CheckTriggers(rulesets []*ParsedRuleset, ms *dstate.MemberState ctxData := &TriggeredRuleData{ MS: ms, CS: cs, - GS: ms.Guild, + GS: gs, Plugin: p, Ruleset: rs, @@ -409,9 +425,7 @@ func (p *Plugin) RulesetRulesTriggeredCondsPassed(ruleset *ParsedRuleset, trigge cid := int64(0) if ctxData.CS != nil { - ctxData.CS.Owner.RLock() cname = ctxData.CS.Name - ctxData.CS.Owner.RUnlock() cid = ctxData.CS.ID } @@ -444,8 +458,8 @@ func (p *Plugin) RulesetRulesTriggeredCondsPassed(ruleset *ParsedRuleset, trigge RuleID: null.Int64{Int64: rule.Model.ID, Valid: true}, RuleName: rule.Model.Name, RulesetName: rule.Model.R.Ruleset.Name, - UserID: ctxData.MS.ID, - UserName: ctxData.MS.Username + "#" + ctxData.MS.StrDiscriminator(), + UserID: ctxData.MS.User.ID, + UserName: ctxData.MS.User.Username + "#" + ctxData.MS.User.Discriminator, Extradata: serializedExtraData, } } @@ -471,14 +485,14 @@ func (p *Plugin) RulesetRulesTriggeredCondsPassed(ruleset *ParsedRuleset, trigge } } -const ( - CacheKeyRulesets bot.GSCacheKey = "automod_2_rulesets" - CacheKeyLists bot.GSCacheKey = "automod_2_lists" +var ( + cachedRulesets = common.CacheSet.RegisterSlot("amod2_rulesets", nil, int64(0)) + cachedLists = common.CacheSet.RegisterSlot("amod2_lists", nil, int64(0)) ) -func (p *Plugin) FetchGuildRulesets(gs *dstate.GuildState) ([]*ParsedRuleset, error) { - v, err := gs.UserCacheFetch(CacheKeyRulesets, func() (interface{}, error) { - rulesets, err := models.AutomodRulesets(qm.Where("guild_id=?", gs.ID), +func (p *Plugin) FetchGuildRulesets(guildID int64) ([]*ParsedRuleset, error) { + v, err := cachedRulesets.GetCustomFetch(guildID, func(key interface{}) (interface{}, error) { + rulesets, err := models.AutomodRulesets(qm.Where("guild_id=?", guildID), qm.Load("RulesetAutomodRules.RuleAutomodRuleData"), qm.Load("RulesetAutomodRulesetConditions")).AllG(context.Background()) if err != nil { @@ -505,9 +519,9 @@ func (p *Plugin) FetchGuildRulesets(gs *dstate.GuildState) ([]*ParsedRuleset, er return cast, nil } -func FetchGuildLists(gs *dstate.GuildState) ([]*models.AutomodList, error) { - v, err := gs.UserCacheFetch(CacheKeyLists, func() (interface{}, error) { - lists, err := models.AutomodLists(qm.Where("guild_id = ?", gs.ID)).AllG(context.Background()) +func FetchGuildLists(guildID int64) ([]*models.AutomodList, error) { + v, err := cachedLists.GetCustomFetch(guildID, func(key interface{}) (interface{}, error) { + lists, err := models.AutomodLists(qm.Where("guild_id = ?", guildID)).AllG(context.Background()) if err != nil { return nil, err } @@ -525,8 +539,8 @@ func FetchGuildLists(gs *dstate.GuildState) ([]*models.AutomodList, error) { var ErrListNotFound = errors.New("list not found") -func FindFetchGuildList(gs *dstate.GuildState, listID int64) (*models.AutomodList, error) { - lists, err := FetchGuildLists(gs) +func FindFetchGuildList(guildID int64, listID int64) (*models.AutomodList, error) { + lists, err := FetchGuildLists(guildID) if err != nil { return nil, err } diff --git a/automod/automod_web.go b/automod/automod_web.go index 299e0b66e6..e5196020ba 100644 --- a/automod/automod_web.go +++ b/automod/automod_web.go @@ -13,12 +13,12 @@ import ( "github.com/fatih/structs" "github.com/gorilla/schema" - "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/automod/models" - "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/cplogs" "github.com/jonas747/yagpdb/common/featureflags" + "github.com/jonas747/yagpdb/common/pubsub" "github.com/jonas747/yagpdb/web" "github.com/volatiletech/sqlboiler/boil" "github.com/volatiletech/sqlboiler/queries/qm" @@ -205,7 +205,7 @@ func (p *Plugin) handlePostAutomodCreateList(w http.ResponseWriter, r *http.Requ err = list.InsertG(r.Context(), boil.Infer()) if err == nil { - bot.EvictGSCache(g.ID, CacheKeyLists) + pubsub.EvictCacheSet(cachedLists, g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyNewList)) } return tmpl, err @@ -228,7 +228,7 @@ func (p *Plugin) handlePostAutomodUpdateList(w http.ResponseWriter, r *http.Requ list.Content = strings.Fields(data.Content) _, err = list.UpdateG(r.Context(), boil.Whitelist("content")) if err == nil { - bot.EvictGSCache(g.ID, CacheKeyLists) + pubsub.EvictCacheSet(cachedLists, g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedList)) } return tmpl, err @@ -245,7 +245,7 @@ func (p *Plugin) handlePostAutomodDeleteList(w http.ResponseWriter, r *http.Requ _, err = list.DeleteG(r.Context()) if err == nil { - bot.EvictGSCache(g.ID, CacheKeyLists) + pubsub.EvictCacheSet(cachedLists, g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyRemovedList)) } return tmpl, err @@ -390,7 +390,7 @@ func (p *Plugin) handlePostAutomodUpdateRuleset(w http.ResponseWriter, r *http.R return tmpl, err } - bot.EvictGSCache(g.ID, CacheKeyRulesets) + pubsub.EvictCacheSet(cachedRulesets, g.ID) featureflags.MarkGuildDirty(g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedRuleset)) @@ -413,7 +413,7 @@ func (p *Plugin) handlePostAutomodDeleteRuleset(w http.ResponseWriter, r *http.R delete(tmpl, "CurrentRuleset") if rows > 0 { - bot.EvictGSCache(g.ID, CacheKeyRulesets) + pubsub.EvictCacheSet(cachedRulesets, g.ID) featureflags.MarkGuildDirty(g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyRemovedRuleset)) } @@ -523,7 +523,7 @@ func (p *Plugin) handlePostAutomodUpdateRule(w http.ResponseWriter, r *http.Requ WebLoadRuleSettings(r, tmpl, ruleSet) - bot.EvictGSCache(g.ID, CacheKeyRulesets) + pubsub.EvictCacheSet(cachedRulesets, g.ID) featureflags.MarkGuildDirty(g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedRule)) @@ -599,7 +599,7 @@ func CheckLimits(exec boil.ContextExecutor, rule *models.AutomodRule, tmpl web.T return } -func ReadRuleRowData(guild *discordgo.Guild, tmpl web.TemplateData, rawData []RuleRowData, form url.Values, namePrefix string) (result []*models.AutomodRuleDatum, validationOK bool, err error) { +func ReadRuleRowData(guild *dstate.GuildSet, tmpl web.TemplateData, rawData []RuleRowData, form url.Values, namePrefix string) (result []*models.AutomodRuleDatum, validationOK bool, err error) { parsedSettings := make([]*ParsedPart, 0, len(rawData)) for i, entry := range rawData { @@ -724,7 +724,7 @@ func (p *Plugin) handlePostAutomodDeleteRule(w http.ResponseWriter, r *http.Requ _, err := v.DeleteG(r.Context()) if err == nil { ruleset.R.RulesetAutomodRules = append(ruleset.R.RulesetAutomodRules[:k], ruleset.R.RulesetAutomodRules[k+1:]...) - bot.EvictGSCache(g.ID, CacheKeyRulesets) + pubsub.EvictCacheSet(cachedRulesets, g.ID) featureflags.MarkGuildDirty(g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyRemovedRule)) } diff --git a/automod/commands.go b/automod/commands.go index 2151a26ca5..b7822771a8 100644 --- a/automod/commands.go +++ b/automod/commands.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/automod/models" "github.com/jonas747/yagpdb/bot/paginatedmessages" "github.com/jonas747/yagpdb/commands" @@ -43,8 +43,8 @@ func (p *Plugin) AddCommands() { return nil, err } - data.GuildData.GS.UserCacheDel(CacheKeyRulesets) - data.GuildData.GS.UserCacheDel(CacheKeyLists) + cachedRulesets.Delete(data.GuildData.GS.ID) + cachedLists.Delete(data.GuildData.GS.ID) featureflags.MarkGuildDirty(data.GuildData.GS.ID) enabledStr := "enabled" @@ -376,7 +376,7 @@ func (p *Plugin) AddCommands() { container.AddCommand(cmdListVLC, cmdListVLC.GetTrigger()) container.AddCommand(cmdDelV, cmdDelV.GetTrigger()) container.AddCommand(cmdClearV, cmdClearV.GetTrigger()) - commands.RegisterSlashCommandsContainer(container, false, func(gs *dstate.GuildState) ([]int64, error) { + commands.RegisterSlashCommandsContainer(container, false, func(gs *dstate.GuildSet) ([]int64, error) { return nil, nil }) } diff --git a/automod/conditions.go b/automod/conditions.go index 4d58fe6421..0d9bb9363b 100644 --- a/automod/conditions.go +++ b/automod/conditions.go @@ -63,7 +63,7 @@ func (mrc *MemberRolesCondition) UserSettings() []*SettingDef { func (mrc *MemberRolesCondition) IsMet(data *TriggeredRuleData, settings interface{}) (bool, error) { settingsCast := settings.(*MemberRolesConditionData) for _, r := range settingsCast.Roles { - if common.ContainsInt64Slice(data.MS.Roles, r) { + if common.ContainsInt64Slice(data.MS.Member.Roles, r) { if mrc.Blacklist { // Had a blacklist role, this condition is not met return false, nil @@ -334,7 +334,7 @@ func (ac *AccountAgeCondition) UserSettings() []*SettingDef { func (ac *AccountAgeCondition) IsMet(data *TriggeredRuleData, settings interface{}) (bool, error) { settingsCast := settings.(*AccountAgeConditionData) - created := bot.SnowflakeToTime(data.MS.ID) + created := bot.SnowflakeToTime(data.MS.User.ID) minutes := int(time.Since(created).Minutes()) if minutes <= settingsCast.Treshold { // account were made within threshold @@ -406,13 +406,20 @@ func (mc *MemberAgecondition) UserSettings() []*SettingDef { func (mc *MemberAgecondition) IsMet(data *TriggeredRuleData, settings interface{}) (bool, error) { settingsCast := settings.(*MemberAgeConditionData) - joinedAt := data.MS.JoinedAt - if joinedAt.IsZero() { - newMS, err := bot.GetMemberJoinedAt(data.GS.ID, data.MS.ID) + var joinedAt time.Time + if data.MS.Member != nil && data.MS.Member.JoinedAt != "" { + joinedAt, _ = data.MS.Member.JoinedAt.Parse() + } else { + newMS, err := bot.GetMember(data.GS.ID, data.MS.User.ID) if err != nil { return false, err } - joinedAt = newMS.JoinedAt + + if newMS.Member != nil { + joinedAt, _ = newMS.Member.JoinedAt.Parse() + } else { + return false, nil + } } minutes := int(time.Since(joinedAt).Minutes()) @@ -476,10 +483,10 @@ func (bc *BotCondition) UserSettings() []*SettingDef { func (bc *BotCondition) IsMet(data *TriggeredRuleData, settings interface{}) (bool, error) { if bc.Ignore { - return !data.MS.Bot, nil + return !data.MS.User.Bot, nil } - return data.MS.Bot, nil + return data.MS.User.Bot, nil } func (bc *BotCondition) MergeDuplicates(data []interface{}) interface{} { diff --git a/automod/effects.go b/automod/effects.go index f0be9b61ee..a89ca538b2 100644 --- a/automod/effects.go +++ b/automod/effects.go @@ -6,7 +6,7 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/automod/models" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" @@ -115,70 +115,48 @@ func (del *DeleteMessagesEffect) Apply(ctxData *TriggeredRuleData, settings inte settingsCast := settings.(*DeleteMessagesEffectData) timeLimit := time.Now().Add(-time.Second * time.Duration(settingsCast.TimeLimit)) - ctxData.GS.RLock() - defer ctxData.GS.RUnlock() - var channel *dstate.ChannelState if ctxData.CS != nil { channel = ctxData.CS } else { - - // no channel in context, attempt to find the last channel the user spoke in - var lastMessage *dstate.MessageState - - for _, c := range ctxData.GS.Channels { - for i := len(c.Messages) - 1; i >= 0; i-- { - cMsg := c.Messages[i] - - if settingsCast.TimeLimit > 0 && timeLimit.After(cMsg.ParsedCreated) { - break - } - - if lastMessage != nil && lastMessage.ParsedCreated.After(cMsg.ParsedCreated) { - break - } - - if cMsg.Author.ID == ctxData.MS.ID { - channel = c - lastMessage = cMsg - break - } - } - } + // do nothing for now + return nil } if channel == nil { return nil } - messages := make([]int64, 0, 100) + messages := bot.State.GetMessages(ctxData.GS.ID, ctxData.CS.ID, &dstate.MessagesQuery{ + Limit: 1000, + }) - for i := len(channel.Messages) - 1; i >= 0; i-- { - cMsg := channel.Messages[i] + deleteMessages := make([]int64, 0) - if settingsCast.TimeLimit > 0 && timeLimit.After(cMsg.ParsedCreated) { + for _, cMsg := range messages { + if settingsCast.TimeLimit > 0 && timeLimit.After(cMsg.ParsedCreatedAt) { break } - if cMsg.Author.ID != ctxData.MS.ID { + if cMsg.Author.ID != ctxData.MS.User.ID { continue } - messages = append(messages, cMsg.ID) - if len(messages) >= 100 || len(messages) >= settingsCast.NumMessages { + deleteMessages = append(deleteMessages, cMsg.ID) + if len(deleteMessages) >= 100 || len(deleteMessages) >= settingsCast.NumMessages { break } } - if len(messages) < 0 { + if len(deleteMessages) < 0 { return nil } go func(cs *dstate.ChannelState, messages []int64) { // deleting messages too fast can sometimes make them still show in the discord client even after deleted time.Sleep(500 * time.Millisecond) - bot.MessageDeleteQueue.DeleteMessages(cs.Guild.ID, cs.ID, messages...) - }(channel, messages) + bot.MessageDeleteQueue.DeleteMessages(cs.GuildID, cs.ID, messages...) + }(channel, deleteMessages) return nil } @@ -228,7 +206,7 @@ func (vio *AddViolationEffect) Apply(ctxData *TriggeredRuleData, settings interf settingsCast := settings.(*AddViolationEffectData) violation := &models.AutomodViolation{ GuildID: ctxData.GS.ID, - UserID: ctxData.MS.ID, + UserID: ctxData.MS.User.ID, RuleID: null.Int64From(ctxData.CurrentRule.Model.ID), Name: settingsCast.Name, } @@ -294,7 +272,7 @@ func (kick *KickUserEffect) Apply(ctxData *TriggeredRuleData, settings interface reason += ctxData.ConstructReason(true) } - err := moderation.KickUser(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, reason, ctxData.MS.DGoUser()) + err := moderation.KickUser(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, reason, &ctxData.MS.User) return err } @@ -365,7 +343,7 @@ func (ban *BanUserEffect) Apply(ctxData *TriggeredRuleData, settings interface{} } duration := time.Duration(settingsCast.Duration) * time.Minute - err := moderation.BanUserWithDuration(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, reason, ctxData.MS.DGoUser(), duration, settingsCast.MessageDeleteDays) + err := moderation.BanUserWithDuration(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, reason, &ctxData.MS.User, duration, settingsCast.MessageDeleteDays) return err } @@ -481,7 +459,7 @@ func (warn *WarnUserEffect) Apply(ctxData *TriggeredRuleData, settings interface reason += ctxData.ConstructReason(true) } - err := moderation.WarnUser(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, ctxData.MS.DGoUser(), reason) + err := moderation.WarnUser(nil, ctxData.GS.ID, ctxData.CS, ctxData.Message, common.BotUser, &ctxData.MS.User, reason) return err } @@ -528,18 +506,13 @@ func (sn *SetNicknameEffect) Description() (description string) { func (sn *SetNicknameEffect) Apply(ctxData *TriggeredRuleData, settings interface{}) error { settingsCast := settings.(*SetNicknameEffectData) - curNick := "" - ctxData.GS.RLock() - curNick = ctxData.MS.Nick - ctxData.GS.RUnlock() - - if curNick == settingsCast.NewName { + if ctxData.MS.Member.Nick == settingsCast.NewName { // Avoid infinite recursion return nil } logger.WithField("guild", ctxData.GS.ID).Info("set nickname: ", settingsCast.NewName) - err := common.BotSession.GuildMemberNickname(ctxData.GS.ID, ctxData.MS.ID, settingsCast.NewName) + err := common.BotSession.GuildMemberNickname(ctxData.GS.ID, ctxData.MS.User.ID, settingsCast.NewName) return err } @@ -586,7 +559,7 @@ func (rv *ResetViolationsEffect) Description() (description string) { func (rv *ResetViolationsEffect) Apply(ctxData *TriggeredRuleData, settings interface{}) error { settingsCast := settings.(*ResetViolationsEffectData) - _, err := models.AutomodViolations(qm.Where("guild_id = ? AND user_id = ? AND name = ?", ctxData.GS.ID, ctxData.MS.ID, settingsCast.Name)).DeleteAll(context.Background(), common.PQ) + _, err := models.AutomodViolations(qm.Where("guild_id = ? AND user_id = ? AND name = ?", ctxData.GS.ID, ctxData.MS.User.ID, settingsCast.Name)).DeleteAll(context.Background(), common.PQ) return err } @@ -646,7 +619,7 @@ func (gf *GiveRoleEffect) Apply(ctxData *TriggeredRuleData, settings interface{} } if settingsCast.Duration > 0 { - err := scheduledevents2.ScheduleRemoveRole(context.Background(), ctxData.GS.ID, ctxData.MS.ID, settingsCast.Role, time.Now().Add(time.Second*time.Duration(settingsCast.Duration))) + err := scheduledevents2.ScheduleRemoveRole(context.Background(), ctxData.GS.ID, ctxData.MS.User.ID, settingsCast.Role, time.Now().Add(time.Second*time.Duration(settingsCast.Duration))) if err != nil { return err } @@ -701,7 +674,7 @@ func (rf *RemoveRoleEffect) Description() (description string) { func (rf *RemoveRoleEffect) Apply(ctxData *TriggeredRuleData, settings interface{}) error { settingsCast := settings.(*RemoveRoleEffectData) - if !common.ContainsInt64Slice(ctxData.MS.Roles, settingsCast.Role) { + if !common.ContainsInt64Slice(ctxData.MS.Member.Roles, settingsCast.Role) { return nil } @@ -715,7 +688,7 @@ func (rf *RemoveRoleEffect) Apply(ctxData *TriggeredRuleData, settings interface } if settingsCast.Duration > 0 { - err := scheduledevents2.ScheduleAddRole(context.Background(), ctxData.GS.ID, ctxData.MS.ID, settingsCast.Role, time.Now().Add(time.Second*time.Duration(settingsCast.Duration))) + err := scheduledevents2.ScheduleAddRole(context.Background(), ctxData.GS.ID, ctxData.MS.User.ID, settingsCast.Role, time.Now().Add(time.Second*time.Duration(settingsCast.Duration))) if err != nil { return err } diff --git a/automod/rulepart.go b/automod/rulepart.go index 32df95b6f3..6512e37d8c 100644 --- a/automod/rulepart.go +++ b/automod/rulepart.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/automod/models" ) @@ -153,7 +153,7 @@ type MergeableRulePart interface { type TriggeredRuleData struct { // Should always be available Plugin *Plugin - GS *dstate.GuildState + GS *dstate.GuildSet MS *dstate.MemberState Ruleset *ParsedRuleset @@ -208,11 +208,17 @@ func (t *TriggeredRuleData) ConstructReason(includePrevious bool) string { return builder.String() } +type TriggerContext struct { + GS *dstate.GuildSet + MS *dstate.MemberState + Data interface{} +} + // MessageCondition is a active condition that needs to run on a message type MessageTrigger interface { RulePart - CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (isAffected bool, err error) + CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (isAffected bool, err error) } // ViolationListener is a trigger that gets triggered on a violation @@ -226,19 +232,19 @@ type ViolationListener interface { type NicknameListener interface { RulePart - CheckNickname(ms *dstate.MemberState, data interface{}) (isAffected bool, err error) + CheckNickname(triggerCtx *TriggerContext) (isAffected bool, err error) } // UsernameListener is a trigger that gets triggered on a nickname change type UsernameListener interface { RulePart - CheckUsername(ms *dstate.MemberState, data interface{}) (isAffected bool, err error) + CheckUsername(triggerCtx *TriggerContext) (isAffected bool, err error) } // JoinListener is triggers that does stuff when members joins type JoinListener interface { RulePart - CheckJoin(ms *dstate.MemberState, data interface{}) (isAffected bool, err error) + CheckJoin(triggerCtx *TriggerContext) (isAffected bool, err error) } diff --git a/automod/triggers.go b/automod/triggers.go index 9dd623ef9e..62893a582d 100644 --- a/automod/triggers.go +++ b/automod/triggers.go @@ -8,9 +8,10 @@ import ( "unicode" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/automod/models" "github.com/jonas747/yagpdb/automod_legacy" + "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/safebrowsing" ) @@ -84,8 +85,8 @@ func (mc *MentionsTrigger) UserSettings() []*SettingDef { } } -func (mc *MentionsTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { - dataCast := data.(*MentionsTriggerData) +func (mc *MentionsTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { + dataCast := triggerCtx.Data.(*MentionsTriggerData) if len(m.Mentions) >= dataCast.Treshold { return true, nil } @@ -123,7 +124,7 @@ func (alc *AnyLinkTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (alc *AnyLinkTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, stripped string, data interface{}) (bool, error) { +func (alc *AnyLinkTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { if common.LinkRegex.MatchString(forwardSlashReplacer.Replace(m.Content)) { return true, nil } @@ -180,10 +181,10 @@ func (wl *WordListTrigger) UserSettings() []*SettingDef { } } -func (wl *WordListTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { - dataCast := data.(*WorldListTriggerData) +func (wl *WordListTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { + dataCast := triggerCtx.Data.(*WorldListTriggerData) - list, err := FindFetchGuildList(cs.Guild, dataCast.ListID) + list, err := FindFetchGuildList(triggerCtx.GS.ID, dataCast.ListID) if err != nil { return false, nil } @@ -259,10 +260,10 @@ func (dt *DomainTrigger) UserSettings() []*SettingDef { } } -func (dt *DomainTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { - dataCast := data.(*DomainTriggerData) +func (dt *DomainTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { + dataCast := triggerCtx.Data.(*DomainTriggerData) - list, err := FindFetchGuildList(cs.Guild, dataCast.ListID) + list, err := FindFetchGuildList(triggerCtx.GS.ID, dataCast.ListID) if err != nil { return false, nil } @@ -448,8 +449,8 @@ func (caps *AllCapsTrigger) UserSettings() []*SettingDef { } } -func (caps *AllCapsTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { - dataCast := data.(*AllCapsTriggerData) +func (caps *AllCapsTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { + dataCast := triggerCtx.Data.(*AllCapsTriggerData) if len(m.Content) < dataCast.MinLength { return false, nil @@ -512,7 +513,7 @@ func (inv *ServerInviteTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (inv *ServerInviteTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (inv *ServerInviteTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { containsBadInvited := automod_legacy.CheckMessageForBadInvites(m.Content, m.GuildID) return containsBadInvited, nil } @@ -547,7 +548,7 @@ func (g *GoogleSafeBrowsingTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (g *GoogleSafeBrowsingTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (g *GoogleSafeBrowsingTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { threat, err := safebrowsing.CheckString(forwardSlashReplacer.Replace(m.Content)) if err != nil { logger.WithError(err).Error("Failed checking urls against google safebrowser") @@ -643,39 +644,38 @@ func (s *SlowmodeTrigger) UserSettings() []*SettingDef { } } -func (s *SlowmodeTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (s *SlowmodeTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { if s.Attachments && len(m.Attachments) < 1 { return false, nil } - settings := data.(*SlowmodeTriggerData) + settings := triggerCtx.Data.(*SlowmodeTriggerData) within := time.Duration(settings.Interval) * time.Second now := time.Now() amount := 1 - cs.Owner.RLock() - defer cs.Owner.RUnlock() + messages := bot.State.GetMessages(cs.GuildID, cs.ID, &dstate.MessagesQuery{ + Limit: 1000, + }) // New messages are at the end - for i := len(cs.Messages) - 1; i >= 0; i-- { - cMsg := cs.Messages[i] - - age := now.Sub(cMsg.ParsedCreated) + for _, v := range messages { + age := now.Sub(v.ParsedCreatedAt) if age > within { break } - if m.ID == cMsg.ID { + if m.ID == v.ID { continue } - if !s.ChannelBased && cMsg.Author.ID != ms.ID { + if !s.ChannelBased && v.Author.ID != triggerCtx.MS.User.ID { continue } - if s.Attachments && len(cMsg.Attachments) < 1 { + if s.Attachments && len(v.Attachments) < 1 { continue // were only checking messages with attachments } @@ -753,36 +753,32 @@ func (mt *MultiMsgMentionTrigger) UserSettings() []*SettingDef { } } -func (mt *MultiMsgMentionTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (mt *MultiMsgMentionTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { if len(m.Mentions) < 1 { return false, nil } - settings := data.(*MultiMsgMentionTriggerData) + settings := triggerCtx.Data.(*MultiMsgMentionTriggerData) within := time.Duration(settings.Interval) * time.Second now := time.Now() mentions := make([]int64, 0) - cs.Owner.RLock() - defer cs.Owner.RUnlock() - // New messages are at the end - for i := len(cs.Messages) - 1; i >= 0; i-- { - cMsg := cs.Messages[i] + messages := bot.State.GetMessages(cs.GuildID, cs.ID, &dstate.MessagesQuery{ + Limit: 1000, + }) - age := now.Sub(cMsg.ParsedCreated) + // New messages are at the end + for _, v := range messages { + age := now.Sub(v.ParsedCreatedAt) if age > within { break } - if mt.ChannelBased || cMsg.Author.ID == ms.ID { + if mt.ChannelBased || v.Author.ID == triggerCtx.MS.GuildID { // we only care about unique mentions, e.g mentioning the same user a ton wont do anythin - for _, msgMention := range cMsg.Mentions { - if msgMention == nil { - continue - } - + for _, msgMention := range v.Mentions { if settings.CountDuplicates || !common.ContainsInt64Slice(mentions, msgMention.ID) { mentions = append(mentions, msgMention.ID) } @@ -829,8 +825,8 @@ func (r *MessageRegexTrigger) Description() string { return "Triggers when a message matches the provided regex" } -func (r *MessageRegexTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { - dataCast := data.(*BaseRegexTriggerData) +func (r *MessageRegexTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { + dataCast := triggerCtx.Data.(*BaseRegexTriggerData) item, err := RegexCache.Fetch(dataCast.Regex, time.Minute*10, func() (interface{}, error) { re, err := regexp.Compile(dataCast.Regex) @@ -908,9 +904,9 @@ func (spam *SpamTrigger) UserSettings() []*SettingDef { } } -func (spam *SpamTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (spam *SpamTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { - settingsCast := data.(*SpamTriggerData) + settingsCast := triggerCtx.Data.(*SpamTriggerData) mToCheckAgainst := strings.TrimSpace(strings.ToLower(m.Content)) @@ -918,36 +914,35 @@ func (spam *SpamTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.Channel timeLimit := time.Now().Add(-time.Second * time.Duration(settingsCast.TimeLimit)) - cs.Owner.RLock() - for i := len(cs.Messages) - 1; i >= 0; i-- { - cMsg := cs.Messages[i] + messages := bot.State.GetMessages(cs.GuildID, cs.ID, &dstate.MessagesQuery{ + Limit: 1000, + }) - if cMsg.ID == m.ID { + for _, v := range messages { + if v.ID == m.ID { continue } - if cMsg.Author.ID != m.Author.ID { + if v.Author.ID != m.Author.ID { continue } - if settingsCast.TimeLimit > 0 && timeLimit.After(cMsg.ParsedCreated) { + if settingsCast.TimeLimit > 0 && timeLimit.After(v.ParsedCreatedAt) { // if this message was created before the time limit, then break out break } - if len(cMsg.Attachments) > 0 { + if len(v.Attachments) > 0 { break // treat any attachment as a different message, in the future i may download them and check hash or something? maybe too much } - if strings.ToLower(strings.TrimSpace(cMsg.Content)) == mToCheckAgainst { + if strings.ToLower(strings.TrimSpace(v.Content)) == mToCheckAgainst { count++ } else { break } } - cs.Owner.RUnlock() - if count >= settingsCast.Treshold { return true, nil } @@ -979,8 +974,8 @@ func (r *NicknameRegexTrigger) Description() string { return "Triggers when a members nickname matches the provided regex" } -func (r *NicknameRegexTrigger) CheckNickname(ms *dstate.MemberState, data interface{}) (bool, error) { - dataCast := data.(*BaseRegexTriggerData) +func (r *NicknameRegexTrigger) CheckNickname(t *TriggerContext) (bool, error) { + dataCast := t.Data.(*BaseRegexTriggerData) item, err := RegexCache.Fetch(dataCast.Regex, time.Minute*10, func() (interface{}, error) { re, err := regexp.Compile(dataCast.Regex) @@ -996,7 +991,7 @@ func (r *NicknameRegexTrigger) CheckNickname(ms *dstate.MemberState, data interf } re := item.Value().(*regexp.Regexp) - if re.MatchString(ms.Nick) { + if re.MatchString(t.MS.Member.Nick) { if r.BaseRegexTrigger.Inverse { return false, nil } @@ -1055,15 +1050,15 @@ func (nwl *NicknameWordlistTrigger) UserSettings() []*SettingDef { } } -func (nwl *NicknameWordlistTrigger) CheckNickname(ms *dstate.MemberState, data interface{}) (bool, error) { - dataCast := data.(*NicknameWordlistTriggerData) +func (nwl *NicknameWordlistTrigger) CheckNickname(t *TriggerContext) (bool, error) { + dataCast := t.Data.(*NicknameWordlistTriggerData) - list, err := FindFetchGuildList(ms.Guild, dataCast.ListID) + list, err := FindFetchGuildList(t.GS.ID, dataCast.ListID) if err != nil { return false, nil } - fields := strings.Fields(PrepareMessageForWordCheck(ms.Nick)) + fields := strings.Fields(PrepareMessageForWordCheck(t.MS.Member.Nick)) for _, mf := range fields { contained := false @@ -1112,8 +1107,8 @@ func (r *UsernameRegexTrigger) Description() string { return "Triggers when a member joins with a username that matches the provided regex" } -func (r *UsernameRegexTrigger) CheckUsername(ms *dstate.MemberState, data interface{}) (bool, error) { - dataCast := data.(*BaseRegexTriggerData) +func (r *UsernameRegexTrigger) CheckUsername(t *TriggerContext) (bool, error) { + dataCast := t.Data.(*BaseRegexTriggerData) item, err := RegexCache.Fetch(dataCast.Regex, time.Minute*10, func() (interface{}, error) { re, err := regexp.Compile(dataCast.Regex) @@ -1129,7 +1124,7 @@ func (r *UsernameRegexTrigger) CheckUsername(ms *dstate.MemberState, data interf } re := item.Value().(*regexp.Regexp) - if re.MatchString(ms.Username) { + if re.MatchString(t.MS.User.Username) { if r.BaseRegexTrigger.Inverse { return false, nil } @@ -1188,15 +1183,15 @@ func (uwl *UsernameWordlistTrigger) UserSettings() []*SettingDef { } } -func (uwl *UsernameWordlistTrigger) CheckUsername(ms *dstate.MemberState, data interface{}) (bool, error) { - dataCast := data.(*UsernameWorldlistData) +func (uwl *UsernameWordlistTrigger) CheckUsername(t *TriggerContext) (bool, error) { + dataCast := t.Data.(*UsernameWorldlistData) - list, err := FindFetchGuildList(ms.Guild, dataCast.ListID) + list, err := FindFetchGuildList(t.GS.ID, dataCast.ListID) if err != nil { return false, nil } - fields := strings.Fields(PrepareMessageForWordCheck(ms.Username)) + fields := strings.Fields(PrepareMessageForWordCheck(t.MS.User.Username)) for _, mf := range fields { contained := false @@ -1248,8 +1243,8 @@ func (uv *UsernameInviteTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (uv *UsernameInviteTrigger) CheckUsername(ms *dstate.MemberState, data interface{}) (bool, error) { - if common.ContainsInvite(ms.Username, true, true) != nil { +func (uv *UsernameInviteTrigger) CheckUsername(t *TriggerContext) (bool, error) { + if common.ContainsInvite(t.MS.User.Username, true, true) != nil { return true, nil } @@ -1283,7 +1278,7 @@ func (mj *MemberJoinTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (mj *MemberJoinTrigger) CheckJoin(ms *dstate.MemberState, data interface{}) (isAffected bool, err error) { +func (mj *MemberJoinTrigger) CheckJoin(t *TriggerContext) (isAffected bool, err error) { return true, nil } @@ -1323,7 +1318,7 @@ func (mat *MessageAttachmentTrigger) UserSettings() []*SettingDef { return []*SettingDef{} } -func (mat *MessageAttachmentTrigger) CheckMessage(ms *dstate.MemberState, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string, data interface{}) (bool, error) { +func (mat *MessageAttachmentTrigger) CheckMessage(triggerCtx *TriggerContext, cs *dstate.ChannelState, m *discordgo.Message, mdStripped string) (bool, error) { contains := len(m.Attachments) > 0 if contains && mat.RequiresAttachment { return true, nil diff --git a/automod_legacy/bot.go b/automod_legacy/bot.go index 64f464fc0b..9e082bfaf8 100644 --- a/automod_legacy/bot.go +++ b/automod_legacy/bot.go @@ -4,7 +4,7 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -79,13 +79,13 @@ func CheckMessage(evt *eventsystem.EventData, m *discordgo.Message) bool { return false } - cs := bot.State.Channel(true, m.ChannelID) + cs := evt.GS.GetChannel(m.ChannelID) if cs == nil { logger.WithField("channel", m.ChannelID).Error("Channel not found in state") return false } - config, err := CachedGetConfig(cs.Guild.ID) + config, err := CachedGetConfig(cs.GuildID) if err != nil { logger.WithError(err).Error("Failed retrieving config") return false @@ -95,7 +95,8 @@ func CheckMessage(evt *eventsystem.EventData, m *discordgo.Message) bool { return false } - member := dstate.MSFromDGoMember(cs.Guild, m.Member) + member := dstate.MemberStateFromMember(m.Member) + member.GuildID = m.GuildID del := false // Set if a rule triggered a message delete punishMsg := "" @@ -117,7 +118,7 @@ func CheckMessage(evt *eventsystem.EventData, m *discordgo.Message) bool { del = true } if err != nil { - logger.WithError(err).WithField("guild", cs.Guild.ID).Error("Failed checking aumod rule:", err) + logger.WithError(err).WithField("guild", cs.GuildID).Error("Failed checking aumod rule:", err) continue } @@ -136,12 +137,12 @@ func CheckMessage(evt *eventsystem.EventData, m *discordgo.Message) bool { if !del { if didCheck { - go analytics.RecordActiveUnit(cs.Guild.ID, &Plugin{}, "checked") + go analytics.RecordActiveUnit(cs.GuildID, &Plugin{}, "checked") } return false } - go analytics.RecordActiveUnit(cs.Guild.ID, &Plugin{}, "rule_triggered") + go analytics.RecordActiveUnit(cs.GuildID, &Plugin{}, "rule_triggered") if punishMsg != "" { // Strip last newline @@ -151,13 +152,13 @@ func CheckMessage(evt *eventsystem.EventData, m *discordgo.Message) bool { go func() { switch highestPunish { case PunishNone: - err = moderation.WarnUser(nil, cs.Guild.ID, cs, m, common.BotUser, member.DGoUser(), "Automoderator: "+punishMsg) + err = moderation.WarnUser(nil, cs.GuildID, cs, m, common.BotUser, &member.User, "Automoderator: "+punishMsg) case PunishMute: - err = moderation.MuteUnmuteUser(nil, true, cs.Guild.ID, cs, m, common.BotUser, "Automoderator: "+punishMsg, member, muteDuration) + err = moderation.MuteUnmuteUser(nil, true, cs.GuildID, cs, m, common.BotUser, "Automoderator: "+punishMsg, member, muteDuration) case PunishKick: - err = moderation.KickUser(nil, cs.Guild.ID, cs, m, common.BotUser, "Automoderator: "+punishMsg, member.DGoUser()) + err = moderation.KickUser(nil, cs.GuildID, cs, m, common.BotUser, "Automoderator: "+punishMsg, &member.User) case PunishBan: - err = moderation.BanUser(nil, cs.Guild.ID, cs, m, common.BotUser, "Automoderator: "+punishMsg, member.DGoUser()) + err = moderation.BanUser(nil, cs.GuildID, cs, m, common.BotUser, "Automoderator: "+punishMsg, &member.User) } // Execute the punishment before removing the message to make sure it's included in logs diff --git a/automod_legacy/rules.go b/automod_legacy/rules.go index 24d5a34301..e298b8354e 100644 --- a/automod_legacy/rules.go +++ b/automod_legacy/rules.go @@ -8,7 +8,8 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" + "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/safebrowsing" "github.com/mediocregopher/radix/v3" @@ -105,7 +106,7 @@ func (r BaseRule) ShouldIgnore(evt *discordgo.Message, ms *dstate.MemberState) b } } - for _, role := range ms.Roles { + for _, role := range ms.Member.Roles { if r.IgnoreRoleInt() == role { return true } @@ -129,7 +130,7 @@ func (s *SpamRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del b del = true - punishment, err = s.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "spam")) + punishment, err = s.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "spam")) if err != nil { return } @@ -140,23 +141,22 @@ func (s *SpamRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del b } func (s *SpamRule) FindSpam(evt *discordgo.Message, cs *dstate.ChannelState) bool { - cs.Owner.RLock() - defer cs.Owner.RUnlock() - within := time.Duration(s.Within) * time.Second now := time.Now() amount := 1 - for i := len(cs.Messages) - 1; i >= 0; i-- { - cMsg := cs.Messages[i] + messages := bot.State.GetMessages(cs.GuildID, cs.ID, &dstate.MessagesQuery{ + Limit: 1000, + }) - age := now.Sub(cMsg.ParsedCreated) + for _, v := range messages { + age := now.Sub(v.ParsedCreatedAt) if age > within { break } - if cMsg.Author.ID == evt.Author.ID && evt.ID != cMsg.ID { + if v.Author.ID == evt.Author.ID && evt.ID != v.ID { amount++ } } @@ -169,13 +169,13 @@ type InviteRule struct { } func (i *InviteRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del bool, punishment Punishment, msg string, err error) { - if !CheckMessageForBadInvites(evt.ContentWithMentionsReplaced(), cs.Guild.ID) { + if !CheckMessageForBadInvites(evt.ContentWithMentionsReplaced(), cs.GuildID) { return } del = true - punishment, err = i.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "invite")) + punishment, err = i.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "invite")) if err != nil { return } @@ -251,7 +251,7 @@ func (m *MentionRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (de del = true - punishment, err = m.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "mention")) + punishment, err = m.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "mention")) if err != nil { return } @@ -270,7 +270,7 @@ func (l *LinksRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del } del = true - punishment, err = l.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "links")) + punishment, err = l.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "links")) if err != nil { return } @@ -310,7 +310,7 @@ func (w *WordsRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del // Fonud a bad word! del = true - punishment, err = w.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "badword")) + punishment, err = w.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "badword")) msg = fmt.Sprintf("The word `%s` is banned, watch your language.", word) return @@ -368,7 +368,7 @@ func (s *SitesRule) Check(evt *discordgo.Message, cs *dstate.ChannelState) (del return } - punishment, err = s.PushViolation(KeyViolations(cs.Guild.ID, evt.Author.ID, "badlink")) + punishment, err = s.PushViolation(KeyViolations(cs.GuildID, evt.Author.ID, "badlink")) extraInfo := "" if threatList != "" { extraInfo = "(sb: " + threatList + ")" diff --git a/automod_legacy/web.go b/automod_legacy/web.go index bd65da990d..0e5d4fa0d3 100644 --- a/automod_legacy/web.go +++ b/automod_legacy/web.go @@ -14,12 +14,6 @@ import ( "goji.io/pat" ) -type CtxKey int - -const ( - CurrentConfig CtxKey = iota -) - type GeneralForm struct { Enabled bool } diff --git a/autorole/autorole.go b/autorole/autorole.go index e9b250ecb4..61d83db99d 100644 --- a/autorole/autorole.go +++ b/autorole/autorole.go @@ -8,6 +8,11 @@ import ( var confDisableNonPremiumRetroActiveAssignment = config.RegisterOption("yagpdb.autorole.non_premium_retroactive_assignment", "Wether to enable retroactive assignemnt on non premium guilds", true) +var configCache = common.CacheSet.RegisterSlot("autorole_config", func(key interface{}) (interface{}, error) { + config, err := GetGeneralConfig(key.(int64)) + return config, err +}, int64(0)) + var logger = common.GetPluginLogger(&Plugin{}) func KeyGeneral(guildID int64) string { return "autorole:" + discordgo.StrID(guildID) + ":general" } diff --git a/autorole/bot.go b/autorole/bot.go index 54dfe80edd..56d2abc9b8 100644 --- a/autorole/bot.go +++ b/autorole/bot.go @@ -7,9 +7,8 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -42,7 +41,6 @@ func (p *Plugin) BotInit() { scheduledevents2.RegisterHandler("autorole_assign_role", assignRoleEventdata{}, handleAssignRole) - pubsub.AddHandler("autorole_stop_processing", HandleUpdateAutoroles, nil) // go runDurationChecker() } @@ -51,7 +49,7 @@ func (p *Plugin) StopBot(wg *sync.WaitGroup) { } var roleCommands = []*commands.YAGCommand{ - &commands.YAGCommand{ + { CmdCategory: commands.CategoryDebug, Name: "Roledbg", Description: "Debug debug debug autorole assignment", @@ -63,14 +61,6 @@ var roleCommands = []*commands.YAGCommand{ }, } -// Stop updating -func HandleUpdateAutoroles(event *pubsub.Event) { - gs := bot.State.Guild(true, event.TargetGuildInt) - if gs != nil { - gs.UserCacheDel(CacheKeyConfig) - } -} - // HandlePresenceUpdate makes sure the member with joined_at is available for the relevant guilds // TODO: Figure out a solution that scales better // func HandlePresenceUpdate(evt *eventsystem.EventData) (retry bool, err error) { @@ -114,12 +104,12 @@ func saveGeneral(guildID int64, config *GeneralConfig) { func onMemberJoin(evt *eventsystem.EventData) (retry bool, err error) { addEvt := evt.GuildMemberAdd() - config, err := GuildCacheGetGeneralConfig(evt.GS) + config, err := GuildCacheGetGeneralConfig(addEvt.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.Role == 0 || evt.GS.Role(true, config.Role) == nil { + if config.Role == 0 || evt.GS.GetRole(config.Role) == nil { return } @@ -289,16 +279,8 @@ func WorkingOnFullScan(guildID int64) bool { return b } -type CacheKey int - -const CacheKeyConfig CacheKey = 1 - -func GuildCacheGetGeneralConfig(gs *dstate.GuildState) (*GeneralConfig, error) { - v, err := gs.UserCacheFetch(CacheKeyConfig, func() (interface{}, error) { - config, err := GetGeneralConfig(gs.ID) - return config, err - }) - +func GuildCacheGetGeneralConfig(guildID int64) (*GeneralConfig, error) { + v, err := configCache.Get(guildID) if err != nil { return nil, err } @@ -319,7 +301,7 @@ func handleAssignRole(evt *scheduledEventsModels.ScheduledEvent, data interface{ dataCast := data.(*assignRoleEventdata) - member, err := bot.GetMemberJoinedAt(evt.GuildID, dataCast.UserID) + member, err := bot.GetMember(evt.GuildID, dataCast.UserID) if err != nil { if common.IsDiscordErr(err, discordgo.ErrCodeUnknownMember) { return false, nil @@ -328,7 +310,8 @@ func handleAssignRole(evt *scheduledEventsModels.ScheduledEvent, data interface{ return bot.CheckDiscordErrRetry(err), err } - memberDuration := time.Now().Sub(member.JoinedAt) + parsedT, _ := member.Member.JoinedAt.Parse() + memberDuration := time.Now().Sub(parsedT) if memberDuration < time.Duration(config.RequiredDuration)*time.Minute { // settings may have been changed, re-schedule @@ -337,7 +320,7 @@ func handleAssignRole(evt *scheduledEventsModels.ScheduledEvent, data interface{ return bot.CheckDiscordErrRetry(err), err } - if !config.CanAssignTo(member.Roles, member.JoinedAt) { + if !config.CanAssignTo(member.Member.Roles, parsedT) { // some other reason they can't get the role, such as whitelist or ignore roles return false, nil } @@ -350,12 +333,12 @@ func handleAssignRole(evt *scheduledEventsModels.ScheduledEvent, data interface{ func handleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) { update := evt.GuildMemberUpdate() - config, err := GuildCacheGetGeneralConfig(evt.GS) + config, err := GuildCacheGetGeneralConfig(update.GuildID) if err != nil { return true, errors.WithStackIf(err) } - if config.Role == 0 || config.OnlyOnJoin || evt.GS.Role(true, config.Role) == nil { + if config.Role == 0 || config.OnlyOnJoin || evt.GS.GetRole(config.Role) == nil { return false, nil } @@ -369,12 +352,13 @@ func handleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) if config.RequiredDuration > 0 { // check the autorole duration - ms, err := bot.GetMemberJoinedAt(update.GuildID, update.User.ID) + ms, err := bot.GetMember(update.GuildID, update.User.ID) if err != nil { return bot.CheckDiscordErrRetry(err), errors.WithStackIf(err) } - if time.Since(ms.JoinedAt) < time.Duration(config.RequiredDuration)*time.Minute { + parsedT, _ := ms.Member.JoinedAt.Parse() + if time.Since(parsedT) < time.Duration(config.RequiredDuration)*time.Minute { // haven't been a member long enough return false, nil } diff --git a/autorole/web.go b/autorole/web.go index 8a98e57332..4a5a22acc6 100644 --- a/autorole/web.go +++ b/autorole/web.go @@ -29,13 +29,13 @@ var ( ) func (f Form) Save(guildID int64) error { - pubsub.Publish("autorole_stop_processing", guildID, nil) err := common.SetRedisJson(KeyGeneral(guildID), f.GeneralConfig) if err != nil { return err } + pubsub.EvictCacheSet(configCache, guildID) return nil } @@ -125,7 +125,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w enabledDisabled := "" autoroleRole := "none" - if role := ag.Role(general.Role); role != nil { + if role := ag.GetRole(general.Role); role != nil { templateData["WidgetEnabled"] = true enabledDisabled = web.EnabledDisabledSpanStatus(true) autoroleRole = html.EscapeString(role.Name) diff --git a/aylien/aylien.go b/aylien/aylien.go index ef9892beb9..6e3fa1f4a9 100644 --- a/aylien/aylien.go +++ b/aylien/aylien.go @@ -7,8 +7,8 @@ import ( "strings" textapi "github.com/AYLIEN/aylien_textapi_go" - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -82,7 +82,7 @@ func (p *Plugin) AddCommands() { } else { // Get the message to analyze - msgs, err := bot.GetMessages(cmd.ChannelID, 100, false) + msgs, err := bot.GetMessages(cmd.GuildData.GS.ID, cmd.ChannelID, 100, false) if err != nil { return "", err } diff --git a/bot/batchmemberjob.go b/bot/batchmemberjob.go index 96fcbe39bb..4bded7d97b 100644 --- a/bot/batchmemberjob.go +++ b/bot/batchmemberjob.go @@ -64,7 +64,7 @@ OUTER: } var ( - ErrGuildNotOnProcess = errors.New("Guild not on process") + ErrGuildNotOnProcess = errors.New("guild not on process") ) func (m *batchMemberJobManager) NewBatchMemberJob(guildID int64, f func(guildID int64, member []*discordgo.Member)) error { @@ -72,7 +72,7 @@ func (m *batchMemberJobManager) NewBatchMemberJob(guildID int64, f func(guildID return ErrGuildNotOnProcess } - gs := State.Guild(true, guildID) + gs := State.GetGuild(guildID) if gs == nil { return ErrGuildNotFound } @@ -92,7 +92,7 @@ func (m *batchMemberJobManager) NewBatchMemberJob(guildID int64, f func(guildID session := ShardManager.SessionForGuild(guildID) if session == nil { - return errors.New("No session?") + return errors.New("no session?") } q := "" session.GatewayManager.RequestGuildMembersComplex(&discordgo.RequestGuildMembersData{ @@ -109,7 +109,7 @@ func (m *batchMemberJobManager) SearchByUsername(guildID int64, query string) ([ return nil, ErrGuildNotOnProcess } - gs := State.Guild(true, guildID) + gs := State.GetGuild(guildID) if gs == nil { return nil, ErrGuildNotFound } @@ -133,7 +133,7 @@ func (m *batchMemberJobManager) SearchByUsername(guildID int64, query string) ([ session := ShardManager.SessionForGuild(guildID) if session == nil { - return nil, errors.New("No session?") + return nil, errors.New("no session?") } session.GatewayManager.RequestGuildMembersComplex(&discordgo.RequestGuildMembersData{ @@ -145,7 +145,7 @@ func (m *batchMemberJobManager) SearchByUsername(guildID int64, query string) ([ return m.waitResponse(time.Second*10, retCh) } -var ErrTimeoutWaitingForMember = errors.New("Timeout waiting for members") +var ErrTimeoutWaitingForMember = errors.New("timeout waiting for members") func (m *batchMemberJobManager) waitResponse(timeout time.Duration, retCh chan []*discordgo.Member) ([]*discordgo.Member, error) { select { diff --git a/bot/bot.go b/bot/bot.go index 05e938c90a..5a6f47ee65 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -11,7 +11,8 @@ import ( "github.com/jonas747/discordgo" "github.com/jonas747/dshardorchestrator/v2/node" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" + "github.com/jonas747/dstate/v3/inmemorytracker" dshardmanager "github.com/jonas747/jdshardmanager" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/bot/shardmemberfetcher" @@ -19,8 +20,6 @@ import ( "github.com/jonas747/yagpdb/common/config" "github.com/jonas747/yagpdb/common/pubsub" "github.com/mediocregopher/radix/v3" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" ) var ( @@ -28,7 +27,9 @@ var ( Started = time.Now() Enabled bool // wether the bot is set to run at some point in this process Running bool // wether the bot is currently running - State *dstate.State + State dstate.StateTracker + stateTracker *inmemorytracker.InMemoryTracker + ShardManager *dshardmanager.Manager NodeConn *node.Conn @@ -104,7 +105,6 @@ func setup() { discordgo.IdentifyRatelimiter = &identifyRatelimiter{} - setupState() addBotHandlers() setupShardManager() } @@ -121,6 +121,7 @@ func setupStandalone() { usingFixedSharding = true ShardManager.SetNumShards(totalShardCount) } + setupState() EventLogger.init(totalShardCount) eventsystem.InitWorkers(totalShardCount) @@ -168,8 +169,6 @@ func botReady() { updateAllShardStatuses() }, nil) - pubsub.AddHandler("bot_core_evict_gs_cache", handleEvictCachePubsub, "") - memberFetcher = shardmemberfetcher.NewManager(int64(totalShardCount), State, func(guildID int64, userIDs []int64, nonce string) error { shardID := guildShardID(guildID) session := ShardManager.Session(shardID) @@ -242,21 +241,16 @@ func Stop(wg *sync.WaitGroup) { func GuildCountsFunc() []int { numShards := ShardManager.GetNumShards() result := make([]int, numShards) - State.RLock() - for _, v := range State.Guilds { - shard := (v.ID >> 22) % int64(numShards) - result[shard]++ + + for i := 0; i < numShards; i++ { + guilds := State.GetShardGuilds(int64(i)) + result[i] = len(guilds) } - State.RUnlock() return result } -// Standard implementation of the GatewayIdentifyRatelimiter type identifyRatelimiter struct { - ch chan bool - once sync.Once - mu sync.Mutex lastShardRatelimited int lastRatelimitAt time.Time @@ -324,77 +318,101 @@ func (rl *identifyRatelimiter) checkSameBucket(shardID int) bool { return true } -var ( - metricsCacheHits = promauto.NewCounter(prometheus.CounterOpts{ - Name: "yagpdb_state_cache_hits_total", - Help: "Cache hits in the satte cache", - }) - - metricsCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ - Name: "yagpdb_state_cache_misses_total", - Help: "Cache misses in the sate cache", - }) - - metricsCacheEvictions = promauto.NewCounter(prometheus.CounterOpts{ - Name: "yagpdb_state_cache_evicted_total", - Help: "Cache evictions", - }) - - metricsCacheMemberEvictions = promauto.NewCounter(prometheus.CounterOpts{ - Name: "yagpdb_state_members_evicted_total", - Help: "Members evicted from state cache", - }) -) - -var confStateRemoveOfflineMembers = config.RegisterOption("yagpdb.state.remove_offline_members", "Gateway connection logging channel", true) +// var ( +// metricsCacheHits = promauto.NewCounter(prometheus.CounterOpts{ +// Name: "yagpdb_state_cache_hits_total", +// Help: "Cache hits in the satte cache", +// }) + +// metricsCacheMisses = promauto.NewCounter(prometheus.CounterOpts{ +// Name: "yagpdb_state_cache_misses_total", +// Help: "Cache misses in the sate cache", +// }) + +// metricsCacheEvictions = promauto.NewCounter(prometheus.CounterOpts{ +// Name: "yagpdb_state_cache_evicted_total", +// Help: "Cache evictions", +// }) + +// metricsCacheMemberEvictions = promauto.NewCounter(prometheus.CounterOpts{ +// Name: "yagpdb_state_members_evicted_total", +// Help: "Members evicted from state cache", +// }) +// ) + +var confStateRemoveOfflineMembers = config.RegisterOption("yagpdb.state.remove_offline_members", "Remove offline members from state", true) + +// func setupState() { +// // Things may rely on state being available at this point for initialization +// State = dstate.NewState() +// State.MaxChannelMessages = 1000 +// State.MaxMessageAge = time.Hour +// // State.Debug = true +// State.ThrowAwayDMMessages = true +// State.TrackPrivateChannels = false +// State.CacheExpirey = time.Hour * 2 + +// if confStateRemoveOfflineMembers.GetBool() { +// State.RemoveOfflineMembers = true +// } + +// go State.RunGCWorker() + +// eventsystem.DiscordState = State + +// // track cache hits/misses +// go func() { +// lastHits := int64(0) +// lastMisses := int64(0) +// lastEvictionsCache := int64(0) +// lastEvictionsMembers := int64(0) + +// ticker := time.NewTicker(time.Minute) +// for { +// <-ticker.C + +// stats := State.StateStats() +// deltaHits := stats.CacheHits - lastHits +// deltaMisses := stats.CacheMisses - lastMisses +// lastHits = stats.CacheHits +// lastMisses = stats.CacheMisses + +// metricsCacheHits.Add(float64(deltaHits)) +// metricsCacheMisses.Add(float64(deltaMisses)) + +// metricsCacheEvictions.Add(float64(stats.UserCachceEvictedTotal - lastEvictionsCache)) +// metricsCacheMemberEvictions.Add(float64(stats.MembersRemovedTotal - lastEvictionsMembers)) + +// lastEvictionsCache = stats.UserCachceEvictedTotal +// lastEvictionsMembers = stats.MembersRemovedTotal + +// // logger.Debugf("guild cache Hits: %d Misses: %d", deltaHits, deltaMisses) +// } +// }() +// } + +var StateLimitsF func(guildID int64) (int, time.Duration) = func(guildID int64) (int, time.Duration) { + return 1000, time.Hour +} func setupState() { - // Things may rely on state being available at this point for initialization - State = dstate.NewState() - State.MaxChannelMessages = 1000 - State.MaxMessageAge = time.Hour - // State.Debug = true - State.ThrowAwayDMMessages = true - State.TrackPrivateChannels = false - State.CacheExpirey = time.Hour * 2 + removeMembersDur := time.Duration(0) if confStateRemoveOfflineMembers.GetBool() { - State.RemoveOfflineMembers = true + removeMembersDur = time.Hour } - go State.RunGCWorker() - - eventsystem.DiscordState = State + tracker := inmemorytracker.NewInMemoryTracker(inmemorytracker.TrackerConfig{ + ChannelMessageLimitsF: StateLimitsF, + RemoveOfflineMembersAfter: removeMembersDur, + }, int64(totalShardCount)) - // track cache hits/misses - go func() { - lastHits := int64(0) - lastMisses := int64(0) - lastEvictionsCache := int64(0) - lastEvictionsMembers := int64(0) + go tracker.RunGCLoop(time.Second) - ticker := time.NewTicker(time.Minute) - for { - <-ticker.C + eventsystem.DiscordState = tracker - stats := State.StateStats() - deltaHits := stats.CacheHits - lastHits - deltaMisses := stats.CacheMisses - lastMisses - lastHits = stats.CacheHits - lastMisses = stats.CacheMisses - - metricsCacheHits.Add(float64(deltaHits)) - metricsCacheMisses.Add(float64(deltaMisses)) - - metricsCacheEvictions.Add(float64(stats.UserCachceEvictedTotal - lastEvictionsCache)) - metricsCacheMemberEvictions.Add(float64(stats.MembersRemovedTotal - lastEvictionsMembers)) - - lastEvictionsCache = stats.UserCachceEvictedTotal - lastEvictionsMembers = stats.MembersRemovedTotal - - // logger.Debugf("guild cache Hits: %d Misses: %d", deltaHits, deltaMisses) - } - }() + stateTracker = tracker + State = tracker } func setupShardManager() { diff --git a/bot/botrest/client.go b/bot/botrest/client.go index 2fb78a073e..451a61ef77 100644 --- a/bot/botrest/client.go +++ b/bot/botrest/client.go @@ -7,6 +7,7 @@ import ( "time" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/internalapi" "github.com/mediocregopher/radix/v3" @@ -14,7 +15,7 @@ import ( var clientLogger = common.GetFixedPrefixLogger("botrest_client") -func GetGuild(guildID int64) (g *discordgo.Guild, err error) { +func GetGuild(guildID int64) (g *dstate.GuildSet, err error) { err = internalapi.GetWithGuild(guildID, discordgo.StrID(guildID)+"/guild", &g) return } diff --git a/bot/botrest/server.go b/bot/botrest/server.go index 581b0e0e3d..f591fbd1f0 100644 --- a/bot/botrest/server.go +++ b/bot/botrest/server.go @@ -3,14 +3,12 @@ package botrest import ( "net/http" "os" - "sort" "strconv" "time" "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" - "github.com/jonas747/dutil" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/internalapi" @@ -73,33 +71,25 @@ func (p *Plugin) InitInternalAPIRoutes(muxer *goji.Mux) { func HandleGuild(w http.ResponseWriter, r *http.Request) { gId, _ := strconv.ParseInt(pat.Param(r, "guild"), 10, 64) - guild := bot.State.Guild(true, gId) + guild := bot.State.GetGuild(gId) if guild == nil { internalapi.ServerError(w, r, errors.New("Guild not found")) return } - gCopy := guild.DeepCopy(true, true, false, true) - - internalapi.ServeJson(w, r, gCopy) + internalapi.ServeJson(w, r, guild) } func HandleBotMember(w http.ResponseWriter, r *http.Request) { gId, _ := strconv.ParseInt(pat.Param(r, "guild"), 10, 64) - guild := bot.State.Guild(true, gId) - if guild == nil { - internalapi.ServerError(w, r, errors.New("Guild not found")) - return - } - - member := guild.MemberDGoCopy(true, common.BotUser.ID) - if member == nil { + member, err := bot.GetMember(gId, common.BotUser.ID) + if err != nil { internalapi.ServerError(w, r, errors.New("Bot Member not found")) return } - internalapi.ServeJson(w, r, member) + internalapi.ServeJson(w, r, member.DgoMember()) } func HandleGetMembers(w http.ResponseWriter, r *http.Request) { @@ -116,12 +106,6 @@ func HandleGetMembers(w http.ResponseWriter, r *http.Request) { return } - guild := bot.State.Guild(true, gId) - if guild == nil { - internalapi.ServerError(w, r, errors.New("Guild not found")) - return - } - uIDsParsed := make([]int64, 0, len(uIDs)) for _, v := range uIDs { parsed, _ := strconv.ParseInt(v, 10, 64) @@ -131,7 +115,7 @@ func HandleGetMembers(w http.ResponseWriter, r *http.Request) { memberStates, _ := bot.GetMembers(gId, uIDsParsed...) members := make([]*discordgo.Member, len(memberStates)) for i, v := range memberStates { - members[i] = v.DGoCopy() + members[i] = v.DgoMember() } internalapi.ServeJson(w, r, members) @@ -151,7 +135,7 @@ func HandleGetMemberColors(w http.ResponseWriter, r *http.Request) { return } - guild := bot.State.Guild(true, gId) + guild := bot.State.GetGuild(gId) if guild == nil { internalapi.ServerError(w, r, errors.New("Guild not found")) return @@ -165,26 +149,20 @@ func HandleGetMemberColors(w http.ResponseWriter, r *http.Request) { memberStates, _ := bot.GetMembers(gId, uIDsParsed...) - guild.Lock() - defer guild.Unlock() - - // Make sure the roles are in the proper order - sort.Sort(dutil.Roles(guild.Guild.Roles)) - colors := make(map[string]int) for _, ms := range memberStates { // Find the highest role this user has with a color - for _, role := range guild.Guild.Roles { + for _, role := range guild.Roles { if role.Color == 0 { continue } - if !common.ContainsInt64Slice(ms.Roles, role.ID) { + if !common.ContainsInt64Slice(ms.Member.Roles, role.ID) { continue } // Bingo - colors[ms.StrID()] = role.Color + colors[strconv.FormatInt(ms.User.ID, 10)] = role.Color break } } @@ -195,20 +173,12 @@ func HandleGetMemberColors(w http.ResponseWriter, r *http.Request) { func HandleGetOnlineCount(w http.ResponseWriter, r *http.Request) { gId, _ := strconv.ParseInt(pat.Param(r, "guild"), 10, 64) - guild := bot.State.Guild(true, gId) - if guild == nil { - internalapi.ServerError(w, r, errors.New("Guild not found")) - return - } - count := 0 - guild.RLock() - for _, ms := range guild.Members { - if ms.PresenceSet && ms.PresenceStatus != dstate.StatusNotSet && ms.PresenceStatus != dstate.StatusOffline { - count++ - } - } - guild.RUnlock() + + bot.State.IterateMembers(gId, func(chunk []*dstate.MemberState) bool { + count += len(chunk) + return true + }) internalapi.ServeJson(w, r, count) } @@ -217,14 +187,19 @@ func HandleChannelPermissions(w http.ResponseWriter, r *http.Request) { gId, _ := strconv.ParseInt(pat.Param(r, "guild"), 10, 64) cId, _ := strconv.ParseInt(pat.Param(r, "channel"), 10, 64) - guild := bot.State.Guild(true, gId) + guild := bot.State.GetGuild(gId) if guild == nil { internalapi.ServerError(w, r, errors.New("Guild not found")) return } - perms, err := guild.MemberPermissions(true, cId, common.BotUser.ID) + member, err := bot.GetMember(gId, common.BotUser.ID) + if err != nil { + internalapi.ServerError(w, r, errors.New("Could not find bot member")) + return + } + perms, err := guild.GetMemberPermissions(cId, member.User.ID, member.Member.Roles) if err != nil { internalapi.ServerError(w, r, errors.WithMessage(err, "Error calculating perms")) return @@ -267,7 +242,7 @@ func HandleNodeStatus(w http.ResponseWriter, r *http.Request) { sumEvents := int64(0) sumPeriodEvents := int64(0) - for j, _ := range totalEventStats[shardID] { + for j := range totalEventStats[shardID] { sumEvents += totalEventStats[shardID][j] sumPeriodEvents += periodEventStats[shardID][j] } @@ -279,6 +254,14 @@ func HandleNodeStatus(w http.ResponseWriter, r *http.Request) { beat, ack := shard.GatewayManager.HeartBeatStats() + guilds := bot.State.GetShardGuilds(int64(shardID)) + numUnavailable := 0 + for _, g := range guilds { + if !g.Available { + numUnavailable++ + } + } + result = append(result, &ShardStatus{ ShardID: shardID, ConnStatus: shard.GatewayManager.Status(), @@ -286,25 +269,11 @@ func HandleNodeStatus(w http.ResponseWriter, r *http.Request) { EventsPerSecond: float64(sumPeriodEvents) / bot.EventLoggerPeriodDuration.Seconds(), LastHeartbeatSend: beat, LastHeartbeatAck: ack, + NumGuilds: len(guilds), + UnavailableGuilds: numUnavailable, }) } - // Guild guild stats - gSlice := bot.State.GuildsSlice(true) - for _, g := range gSlice { - shardID := bot.GuildShardID(int64(numShards), g.ID) - available := g.IsAvailable(true) - for _, v := range result { - if v.ShardID == shardID { - v.NumGuilds++ - if !available { - v.UnavailableGuilds++ - } - break - } - } - } - hostname, _ := os.Hostname() internalapi.ServeJson(w, r, &NodeStatus{ diff --git a/bot/discordevents.go b/bot/discordevents.go index 9912f1dc56..36d533193b 100644 --- a/bot/discordevents.go +++ b/bot/discordevents.go @@ -181,7 +181,8 @@ func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) // StateHandler updates the world state // use AddHandlerBefore to add handler before this one, otherwise they will alwyas be after func StateHandler(evt *eventsystem.EventData) { - State.HandleEvent(ContextSession(evt.Context()), evt.EvtInterface) + stateTracker.HandleEvent(evt.Session, evt.EvtInterface) + // State.HandleEvent(ContextSession(evt.Context()), evt.EvtInterface) } func HandleGuildUpdate(evt *eventsystem.EventData) (retry bool, err error) { diff --git a/bot/eventlogger.go b/bot/eventlogger.go index bec54cba98..cf070f0750 100644 --- a/bot/eventlogger.go +++ b/bot/eventlogger.go @@ -30,8 +30,6 @@ type eventLogger struct { // perPeriod is the number of events proccessed the last period alone (the rate of events) perPeriod [][]int64 - - numShards int } func (e *eventLogger) init(numShards int) { @@ -40,12 +38,12 @@ func (e *eventLogger) init(numShards int) { e.perPeriod = make([][]int64, numShards) // Initialize these - for i, _ := range e.totalStats { + for i := range e.totalStats { e.totalStats[i] = make([]*int64, len(eventsystem.AllEvents)) e.lastPeriod[i] = make([]int64, len(eventsystem.AllEvents)) e.perPeriod[i] = make([]int64, len(eventsystem.AllEvents)) - for j, _ := range e.totalStats[i] { + for j := range e.totalStats[i] { e.totalStats[i][j] = new(int64) } } diff --git a/bot/eventsystem/eventsystem.go b/bot/eventsystem/eventsystem.go index d2e62ff79e..762561e722 100644 --- a/bot/eventsystem/eventsystem.go +++ b/bot/eventsystem/eventsystem.go @@ -10,13 +10,13 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/featureflags" "github.com/sirupsen/logrus" ) -var DiscordState *dstate.State +var DiscordState dstate.StateTracker func init() { for i, _ := range handlers { @@ -40,7 +40,7 @@ type EventData struct { Session *discordgo.Session GuildFeatureFlags []string - GS *dstate.GuildState // Guaranteed to be available for guild events, except creates and deletes + GS *dstate.GuildSet // Guaranteed to be available for guild events, except creates and deletes cs *dstate.ChannelState cancelled *int32 @@ -305,7 +305,9 @@ func (d *EventData) CS() *dstate.ChannelState { } if channelEvt, ok := d.EvtInterface.(discordgo.ChannelEvent); ok { - d.cs = DiscordState.Channel(true, channelEvt.GetChannelID()) + if d.GS != nil { + d.cs = d.GS.GetChannel(channelEvt.GetChannelID()) + } } return d.cs @@ -344,7 +346,7 @@ func handleEvent(evtData *EventData) { if guildEvt, ok := evtData.EvtInterface.(discordgo.GuildEvent); ok { id := guildEvt.GetGuildID() if id != 0 { - evtData.GS = DiscordState.Guild(true, id) + evtData.GS = DiscordState.GetGuild(id) // If guild state is not available for any guild related events, except creates and deletes, do not run the handlers if evtData.GS == nil && evtData.Type != EventGuildCreate && evtData.Type != EventGuildDelete { @@ -357,11 +359,11 @@ func handleEvent(evtData *EventData) { evtData.GuildFeatureFlags = flags } } - } - // attempt to fill in channel state if applicable - if channelEvt, ok := evtData.EvtInterface.(discordgo.ChannelEvent); ok { - evtData.cs = DiscordState.Channel(true, channelEvt.GetChannelID()) + // attempt to fill in channel state if applicable + if channelEvt, ok := evtData.EvtInterface.(discordgo.ChannelEvent); ok && evtData.GS != nil { + evtData.cs = evtData.GS.GetChannel(channelEvt.GetChannelID()) + } } defer func() { diff --git a/bot/history.go b/bot/history.go index 34e9ed0720..71ff3c0973 100644 --- a/bot/history.go +++ b/bot/history.go @@ -1,67 +1,37 @@ package bot import ( - "sort" - - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" ) // GetMessages Gets messages from state if possible, if not then it retrieves from the discord api -// Puts the messages in the state aswell -func GetMessages(channelID int64, limit int, deleted bool) ([]*dstate.MessageState, error) { +func GetMessages(guildID int64, channelID int64, limit int, deleted bool) ([]*dstate.MessageState, error) { if limit < 1 { return []*dstate.MessageState{}, nil } - // check state - msgBuf := make([]*dstate.MessageState, limit) - - cs := State.Channel(true, channelID) - if cs == nil { - return []*dstate.MessageState{}, nil - } - cs.Owner.RLock() - - n := len(msgBuf) - 1 - for i := len(cs.Messages) - 1; i >= 0; i-- { - if cs.Messages[i] == nil { - continue - } - - if !deleted { - if cs.Messages[i].Deleted { - continue - } - } - m := cs.Messages[i].Copy() - msgBuf[n] = m - - n-- - if n < 0 { - break - } - } - - cs.Owner.RUnlock() + msgBuf := State.GetMessages(guildID, channelID, &dstate.MessagesQuery{ + Limit: limit, + IncludeDeleted: deleted, + }) - // Check if the state was full - if n < 0 { + if len(msgBuf) >= limit { + // State had all messages + msgBuf = msgBuf[:limit] return msgBuf, nil } // Not enough messages in state, retrieve them from the api // Initialize the before id to the oldest message we have var before int64 - if n+1 < len(msgBuf) { - if msgBuf[n+1] != nil { - before = msgBuf[n+1].ID - } + if len(msgBuf) > 0 { + before = msgBuf[len(msgBuf)-1].ID } // Start fetching from the api - for n >= 0 { - toFetch := n + 1 + for len(msgBuf) < limit { + toFetch := limit - len(msgBuf) if toFetch > 100 { toFetch = 100 } @@ -77,62 +47,39 @@ func GetMessages(channelID int64, limit int, deleted bool) ([]*dstate.MessageSta } // Copy over to buffer - for k, m := range msgs { - ms := dstate.MessageStateFromMessage(m) - msgBuf[n-k] = ms + for _, m := range msgs { + ms := dstate.MessageStateFromDgo(m) + // ms := dstate.MessageStateFromMessage(m) + msgBuf = append(msgBuf, ms) + // msgBuf[nRemaining-k] = ms } // Oldest message is last before = msgs[len(msgs)-1].ID - n -= len(msgs) if len(msgs) < toFetch { + // ran out of messages in the channel break } } - // remove nil entries if it wasn't big enough - if n+1 > 0 { - msgBuf = msgBuf[n+1:] - } - - // merge the current state with this new one and sort - cs.Owner.Lock() - defer cs.Owner.Unlock() - - for _, m := range msgBuf { - if cs.Message(false, m.ID) != nil { - continue - } - - cs.Messages = append(cs.Messages, m.Copy()) - // cs.MessageAddUpdate(false, m.Message, -1, 0, false, false) - } - - sort.Sort(DiscordMessages(cs.Messages)) - - // Return at most limit results - if limit < len(msgBuf) { - return msgBuf[len(msgBuf)-limit:], nil - } else { - return msgBuf, nil - } + return msgBuf, nil } -type DiscordMessages []*dstate.MessageState +// type DiscordMessages []*dstate.MessageState -// Len is the number of elements in the collection. -func (d DiscordMessages) Len() int { return len(d) } +// // Len is the number of elements in the collection. +// func (d DiscordMessages) Len() int { return len(d) } -// Less reports whether the element with -// index i should sort before the element with index j. -func (d DiscordMessages) Less(i, j int) bool { - return d[i].ParsedCreated.Before(d[j].ParsedCreated) -} +// // Less reports whether the element with +// // index i should sort before the element with index j. +// func (d DiscordMessages) Less(i, j int) bool { +// return d[i].ParsedCreated.Before(d[j].ParsedCreated) +// } // Swap swaps the elements with indexes i and j. -func (d DiscordMessages) Swap(i, j int) { - temp := d[i] - d[i] = d[j] - d[j] = temp -} +// func (d DiscordMessages) Swap(i, j int) { +// temp := d[i] +// d[i] = d[j] +// d[j] = temp +// } diff --git a/bot/memberfetcher.go b/bot/memberfetcher.go index 1b6b40fa52..67cb61295c 100644 --- a/bot/memberfetcher.go +++ b/bot/memberfetcher.go @@ -1,7 +1,7 @@ package bot import ( - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot/shardmemberfetcher" "github.com/jonas747/yagpdb/common" ) @@ -16,15 +16,9 @@ func GetMember(guildID, userID int64) (*dstate.MemberState, error) { return memberFetcher.GetMember(guildID, userID) } - // fallback to this - gs := State.Guild(true, guildID) - if gs == nil { - return nil, ErrGuildNotFound - } - - cop := gs.MemberCopy(true, userID) - if cop != nil && cop.MemberSet { - return cop, nil + ms := State.GetMember(guildID, userID) + if ms != nil && ms.Member != nil { + return ms, nil } member, err := common.BotSession.GuildMember(guildID, userID) @@ -32,8 +26,7 @@ func GetMember(guildID, userID int64) (*dstate.MemberState, error) { return nil, err } - ms := dstate.MSFromDGoMember(gs, member) - return ms, nil + return dstate.MemberStateFromMember(member), nil } // GetMembers is the same as GetMember but with multiple members @@ -55,28 +48,3 @@ func GetMembers(guildID int64, userIDs ...int64) ([]*dstate.MemberState, error) return result, nil } - -// GetMemberJoinedAt is the same as GetMember but it ensures the JoinedAt field is present -func GetMemberJoinedAt(guildID, userID int64) (*dstate.MemberState, error) { - if memberFetcher != nil { - return memberFetcher.GetMember(guildID, userID) - } - - gs := State.Guild(true, guildID) - if gs == nil { - return nil, ErrGuildNotFound - } - - cop := gs.MemberCopy(true, userID) - if cop != nil && cop.MemberSet && !cop.JoinedAt.IsZero() { - return cop, nil - } - - member, err := common.BotSession.GuildMember(guildID, userID) - if err != nil { - return nil, err - } - - ms := dstate.MSFromDGoMember(gs, member) - return ms, nil -} diff --git a/bot/metrics.go b/bot/metrics.go index 6376015a2e..94d7fdd4e5 100644 --- a/bot/metrics.go +++ b/bot/metrics.go @@ -4,7 +4,6 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) @@ -83,28 +82,25 @@ func runUpdateShardMetrics() { } func runUpdateGuildTotalsMetrics() { - guilds := State.GuildsSlice(true) + totalGuilds := 0 + totalMembers := int64(0) + regions := make(map[string]int) - totalMembers := 0 - result := make(map[string]int) + for _, shardID := range ReadyTracker.GetProcessShards() { + guilds := State.GetShardGuilds(int64(shardID)) - for _, g := range guilds { - totalMembers += metricsCountGuild(g, result) - } + totalGuilds += len(guilds) + + for _, g := range guilds { + totalMembers += g.MemberCount + regions[g.Region]++ + } - for region, count := range result { - metricsGuildRegionsTotal.With(prometheus.Labels{"region": region}).Set(float64(count)) + for region, count := range regions { + metricsGuildRegionsTotal.With(prometheus.Labels{"region": region}).Set(float64(count)) + } } - metricsGuildsTotal.Set(float64(len(guilds))) + metricsGuildsTotal.Set(float64(totalGuilds)) metricsMembersTotal.Set(float64(totalMembers)) - -} - -func metricsCountGuild(g *dstate.GuildState, regions map[string]int) int { - g.RLock() - defer g.RUnlock() - - regions[g.Guild.Region]++ - return g.Guild.MemberCount } diff --git a/bot/nodeimpl.go b/bot/nodeimpl.go index f6fc238b90..0f67dcf953 100644 --- a/bot/nodeimpl.go +++ b/bot/nodeimpl.go @@ -12,12 +12,12 @@ import ( "github.com/jonas747/dshardorchestrator/v2" "github.com/jonas747/dshardorchestrator/v2/node" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" ) func init() { - dshardorchestrator.RegisterUserEvent("GuildState", EvtGuildState, dstate.GuildState{}) + dshardorchestrator.RegisterUserEvent("GuildState", EvtGuildState, dstate.GuildSet{}) } // Implementation of DShardOrchestrator/Node/Interface @@ -35,6 +35,8 @@ func (n *NodeImpl) SessionEstablished(info node.SessionInfo) { if totalShardCount == 0 { totalShardCount = info.TotalShards + setupState() + ShardManager.SetNumShards(totalShardCount) eventsystem.InitWorkers(totalShardCount) ReadyTracker.initTotalShardCount(totalShardCount) @@ -120,7 +122,10 @@ func (n *NodeImpl) InitializeShardTransferTo(shard int, sessionID string, sequen } const ( - EvtGuildState dshardorchestrator.EventType = 101 + // This was a legacy format thats now unused + // EvtGuildState dshardorchestrator.EventType = 101 + + EvtGuildState dshardorchestrator.EventType = 102 ) // this should return when all user events has been sent, with the number of user events sent @@ -129,8 +134,9 @@ func (n *NodeImpl) StartShardTransferFrom(shard int) (numEventsSent int) { } func (n *NodeImpl) HandleUserEvent(evt dshardorchestrator.EventType, data interface{}) { + if evt == EvtGuildState { - dataCast := data.(*dstate.GuildState) + dataCast := data.(*dstate.GuildSet) n.LoadGuildState(dataCast) } @@ -145,6 +151,7 @@ func (n *NodeImpl) SendGuilds(shard int) int { started := time.Now() totalSentEvents := 0 + // start with the plugins for _, v := range common.Plugins { if migrator, ok := v.(ShardMigrationSender); ok { @@ -153,40 +160,15 @@ func (n *NodeImpl) SendGuilds(shard int) int { } // Send the guilds on this shard - guildsToSend := make([]*dstate.GuildState, 0) - State.RLock() - for _, v := range State.Guilds { - shardID := guildShardID(v.ID) - if int(shardID) == shard { - guildsToSend = append(guildsToSend, v) - } - } - State.RUnlock() + guildsToSend := State.GetShardGuilds(int64(shard)) - workChan := make(chan *dstate.GuildState) + workChan := make(chan *dstate.GuildSet) var wg sync.WaitGroup // To speed this up we use multiple workers, this has to be done in a relatively short timespan otherwise we won't be able to resume worker := func() { for gs := range workChan { - State.Lock() - delete(State.Guilds, gs.ID) - State.Unlock() - - gs.RLock() - channels := make([]int64, 0, len(gs.Channels)) - for _, c := range gs.Channels { - channels = append(channels, c.ID) - } - NodeConn.SendLogErr(EvtGuildState, gs, true) - gs.RUnlock() - - State.Lock() - for _, c := range channels { - delete(State.Channels, c) - } - State.Unlock() } wg.Done() @@ -210,28 +192,14 @@ func (n *NodeImpl) SendGuilds(shard int) int { close(workChan) wg.Wait() + // clean up after ourselves + stateTracker.DelShard(int64(shard)) + logger.Println("Took ", time.Since(started), " to transfer ", len(guildsToSend), "guildstates") totalSentEvents += len(guildsToSend) return totalSentEvents } -func (n *NodeImpl) LoadGuildState(gs *dstate.GuildState) { - - for _, c := range gs.Channels { - c.Owner = gs - c.Guild = gs - } - - for _, m := range gs.Members { - m.Guild = gs - } - - gs.InitCache(State) - - State.Lock() - State.Guilds[gs.ID] = gs - for _, c := range gs.Channels { - State.Channels[c.ID] = c - } - State.Unlock() +func (n *NodeImpl) LoadGuildState(gs *dstate.GuildSet) { + stateTracker.SetGuild(gs) } diff --git a/bot/paginatedmessages/paginatedcommand.go b/bot/paginatedmessages/paginatedcommand.go index a5257c92cb..7003386c77 100644 --- a/bot/paginatedmessages/paginatedcommand.go +++ b/bot/paginatedmessages/paginatedcommand.go @@ -1,7 +1,7 @@ package paginatedmessages import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" ) diff --git a/bot/plugin.go b/bot/plugin.go index 2d149b4298..f6de7e1bd6 100644 --- a/bot/plugin.go +++ b/bot/plugin.go @@ -15,7 +15,7 @@ import ( "github.com/jonas747/discordgo" "github.com/jonas747/dshardorchestrator/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" ) @@ -51,7 +51,7 @@ type BotStopperHandler interface { } type ShardMigrationHandler interface { - GuildMigrated(guild *dstate.GuildState, toThisSlave bool) + GuildMigrated(guild *dstate.GuildSet, toThisSlave bool) } var metricsLeftGuilds = promauto.NewCounter(prometheus.CounterOpts{ diff --git a/bot/shardmemberfetcher/shardmemberfetcher.go b/bot/shardmemberfetcher/shardmemberfetcher.go index 3ab5e77747..cd1a0a1779 100644 --- a/bot/shardmemberfetcher/shardmemberfetcher.go +++ b/bot/shardmemberfetcher/shardmemberfetcher.go @@ -7,7 +7,8 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" + "github.com/jonas747/dstate/v3/inmemorytracker" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/common" "github.com/karlseguin/ccache" @@ -38,7 +39,7 @@ var metricsGatewayChunkFailed = promauto.NewCounter(prometheus.CounterOpts{ type GatewayRequestFunc func(guildID int64, userIDs []int64, nonce string) error type Manager struct { - state *dstate.State + state dstate.StateTracker totalShards int64 fetchers []*shardMemberFetcher @@ -48,7 +49,7 @@ type Manager struct { failedUsersCache *ccache.Cache } -func NewManager(totalShards int64, state *dstate.State, f GatewayRequestFunc) *Manager { +func NewManager(totalShards int64, state dstate.StateTracker, f GatewayRequestFunc) *Manager { return &Manager{ totalShards: totalShards, state: state, @@ -62,38 +63,28 @@ func (m *Manager) GetMember(guildID, userID int64) (*dstate.MemberState, error) } func (m *Manager) GetMembers(guildID int64, userIDs ...int64) ([]*dstate.MemberState, error) { - gs := m.state.Guild(true, guildID) - if gs == nil { - return nil, errors.New("guild not found") - } - result := make([]*dstate.MemberState, 0, len(userIDs)) resultChan := make(chan *MemberFetchResult) requests := make([]*MemberFetchRequest, 0, len(userIDs)) - gs.RLock() - for _, v := range userIDs { // check state first - ms := gs.MemberCopy(false, v) - if ms != nil && ms.MemberSet { + ms := m.state.GetMember(guildID, v) + if ms != nil && ms.Member != nil { result = append(result, ms) continue } // otherwise create a request requests = append(requests, &MemberFetchRequest{ - resp: resultChan, - Member: v, - Guild: guildID, - NeedJoinedAt: false, + resp: resultChan, + Member: v, + Guild: guildID, }) } - gs.RUnlock() - fetcher := m.findCreateFetcher(guildID) fetcher.reqChan <- requests @@ -107,19 +98,11 @@ func (m *Manager) GetMembers(guildID int64, userIDs ...int64) ([]*dstate.MemberS return result, nil } -func (m *Manager) GetMemberJoinedAt(guildID, userID int64) (*dstate.MemberState, error) { - return m.getMember(guildID, userID, true) -} - func (m *Manager) getMember(guildID, userID int64, joinedAt bool) (*dstate.MemberState, error) { // check from state first - gs := m.state.Guild(true, guildID) - if gs == nil { - return nil, errors.New("guild not found") - } - ms := gs.MemberCopy(true, userID) - if ms != nil && ms.MemberSet && (!joinedAt || !ms.JoinedAt.IsZero()) { + ms := m.state.GetMember(guildID, userID) + if ms != nil && ms.Member != nil && (!joinedAt || ms.Member.JoinedAt != "") { return ms, nil } @@ -128,10 +111,9 @@ func (m *Manager) getMember(guildID, userID int64, joinedAt bool) (*dstate.Membe req := []*MemberFetchRequest{ { - resp: resultChan, - Member: userID, - Guild: guildID, - NeedJoinedAt: joinedAt, + resp: resultChan, + Member: userID, + Guild: guildID, }, } @@ -203,7 +185,7 @@ func (m *Manager) HandleGuildmembersChunk(evt *eventsystem.EventData) { } type shardMemberFetcher struct { - state *dstate.State + state dstate.StateTracker reqChan chan []*MemberFetchRequest @@ -340,34 +322,34 @@ func (s *shardMemberFetcher) handleFinishedGateway(chunk *discordgo.GuildMembers s.sortedQueue[guildID] = newQueue - gs := s.state.Guild(true, chunk.GuildID) - // send the results OUTER_SEND: for _, req := range doneRequests { for _, v := range chunk.Members { if req.Member == v.User.ID { - go s.sendGWResult(gs, req, v) + go s.sendGWResult(req, v) continue OUTER_SEND } } // not found, sendn il - go s.sendGWResult(gs, req, nil) + go s.sendGWResult(req, nil) } - // add to state - gs.Lock() - defer gs.Unlock() + // add to state if we can + cast, ok := s.state.(*inmemorytracker.InMemoryTracker) + if !ok { + return + } for _, v := range chunk.Members { - if ms, ok := gs.Members[v.User.ID]; ok { - if ms.MemberSet { - continue - } + if ms := s.state.GetMember(guildID, v.User.ID); ms != nil && ms.Member != nil { + continue // already in state } - gs.MemberAddUpdate(false, v) + ms := dstate.MemberStateFromMember(v) + ms.GuildID = guildID + cast.SetMember(ms) } } @@ -375,8 +357,8 @@ func (s *shardMemberFetcher) sendResult(req *MemberFetchRequest, result *MemberF req.resp <- result } -func (s *shardMemberFetcher) sendGWResult(gs *dstate.GuildState, req *MemberFetchRequest, member *discordgo.Member) { - if member == nil || gs == nil { +func (s *shardMemberFetcher) sendGWResult(req *MemberFetchRequest, member *discordgo.Member) { + if member == nil { metricsFailed.With(prometheus.Labels{"type": "gateway"}).Inc() req.resp <- &MemberFetchResult{ @@ -388,7 +370,8 @@ func (s *shardMemberFetcher) sendGWResult(gs *dstate.GuildState, req *MemberFetc } else { metricsProcessed.With(prometheus.Labels{"type": "gateway"}).Add(1) - ms := dstate.MSFromDGoMember(gs, member) + ms := dstate.MemberStateFromMember(member) + ms.GuildID = req.Guild req.resp <- &MemberFetchResult{ Err: nil, Member: ms, @@ -459,15 +442,15 @@ func (s *shardMemberFetcher) fetchSingle(req *MemberFetchRequest) { func (s *shardMemberFetcher) fetchSingleInner(req *MemberFetchRequest) (*dstate.MemberState, error) { // check if its already in state first - gs := s.state.Guild(true, req.Guild) + gs := s.state.GetGuild(req.Guild) if gs == nil { metricsFailed.With(prometheus.Labels{"type": "state"}).Inc() return nil, errors.New("guild not found in state") } // it was already existant in the state - result := gs.MemberCopy(true, req.Member) - if result != nil && result.MemberSet && (!req.NeedJoinedAt || !result.JoinedAt.IsZero()) { + result := s.state.GetMember(req.Guild, req.Member) + if result != nil && result.Member != nil { metricsProcessed.With(prometheus.Labels{"type": "state"}).Inc() return result, nil } @@ -482,8 +465,16 @@ func (s *shardMemberFetcher) fetchSingleInner(req *MemberFetchRequest) (*dstate. metricsProcessed.With(prometheus.Labels{"type": "http"}).Inc() - result = dstate.MSFromDGoMember(gs, m) - gs.MemberAddUpdate(true, m) + result = dstate.MemberStateFromMember(m) + result.GuildID = req.Guild // yes this field is not set... + + // add to state if we can + if cast, ok := s.state.(*inmemorytracker.InMemoryTracker); ok { + if state_ms := s.state.GetMember(req.Guild, req.Member); state_ms == nil || state_ms.Member == nil { + // make a copy because we handed out references above and the below function might mutate it + cast.SetMember(result) + } + } return result, nil } @@ -582,10 +573,9 @@ type FetchingGWState struct { } type MemberFetchRequest struct { - Member int64 - Guild int64 - NeedJoinedAt bool - resp chan *MemberFetchResult + Member int64 + Guild int64 + resp chan *MemberFetchResult } type MemberFetchResult struct { diff --git a/bot/util.go b/bot/util.go index 890313fba5..dd99d5743b 100644 --- a/bot/util.go +++ b/bot/util.go @@ -10,7 +10,7 @@ import ( "github.com/bwmarrin/snowflake" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/pubsub" @@ -64,8 +64,9 @@ func SendDMEmbed(user int64, embed *discordgo.MessageEmbed) error { } var ( - ErrStartingUp = errors.New("Starting up, caches are being filled...") - ErrGuildNotFound = errors.New("Guild not found") + ErrStartingUp = errors.New("Starting up, caches are being filled...") + ErrGuildNotFound = errors.New("Guild not found") + ErrChannelNotFound = errors.New("Channel not found") ) // AdminOrPerm is the same as AdminOrPermMS but only required a member ID @@ -76,20 +77,23 @@ func AdminOrPerm(guildID int64, channelID int64, userID int64, needed int) (bool return false, err } - return AdminOrPermMS(channelID, ms, needed) + return AdminOrPermMS(guildID, channelID, ms, needed) } // AdminOrPermMS checks if the provided member has all of the needed permissions or is a admin -func AdminOrPermMS(channelID int64, ms *dstate.MemberState, needed int) (bool, error) { - perms, err := ms.Guild.MemberPermissionsMS(true, channelID, ms) +func AdminOrPermMS(guildID int64, channelID int64, ms *dstate.MemberState, needed int) (bool, error) { + guild := State.GetGuild(guildID) + if guild == nil { + return false, ErrGuildNotFound + } + + perms, err := guild.GetMemberPermissions(channelID, ms.User.ID, ms.Member.Roles) if err != nil { return false, err } - if needed != 0 { - if perms&needed == needed { - return true, nil - } + if needed != 0 && perms&int64(needed) == int64(needed) { + return true, nil } if perms&discordgo.PermissionManageServer != 0 || perms&discordgo.PermissionAdministrator != 0 { @@ -99,16 +103,6 @@ func AdminOrPermMS(channelID int64, ms *dstate.MemberState, needed int) (bool, e return false, nil } -// GuildName is a convenience function for getting the name of a guild -func GuildName(gID int64) (name string) { - g := State.Guild(true, gID) - g.RLock() - name = g.Guild.Name - g.RUnlock() - - return -} - func SnowflakeToTime(i int64) time.Time { flake := snowflake.ID(i) t := time.Unix(flake.Time()/1000, 0) @@ -143,46 +137,46 @@ func updateAllShardStatuses() { // BotProbablyHasPermission returns true if its possible that the bot has the following permission, // it also returns true if the bot member could not be found or if the guild is not in state (hence, probably) func BotProbablyHasPermission(guildID int64, channelID int64, permission int) bool { - gs := State.Guild(true, guildID) + gs := State.GetGuild(guildID) if gs == nil { - return true + return false } return BotProbablyHasPermissionGS(gs, channelID, permission) } // BotProbablyHasPermissionGS is the same as BotProbablyHasPermission but with a guildstate instead of guildid -func BotProbablyHasPermissionGS(gs *dstate.GuildState, channelID int64, permission int) bool { +func BotProbablyHasPermissionGS(gs *dstate.GuildSet, channelID int64, permission int) bool { ms, err := GetMember(gs.ID, common.BotUser.ID) if err != nil { logger.WithError(err).WithField("guild", gs.ID).Error("bot isnt a member of a guild?") return false } - perms, err := gs.MemberPermissionsMS(true, channelID, ms) - if err != nil && err != dstate.ErrChannelNotFound { + perms, err := gs.GetMemberPermissions(channelID, ms.User.ID, ms.Member.Roles) + if err != nil { logger.WithError(err).WithField("guild", gs.ID).Error("Failed checking perms") return true } - if perms&permission == permission { + if perms&int64(permission) == int64(permission) { return true } - if perms&discordgo.PermissionAdministrator != 0 { + if perms&discordgo.PermissionAdministrator == discordgo.PermissionAdministrator { return true } return false } -func BotPermissions(gs *dstate.GuildState, channelID int64) (int64, error) { +func BotPermissions(gs *dstate.GuildSet, channelID int64) (int64, error) { ms, err := GetMember(gs.ID, common.BotUser.ID) if err != nil { return 0, err } - perms, err := gs.MemberPermissionsMS(true, channelID, ms) + perms, err := gs.GetMemberPermissions(channelID, ms.User.ID, ms.Member.Roles) if err != nil { return 0, err } @@ -200,7 +194,7 @@ func SendMessage(guildID int64, channelID int64, msg string) (permsOK bool, resp return } -func SendMessageGS(gs *dstate.GuildState, channelID int64, msg string) (permsOK bool, resp *discordgo.Message, err error) { +func SendMessageGS(gs *dstate.GuildSet, channelID int64, msg string) (permsOK bool, resp *discordgo.Message, err error) { if !BotProbablyHasPermissionGS(gs, channelID, discordgo.PermissionSendMessages|discordgo.PermissionReadMessages) { return false, nil, nil } @@ -220,7 +214,7 @@ func SendMessageEmbed(guildID int64, channelID int64, msg *discordgo.MessageEmbe return } -func SendMessageEmbedGS(gs *dstate.GuildState, channelID int64, msg *discordgo.MessageEmbed) (permsOK bool, resp *discordgo.Message, err error) { +func SendMessageEmbedGS(gs *dstate.GuildSet, channelID int64, msg *discordgo.MessageEmbed) (permsOK bool, resp *discordgo.Message, err error) { if !BotProbablyHasPermissionGS(gs, channelID, discordgo.PermissionSendMessages|discordgo.PermissionReadMessages|discordgo.PermissionEmbedLinks) { return false, nil, nil } @@ -277,10 +271,10 @@ func RefreshStatus(session *discordgo.Session) { // IsMemberAbove returns wether ms1 is above ms2 in terms of roles (e.g the highest role of ms1 is higher than the highest role of ms2) // assumes gs is locked, otherwise race conditions will occur -func IsMemberAbove(gs *dstate.GuildState, ms1 *dstate.MemberState, ms2 *dstate.MemberState) bool { - if ms1.ID == gs.Guild.OwnerID { +func IsMemberAbove(gs *dstate.GuildSet, ms1 *dstate.MemberState, ms2 *dstate.MemberState) bool { + if ms1.User.ID == gs.OwnerID { return true - } else if ms2.ID == gs.Guild.OwnerID { + } else if ms2.User.ID == gs.OwnerID { return false } @@ -303,8 +297,8 @@ func IsMemberAbove(gs *dstate.GuildState, ms1 *dstate.MemberState, ms2 *dstate.M // IsMemberAboveRole returns wether ms is above role // assumes gs is locked, otherwise race conditions will occur -func IsMemberAboveRole(gs *dstate.GuildState, ms1 *dstate.MemberState, role *discordgo.Role) bool { - if ms1.ID == gs.Guild.OwnerID { +func IsMemberAboveRole(gs *dstate.GuildSet, ms1 *dstate.MemberState, role *discordgo.Role) bool { + if ms1.User.ID == gs.OwnerID { return true } @@ -318,16 +312,16 @@ func IsMemberAboveRole(gs *dstate.GuildState, ms1 *dstate.MemberState, role *dis } // MemberHighestRole returns the highest role for ms, assumes gs is rlocked, otherwise race conditions will occur -func MemberHighestRole(gs *dstate.GuildState, ms *dstate.MemberState) *discordgo.Role { +func MemberHighestRole(gs *dstate.GuildSet, ms *dstate.MemberState) *discordgo.Role { var highest *discordgo.Role - for _, rID := range ms.Roles { - for _, r := range gs.Guild.Roles { + for _, rID := range ms.Member.Roles { + for _, r := range gs.Roles { if r.ID != rID { continue } - if highest == nil || dutil.IsRoleAbove(r, highest) { - highest = r + if highest == nil || dutil.IsRoleAbove(&r, highest) { + highest = &r } break @@ -338,34 +332,18 @@ func MemberHighestRole(gs *dstate.GuildState, ms *dstate.MemberState) *discordgo } func GetUsers(guildID int64, ids ...int64) []*discordgo.User { - gs := State.Guild(true, guildID) - if gs == nil { - return nil - } - - return GetUsersGS(gs, ids...) -} - -func GetUsersGS(gs *dstate.GuildState, ids ...int64) []*discordgo.User { - gs.RLock() - defer gs.RUnlock() - resp := make([]*discordgo.User, 0, len(ids)) for _, id := range ids { - m := gs.Member(false, id) + m := State.GetMember(guildID, id) if m != nil { - resp = append(resp, m.DGoUser()) + resp = append(resp, &m.User) continue } - gs.RUnlock() - user, err := common.BotSession.User(id) - gs.RLock() - if err != nil { - logger.WithError(err).WithField("guild", gs.ID).Error("failed retrieving user from api") + logger.WithError(err).WithField("guild", guildID).Error("failed retrieving user from api") resp = append(resp, &discordgo.User{ ID: id, Username: "Unknown (" + strconv.FormatInt(id, 10) + ")", @@ -379,37 +357,8 @@ func GetUsersGS(gs *dstate.GuildState, ids ...int64) []*discordgo.User { return resp } -func EvictGSCache(guildID int64, key GSCacheKey) { - if Enabled { - evictGSCacheLocal(guildID, key) - } else { - evictGSCacheRemote(guildID, key) - } -} - -func evictGSCacheLocal(guildID int64, key GSCacheKey) { - gs := State.Guild(true, guildID) - if gs == nil { - return - } - - gs.UserCacheDel(key) -} - type GSCacheKey string -func evictGSCacheRemote(guildID int64, key GSCacheKey) { - err := pubsub.Publish("bot_core_evict_gs_cache", guildID, key) - if err != nil { - logger.WithError(err).WithField("guild", guildID).WithField("key", key).Error("failed evicting remote cache") - } -} - -func handleEvictCachePubsub(evt *pubsub.Event) { - key := evt.Data.(*string) - evictGSCacheLocal(evt.TargetGuildInt, GSCacheKey(*key)) -} - func CheckDiscordErrRetry(err error) bool { if err == nil { return false diff --git a/bot/util_test.go b/bot/util_test.go index fc78ba3713..8cf2d2dc19 100644 --- a/bot/util_test.go +++ b/bot/util_test.go @@ -5,18 +5,17 @@ import ( "testing" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" ) func TestMemberHighestRole(t *testing.T) { - gs := &dstate.GuildState{ - Guild: &discordgo.Guild{ - Roles: []*discordgo.Role{ - &discordgo.Role{ID: 10, Position: 10}, - &discordgo.Role{ID: 5, Position: 5}, - &discordgo.Role{ID: 100, Position: 1}, - &discordgo.Role{ID: 102, Position: 1}, - }, + gs := &dstate.GuildSet{ + GuildState: dstate.GuildState{}, + Roles: []discordgo.Role{ + {ID: 10, Position: 10}, + {ID: 5, Position: 5}, + {ID: 100, Position: 1}, + {ID: 102, Position: 1}, }, } @@ -32,7 +31,9 @@ func TestMemberHighestRole(t *testing.T) { for i, v := range cases { t.Run(fmt.Sprintf("case #%d", i), func(t *testing.T) { ms := &dstate.MemberState{ - Roles: v.Roles, + Member: &dstate.MemberFields{ + Roles: v.Roles, + }, } result := MemberHighestRole(gs, ms) @@ -44,15 +45,15 @@ func TestMemberHighestRole(t *testing.T) { } func TestIsMemberAbove(t *testing.T) { - gs := &dstate.GuildState{ - Guild: &discordgo.Guild{ + gs := &dstate.GuildSet{ + GuildState: dstate.GuildState{ OwnerID: 99, - Roles: []*discordgo.Role{ - &discordgo.Role{ID: 10, Position: 10}, - &discordgo.Role{ID: 5, Position: 5}, - &discordgo.Role{ID: 100, Position: 1}, - &discordgo.Role{ID: 102, Position: 1}, - }, + }, + Roles: []discordgo.Role{ + {ID: 10, Position: 10}, + {ID: 5, Position: 5}, + {ID: 100, Position: 1}, + {ID: 102, Position: 1}, }, } @@ -72,11 +73,15 @@ func TestIsMemberAbove(t *testing.T) { for i, v := range cases { t.Run(fmt.Sprintf("case #%d", i), func(t *testing.T) { ms1 := &dstate.MemberState{ - Roles: v.M1, + Member: &dstate.MemberFields{ + Roles: v.M1, + }, } ms2 := &dstate.MemberState{ - Roles: v.M2, + Member: &dstate.MemberFields{ + Roles: v.M2, + }, } result := IsMemberAbove(gs, ms1, ms2) diff --git a/cah/commands.go b/cah/commands.go index 5d7c9c921b..79c0833cde 100644 --- a/cah/commands.go +++ b/cah/commands.go @@ -4,8 +4,8 @@ import ( "strings" "github.com/jonas747/cardsagainstdiscord" - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/sirupsen/logrus" @@ -48,7 +48,7 @@ func (p *Plugin) AddCommands() { CmdCategory: commands.CategoryFun, Description: "Ends a Cards Against Humanity game that is ongoing in this channel.", RunFunc: func(data *dcmd.Data) (interface{}, error) { - isAdmin, err := bot.AdminOrPermMS(data.ChannelID, data.GuildData.MS, 0) + isAdmin, err := bot.AdminOrPermMS(data.GuildData.GS.ID, data.ChannelID, data.GuildData.MS, 0) if err == nil && isAdmin { err = p.Manager.RemoveGame(data.ChannelID) } else { @@ -113,7 +113,7 @@ func (p *Plugin) AddCommands() { container.AddCommand(cmdEnd, cmdEnd.GetTrigger()) container.AddCommand(cmdKick, cmdKick.GetTrigger()) container.AddCommand(cmdPacks, cmdPacks.GetTrigger()) - commands.RegisterSlashCommandsContainer(container, true, func(gs *dstate.GuildState) ([]int64, error) { + commands.RegisterSlashCommandsContainer(container, true, func(gs *dstate.GuildSet) ([]int64, error) { return nil, nil }) } diff --git a/cmd/shardorchestrator/main.go b/cmd/shardorchestrator/main.go index b4f99b22f4..a8ec835ba1 100644 --- a/cmd/shardorchestrator/main.go +++ b/cmd/shardorchestrator/main.go @@ -58,7 +58,7 @@ func main() { VersionArgs: []string{"-version"}, } orch.Logger = &dshardorchestrator.StdLogger{ - Level: dshardorchestrator.LogWarning, + Level: dshardorchestrator.LogInfo, } if confLargeBotShardingEnabled.GetBool() { diff --git a/commands/commands.go b/commands/commands.go index be39ab2018..cdc4beece9 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -6,7 +6,7 @@ package commands import ( "context" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands/models" diff --git a/commands/help.go b/commands/help.go index 7842b334f6..c2528ae504 100644 --- a/commands/help.go +++ b/commands/help.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot/paginatedmessages" "github.com/jonas747/yagpdb/common" @@ -31,10 +31,8 @@ func CmdNotFound(search string) string { func cmdFuncHelp(data *dcmd.Data) (interface{}, error) { target := data.Args[0].Str() - var resp []*discordgo.MessageEmbed - // Send the targetted help in the channel it was requested in - resp = dcmd.GenerateTargettedHelp(target, data, data.ContainerChain[0], &dcmd.StdHelpFormatter{}) + resp := dcmd.GenerateTargettedHelp(target, data, data.ContainerChain[0], &dcmd.StdHelpFormatter{}) for _, v := range resp { ensureEmbedLimits(v) } diff --git a/commands/plugin_bot.go b/commands/plugin_bot.go index 170b129f8f..8f268bfa62 100644 --- a/commands/plugin_bot.go +++ b/commands/plugin_bot.go @@ -10,9 +10,9 @@ import ( "unicode/utf8" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/common" @@ -46,7 +46,7 @@ func (p *Plugin) BotInit() { } -func (p *Plugin) customUsernameSearchFunc(gs *dstate.GuildState, query string) (ms *dstate.MemberState, err error) { +func (p *Plugin) customUsernameSearchFunc(tracker dstate.StateTracker, gs *dstate.GuildSet, query string) (ms *dstate.MemberState, err error) { logger.Info("Searching by username: ", query) members, err := bot.BatchMemberJobManager.SearchByUsername(gs.ID, query) if err != nil { @@ -85,7 +85,7 @@ func (p *Plugin) customUsernameSearchFunc(gs *dstate.GuildState, query string) ( } if len(fullMatches) == 1 { - return dstate.MSFromDGoMember(gs, fullMatches[0]), nil + return dstate.MemberStateFromMember(fullMatches[0]), nil } if len(fullMatches) == 0 && len(partialMatches) == 0 { @@ -167,10 +167,8 @@ func YAGCommandMiddleware(inner dcmd.RunFunc) dcmd.RunFunc { return resp, err } guildID := int64(0) - var cs *dstate.ChannelState if data.GuildData != nil { guildID = data.GuildData.GS.ID - cs = data.GuildData.CS } // Lock the command for execution @@ -185,7 +183,7 @@ func YAGCommandMiddleware(inner dcmd.RunFunc) dcmd.RunFunc { defer removeRunningCommand(guildID, data.ChannelID, data.Author.ID, yc) // Check if the user can execute the command - canExecute, resp, settings, err := yc.checkCanExecuteCommand(data, cs) + canExecute, resp, settings, err := yc.checkCanExecuteCommand(data) if err != nil { yc.Logger(data).WithError(err).Error("An error occured while checking if we could run command") } diff --git a/commands/plugin_web.go b/commands/plugin_web.go index c46791ceab..10c5dec24a 100644 --- a/commands/plugin_web.go +++ b/commands/plugin_web.go @@ -11,8 +11,9 @@ import ( "unicode" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/commands/models" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/cplogs" @@ -27,8 +28,8 @@ import ( ) type ChannelOverrideForm struct { - Channels []int64 `valid:"channel,true` - ChannelCategories []int64 `valid:"channel,true` + Channels []int64 `valid:"channel,true"` + ChannelCategories []int64 `valid:"channel,true"` Global bool CommandsEnabled bool AutodeleteResponse bool @@ -202,7 +203,7 @@ func HandlePostCommands(w http.ResponseWriter, r *http.Request) (web.TemplateDat // Channel override handlers func ChannelOverrideMiddleware(inner func(w http.ResponseWriter, r *http.Request, override *models.CommandsChannelsOverride) (web.TemplateData, error)) web.ControllerHandlerFunc { return func(w http.ResponseWriter, r *http.Request) (web.TemplateData, error) { - activeGuild := r.Context().Value(common.ContextKeyCurrentGuild).(*discordgo.Guild) + activeGuild := r.Context().Value(common.ContextKeyCurrentGuild).(*dstate.GuildSet) var override *models.CommandsChannelsOverride var err error diff --git a/commands/slashcommands.go b/commands/slashcommands.go index d967e86a53..0ed8719e1d 100644 --- a/commands/slashcommands.go +++ b/commands/slashcommands.go @@ -10,9 +10,9 @@ import ( "sync/atomic" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands/models" @@ -261,7 +261,7 @@ func (p *Plugin) handleGuildCreate(evt *eventsystem.EventData) { // TODO: add queue? waitForSlashCommandIDs() - gs := bot.State.Guild(true, evt.GuildCreate().ID) + gs := bot.State.GetGuild(evt.GuildCreate().ID) if gs == nil { panic("gs is nil") } @@ -285,7 +285,7 @@ func (p *Plugin) handleDiscordEventUpdateSlashCommandPermissions(evt *eventsyste } } -func updateSlashCommandGuildPermissions(gs *dstate.GuildState) (updated bool, err error) { +func updateSlashCommandGuildPermissions(gs *dstate.GuildSet) (updated bool, err error) { commandSettings, err := GetAllOverrides(context.Background(), gs.ID) if err != nil { return false, err @@ -374,7 +374,7 @@ func handleInteractionCreate(evt *eventsystem.EventData) { } // Since we can't put permissions on subcommands we do a similar thing we do to command settings that is -func ContainerSlashCommandPermissions(container *slashCommandsContainer, overrides []*models.CommandsChannelsOverride, gs *dstate.GuildState) ([]*discordgo.ApplicationCommandPermissions, error) { +func ContainerSlashCommandPermissions(container *slashCommandsContainer, overrides []*models.CommandsChannelsOverride, gs *dstate.GuildSet) ([]*discordgo.ApplicationCommandPermissions, error) { allowRoles, denyRoles, allowAll, denyAll, err := slashCommandPermissionsFromRolesFunc(container.rolesRunFunc, gs, container.defaultPermissions) if err != nil { return nil, err @@ -401,7 +401,7 @@ func ContainerSlashCommandPermissions(container *slashCommandsContainer, overrid // merges all subcommand allow roles // and returns a common set of all subcommand deny roles -func sumContainerSlashCommandsChildren(container *slashCommandsContainer, overrides []*models.CommandsChannelsOverride, gs *dstate.GuildState) (allowRoles []int64, denyRoles []int64, allowAll bool, denyAll bool, err error) { +func sumContainerSlashCommandsChildren(container *slashCommandsContainer, overrides []*models.CommandsChannelsOverride, gs *dstate.GuildSet) (allowRoles []int64, denyRoles []int64, allowAll bool, denyAll bool, err error) { allowRoles = make([]int64, 0) denyRoles = make([]int64, 0) allowAll = false @@ -435,7 +435,7 @@ func sumContainerSlashCommandsChildren(container *slashCommandsContainer, overri return } -func toApplicationCommandPermissions(gs *dstate.GuildState, defaultEnabeld bool, allowRoles, denyRoles []int64, allowAll, denyAll bool) []*discordgo.ApplicationCommandPermissions { +func toApplicationCommandPermissions(gs *dstate.GuildSet, defaultEnabeld bool, allowRoles, denyRoles []int64, allowAll, denyAll bool) []*discordgo.ApplicationCommandPermissions { result := make([]*discordgo.ApplicationCommandPermissions, 0, 10) // allGuildroles := guildRoles(gs) @@ -503,7 +503,7 @@ func toApplicationCommandPermissions(gs *dstate.GuildState, defaultEnabeld bool, return result } -func (yc *YAGCommand) TopLevelSlashCommandPermissions(overrides []*models.CommandsChannelsOverride, gs *dstate.GuildState) ([]*discordgo.ApplicationCommandPermissions, error) { +func (yc *YAGCommand) TopLevelSlashCommandPermissions(overrides []*models.CommandsChannelsOverride, gs *dstate.GuildSet) ([]*discordgo.ApplicationCommandPermissions, error) { allowRoles, denyRoles, allowAll, denyAll, err := yc.SlashCommandPermissions(overrides, yc.DefaultEnabled, []*dcmd.Container{CommandSystem.Root}, gs) if err != nil { @@ -514,7 +514,7 @@ func (yc *YAGCommand) TopLevelSlashCommandPermissions(overrides []*models.Comman return result, nil } -func (yc *YAGCommand) SlashCommandPermissions(overrides []*models.CommandsChannelsOverride, defaultEnabeld bool, containerChain []*dcmd.Container, gs *dstate.GuildState) (allowRoles []int64, denyRoles []int64, allowAll bool, denyAll bool, err error) { +func (yc *YAGCommand) SlashCommandPermissions(overrides []*models.CommandsChannelsOverride, defaultEnabeld bool, containerChain []*dcmd.Container, gs *dstate.GuildSet) (allowRoles []int64, denyRoles []int64, allowAll bool, denyAll bool, err error) { allowRoles = make([]int64, 0) denyRoles = make([]int64, 0) allowAll = true @@ -560,20 +560,20 @@ func (yc *YAGCommand) SlashCommandPermissions(overrides []*models.CommandsChanne // Apply the inverse of the required roles to the deny roles, this effectively means were only blacklisting roles. // which means the permissions need to be updated after any roles are added. OUTER: - for _, v := range guildRoles(gs) { + for _, v := range gs.Roles { for _, required := range commonRequiredRoles { - if v == required { + if v.ID == required { continue OUTER } } for _, alreadyDenied := range denyRoles { - if alreadyDenied == v { + if alreadyDenied == v.ID { continue OUTER } } - denyRoles = append(denyRoles, v) + denyRoles = append(denyRoles, v.ID) } } else { if allowAll { @@ -591,31 +591,14 @@ func (yc *YAGCommand) SlashCommandPermissions(overrides []*models.CommandsChanne return } -func guildRoles(gs *dstate.GuildState) []int64 { - gs.RLock() - defer gs.RUnlock() - - result := make([]int64, 0, len(gs.Guild.Roles)) - for _, v := range gs.Guild.Roles { - if v.ID == gs.ID || v.Managed { - continue - } - result = append(result, v.ID) - } - - return result -} - -func findRolesWithDiscordPerms(gs *dstate.GuildState, requiredPerms []int64, defaultEnabled bool) []int64 { - gs.RLock() - defer gs.RUnlock() +func findRolesWithDiscordPerms(gs *dstate.GuildSet, requiredPerms []int64, defaultEnabled bool) []int64 { result := make([]int64, 0) // check fixed required perms on the command OUTER: - for _, r := range gs.Guild.Roles { + for _, r := range gs.Roles { perms := int64(r.Permissions) for _, rp := range requiredPerms { if perms&rp == rp || perms&discordgo.PermissionAdministrator == discordgo.PermissionAdministrator || perms&discordgo.PermissionManageServer == discordgo.PermissionManageServer { @@ -740,7 +723,7 @@ func isSlashCommandEnabledChannelOverride(fullName string, override *models.Comm return override.CommandsEnabled } -func slashCommandPermissionsFromRolesFunc(rf RolesRunFunc, gs *dstate.GuildState, defaultEnabled bool) (allow []int64, deny []int64, allowAll bool, denyAll bool, err error) { +func slashCommandPermissionsFromRolesFunc(rf RolesRunFunc, gs *dstate.GuildSet, defaultEnabled bool) (allow []int64, deny []int64, allowAll bool, denyAll bool, err error) { roles, err := rf(gs) if err != nil { @@ -771,7 +754,7 @@ func slashCommandPermissionsFromRolesFunc(rf RolesRunFunc, gs *dstate.GuildState } func (p *Plugin) handleUpdateSlashCommandsPermissions(event *pubsub.Event) { - gs := bot.State.Guild(true, event.TargetGuildInt) + gs := bot.State.GetGuild(event.TargetGuildInt) if gs == nil { return } diff --git a/commands/tmplexec.go b/commands/tmplexec.go index 89c213964d..f37a5e53e1 100644 --- a/commands/tmplexec.go +++ b/commands/tmplexec.go @@ -6,7 +6,7 @@ import ( "strings" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/paginatedmessages" @@ -34,7 +34,7 @@ func tmplUserArg(tmplCtx *templates.Context) interface{} { // Assume it's an id member, _ := bot.GetMember(tmplCtx.GS.ID, num) if member != nil { - return member.DGoUser(), nil + return &member.User, nil } return nil, nil @@ -58,7 +58,7 @@ func tmplUserArg(tmplCtx *templates.Context) interface{} { member, _ := bot.GetMember(tmplCtx.GS.ID, id) if member != nil { // Found member - return member.DGoUser(), nil + return &member.User, nil } } @@ -81,7 +81,7 @@ func TmplExecCmdFuncs(ctx *templates.Context, maxExec int, dryRun bool) (userCtx if ctx.CurrentFrame.CS != nil { //Check if CS is not a nil pointer messageCopy.ChannelID = ctx.CurrentFrame.CS.ID } - mc := &discordgo.MessageCreate{&messageCopy} + mc := &discordgo.MessageCreate{Message: &messageCopy} if maxExec < 1 { return "", errors.New("Max number of commands executed in custom command") } @@ -105,9 +105,9 @@ func TmplExecCmdFuncs(ctx *templates.Context, maxExec int, dryRun bool) (userCtx return "", errors.New("Failed fetching member") } - messageCopy.Member = botMember.DGoCopy() + messageCopy.Member = botMember.DgoMember() - mc := &discordgo.MessageCreate{&messageCopy} + mc := &discordgo.MessageCreate{Message: &messageCopy} if maxExec < 1 { return "", errors.New("Max number of commands executed in custom command") } diff --git a/commands/util.go b/commands/util.go index 05e73f62aa..7cc01d7f0a 100644 --- a/commands/util.go +++ b/commands/util.go @@ -8,9 +8,9 @@ import ( "unicode" "unicode/utf8" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" ) @@ -151,10 +151,7 @@ func CommonContainerNotFoundHandler(container *dcmd.Container, fixedMessage stri return func(data *dcmd.Data) (interface{}, error) { // Only show stuff if atleast 1 of the commands in the container is enabled if data.GuildData != nil { - data.GuildData.GS.RLock() cParentID := data.GuildData.CS.ParentID - data.GuildData.GS.RUnlock() - ms := data.GuildData.MS channelOverrides, err := GetOverridesForChannel(data.ChannelID, cParentID, data.GuildData.GS.ID) @@ -176,12 +173,12 @@ func CommonContainerNotFoundHandler(container *dcmd.Container, fixedMessage stri continue } - if len(settings.RequiredRoles) > 0 && !common.ContainsInt64SliceOneOf(settings.RequiredRoles, ms.Roles) { + if len(settings.RequiredRoles) > 0 && !common.ContainsInt64SliceOneOf(settings.RequiredRoles, ms.Member.Roles) { // missing required role continue } - if len(settings.IgnoreRoles) > 0 && common.ContainsInt64SliceOneOf(settings.IgnoreRoles, ms.Roles) { + if len(settings.IgnoreRoles) > 0 && common.ContainsInt64SliceOneOf(settings.IgnoreRoles, ms.Member.Roles) { // has ignored role continue } @@ -238,7 +235,7 @@ func (ma *MemberArg) ParseFromMessage(def *dcmd.ArgDef, part string, data *dcmd. return nil, dcmd.NewSimpleUserError("Invalid mention or id") } - member, err := bot.GetMemberJoinedAt(data.GuildData.GS.ID, id) + member, err := bot.GetMember(data.GuildData.GS.ID, id) if err != nil { if common.IsDiscordErr(err, discordgo.ErrCodeUnknownMember, discordgo.ErrCodeUnknownUser) { return nil, dcmd.NewSimpleUserError("User not a member of the server") @@ -256,7 +253,7 @@ func (ma *MemberArg) ParseFromInteraction(def *dcmd.ArgDef, data *dcmd.Data, opt return nil, err } - return dstate.MSFromDGoMember(data.GuildData.GS, member), nil + return dstate.MemberStateFromMember(member), nil } func (ma *MemberArg) ExtractID(part string, data *dcmd.Data) int64 { @@ -408,15 +405,11 @@ func (ra *RoleArg) ParseFromMessage(def *dcmd.ArgDef, part string, data *dcmd.Da idName = "" } - roles := data.GuildData.GS.Guild.Roles - var role discordgo.Role - for _, v := range roles { + for _, v := range data.GuildData.GS.Roles { if v.ID == id { - role = *v - return &role, nil + return &v, nil } else if v.Name == idName { - role = *v - return &role, nil + return &v, nil } } diff --git a/commands/yagcommmand.go b/commands/yagcommmand.go index 67a36798b2..3d850bec60 100644 --- a/commands/yagcommmand.go +++ b/commands/yagcommmand.go @@ -9,9 +9,9 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands/models" @@ -82,7 +82,7 @@ type RunningCommand struct { Command *YAGCommand } -type RolesRunFunc func(gs *dstate.GuildState) ([]int64, error) +type RolesRunFunc func(gs *dstate.GuildSet) ([]int64, error) // Slight extension to the simplecommand, it will check if the command is enabled in the HandleCommand func // And invoke a custom handlerfunc with provided redis client @@ -195,10 +195,8 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) { yc.logExecutionTime(time.Since(started), rawCommand, data.Author.Username) }() - var cState *dstate.ChannelState guildID := int64(0) if data.GuildData != nil { - cState = data.GuildData.CS guildID = data.GuildData.GS.ID } @@ -218,8 +216,9 @@ func (yc *YAGCommand) Run(data *dcmd.Data) (interface{}, error) { TimeStamp: time.Now(), } - if cState != nil && cState.Guild != nil { - logEntry.GuildID = discordgo.StrID(cState.Guild.ID) + if data.GuildData != nil { + logEntry.GuildID = discordgo.StrID(data.GuildData.GS.ID) + } metricsExcecutedCommands.With(prometheus.Labels{"name": "(other)", "trigger_type": triggerType}).Inc() @@ -398,28 +397,16 @@ type CanExecuteError struct { } // checks if the specified user can execute the command, and if so returns the settings for said command -func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data, cState *dstate.ChannelState) (canExecute bool, resp *CanExecuteError, settings *CommandSettings, err error) { +func (yc *YAGCommand) checkCanExecuteCommand(data *dcmd.Data) (canExecute bool, resp *CanExecuteError, settings *CommandSettings, err error) { // Check guild specific settings if not triggered from a DM - var guild *dstate.GuildState - - if data.Source != dcmd.TriggerSourceDM { - - guild = cState.Guild - - if guild == nil { - return false, &CanExecuteError{ - Type: ReasonError, - Message: "No guild?!?", - }, settings, errors.NewPlain("Not on a guild") - } + if data.GuildData != nil { + guild := data.GuildData.GS - if data.TriggerType != dcmd.TriggerTypeSlashCommands && !bot.BotProbablyHasPermissionGS(guild, cState.ID, discordgo.PermissionReadMessages|discordgo.PermissionSendMessages) { + if data.TriggerType != dcmd.TriggerTypeSlashCommands && !bot.BotProbablyHasPermissionGS(guild, data.ChannelID, discordgo.PermissionReadMessages|discordgo.PermissionSendMessages) { return false, nil, nil, nil } - cop := cState.Copy(true) - - settings, err = yc.GetSettings(data.ContainerChain, cState.ID, cop.ParentID, guild.ID) + settings, err = yc.GetSettings(data.ContainerChain, data.GuildData.CS.ID, data.GuildData.CS.ParentID, guild.ID) if err != nil { resp = &CanExecuteError{ Type: ReasonError, @@ -494,7 +481,7 @@ func checkWhitelistRoles(guildRoles map[int64]string, whitelistRoles []int64, da return nil } - for _, r := range member.Roles { + for _, r := range member.Member.Roles { if common.ContainsInt64Slice(whitelistRoles, r) { // we have a whitelist role! return nil @@ -539,7 +526,7 @@ func checkBlacklistRoles(guildRoles map[int64]string, blacklistRoles []int64, da } hasRole := int64(0) - for _, r := range member.Roles { + for _, r := range member.Member.Roles { if common.ContainsInt64Slice(blacklistRoles, r) { // we have a blacklist role! hasRole = r @@ -564,17 +551,17 @@ func checkBlacklistRoles(guildRoles map[int64]string, blacklistRoles []int64, da } } -func (yc *YAGCommand) checkRequiredMemberPerms(gs *dstate.GuildState, ms *dstate.MemberState, channelID int64) *CanExecuteError { +func (yc *YAGCommand) checkRequiredMemberPerms(gs *dstate.GuildSet, ms *dstate.MemberState, channelID int64) *CanExecuteError { // This command has permission sets required, if the user has one of them then allow this command to be used if len(yc.RequireDiscordPerms) < 1 { return nil } - perms, err := gs.MemberPermissionsMS(true, channelID, ms) + perms, err := gs.GetMemberPermissions(channelID, ms.User.ID, ms.Member.Roles) if err != nil { return &CanExecuteError{ Type: ReasonError, - Message: "Failed fetching member perms", + Message: "Failed fetching member perms?", } } @@ -598,7 +585,7 @@ func (yc *YAGCommand) checkRequiredMemberPerms(gs *dstate.GuildState, ms *dstate } } -func (yc *YAGCommand) checkRequiredBotPerms(gs *dstate.GuildState, channelID int64) *CanExecuteError { +func (yc *YAGCommand) checkRequiredBotPerms(gs *dstate.GuildSet, channelID int64) *CanExecuteError { // This command has permission sets required, if the user has one of them then allow this command to be used if len(yc.RequireBotPerms) < 1 { return nil @@ -644,11 +631,9 @@ OUTER: } } -func roleNames(gs *dstate.GuildState) map[int64]string { - gs.RLock() - defer gs.RUnlock() +func roleNames(gs *dstate.GuildSet) map[int64]string { result := make(map[int64]string) - for _, v := range gs.Guild.Roles { + for _, v := range gs.Roles { result[v.ID] = v.Name } diff --git a/common/cacheset/cacheset.go b/common/cacheset/cacheset.go new file mode 100644 index 0000000000..a4cd6c1d72 --- /dev/null +++ b/common/cacheset/cacheset.go @@ -0,0 +1,227 @@ +package cacheset + +import ( + "fmt" + "reflect" + "sync" + "time" +) + +type Manager struct { + slots []*Slot + TTL time.Duration + started bool +} + +func NewManager(ttl time.Duration) *Manager { + return &Manager{ + TTL: ttl, + } +} + +func (m *Manager) RunGCLoop() { + m.started = true + + if len(m.slots) < 1 { + // No slots? + return + } + + t := time.NewTicker(time.Minute) + i := 0 + for { + <-t.C + + slot := m.slots[i] + slot.gc(time.Now()) + + i++ + if i >= len(m.slots) { + i = 0 + } + } +} + +func (m *Manager) EvictSlotEntry(slot string, key interface{}) { + for _, v := range m.slots { + if v.name == slot { + v.Delete(key) + } + } +} + +func (m *Manager) FindSlot(slot string) *Slot { + for _, v := range m.slots { + if v.name == slot { + return v + } + } + + return nil +} + +type FetcherFunc = func(key interface{}) (interface{}, error) + +// RegisterSlot register a new cached "thing" +// this is only safe to called during init() and friends +func (m *Manager) RegisterSlot(name string, fetcher FetcherFunc, keyType interface{}) *Slot { + if m.started { + panic("tried adding slots after manager had started") + } + + for _, v := range m.slots { + if v.name == name { + panic(fmt.Sprintf("Key %s already used!", name)) + } + } + + slot := &Slot{ + manager: m, + name: name, + fetcher: fetcher, + values: make(map[interface{}]*cachedEntry), + fetching: make(map[interface{}]*sync.Cond), + keyType: reflect.TypeOf(keyType), + } + + m.slots = append(m.slots, slot) + + return slot +} + +type Slot struct { + manager *Manager + + name string + fetcher FetcherFunc + + valuesmu sync.RWMutex + values map[interface{}]*cachedEntry + fetching map[interface{}]*sync.Cond + + keyType reflect.Type +} + +func (s *Slot) Name() string { + return s.name +} + +type cachedEntry struct { + value interface{} + expiresAt time.Time + accessCounter *int64 +} + +func (s *Slot) Get(key interface{}) (interface{}, error) { + return s.GetCustomFetch(key, s.fetcher) +} + +func (s *Slot) GetCustomFetch(key interface{}, fetcher FetcherFunc) (interface{}, error) { + // fast path + if v := s.GetNoFetch(key); v != nil { + return v, nil + } + + // item was not in cache, we need to fetch it + s.valuesmu.Lock() + for { + if v := s.getValueLocked(key); v != nil { + // we have the value now! + s.valuesmu.Unlock() + return v, nil + } + + if c, ok := s.fetching[key]; ok { + // someone else is fetching this item, wait + c.Wait() + } else { + // we are not currently fetching this item, perform a a fetch + c = sync.NewCond(&s.valuesmu) + s.fetching[key] = c + + // unlock while were fetching to allow work on other guild's values + s.valuesmu.Unlock() + + v, err := s.fetch(fetcher, key) + + s.valuesmu.Lock() + if err == nil { + // we successfully retrieved a value, put it in a cached + s.values[key] = &cachedEntry{ + value: v, + expiresAt: time.Now().Add(s.manager.TTL), + accessCounter: new(int64), + } + } + + // no longer fetching this item + delete(s.fetching, key) + + // wake up all waiters + c.Broadcast() + s.valuesmu.Unlock() + + return v, err + } + } +} + +func (s *Slot) GetNoFetch(key interface{}) interface{} { + s.valuesmu.RLock() + defer s.valuesmu.RUnlock() + + return s.getValueLocked(key) +} + +func (s *Slot) getValueLocked(key interface{}) interface{} { + if v, ok := s.values[key]; ok { + return v.value + } + + return nil +} + +func (s *Slot) Delete(key interface{}) { + s.valuesmu.Lock() + defer s.valuesmu.Unlock() + + delete(s.values, key) +} + +func (s *Slot) DeleteFunc(f func(key interface{}, value interface{}) bool) int { + s.valuesmu.Lock() + defer s.valuesmu.Unlock() + + n := 0 + for k, v := range s.values { + if f(k, v) { + delete(s.values, k) + n++ + } + } + + return n +} + +func (s *Slot) fetch(fetcher FetcherFunc, key interface{}) (interface{}, error) { + return fetcher(key) +} + +func (s *Slot) gc(t time.Time) { + s.valuesmu.Lock() + defer s.valuesmu.Unlock() + + for k, v := range s.values { + if v.expired(t) { + delete(s.values, k) + } + } +} + +func (s *Slot) NewKey() interface{} { + return reflect.Indirect(reflect.New(s.keyType)) +} + +func (e *cachedEntry) expired(t time.Time) bool { + return t.After(e.expiresAt) +} diff --git a/common/cacheset/cachesset_test.go b/common/cacheset/cachesset_test.go new file mode 100644 index 0000000000..bbcbf0e150 --- /dev/null +++ b/common/cacheset/cachesset_test.go @@ -0,0 +1,201 @@ +package cacheset + +import ( + "reflect" + "sync" + "testing" + "time" +) + +func expectNoErr(t *testing.T, err error) { + if err != nil { + t.Fatal("expected no error") + } +} + +func TestGuildCacheBasic(t *testing.T) { + man := NewManager(time.Second) + + fetchCounter := 0 + slot := man.RegisterSlot("test", func(key interface{}) (interface{}, error) { + fetchCounter++ + return true, nil + }, int64(10)) + + checkValue := func(v interface{}) { + cast, ok := v.(bool) + if !ok { + t.Fatal("value is not bool: ", reflect.TypeOf(v)) + } + + if !cast { + t.Fatal("value is not true") + } + } + v, err := slot.Get(1) + expectNoErr(t, err) + checkValue(v) + + if len(slot.values) != 1 { + t.Fatal("value was not added to cache") + } + + v2, err := slot.Get(1) + expectNoErr(t, err) + checkValue(v2) + + if len(slot.values) != 1 { + t.Fatal("slot values incorrect") + } + + if fetchCounter != 1 { + t.Fatal("fetch counter is not 1") + } + + slot.gc(time.Now().Add(time.Hour)) + + if len(slot.values) != 0 { + t.Fatal("value was not remvoed from cache") + } + + v3, err := slot.Get(1) + expectNoErr(t, err) + checkValue(v3) + + if fetchCounter != 2 { + t.Fatal("fetch counter is not 2") + } +} + +type resEntry struct { + err error + v interface{} +} + +// this tests the waiting and broadcast of the cache +// and also tests to make sure only 1 fetch was triggered +func TestWaiting(t *testing.T) { + man := NewManager(time.Second) + + fetchCounter := 0 + slot := man.RegisterSlot("test", func(key interface{}) (interface{}, error) { + fetchCounter++ + time.Sleep(time.Second) + return true, nil + }, int64(10)) + + checkValue := func(v interface{}) { + cast, ok := v.(bool) + if !ok { + t.Fatal("value is not bool: ", reflect.TypeOf(v)) + } + + if !cast { + t.Fatal("value is not true") + } + } + + resChan := make(chan resEntry) + nGoroutines := 100 + + for i := 0; i < nGoroutines; i++ { + go func() { + vg, errg := slot.Get(1) + resChan <- resEntry{ + err: errg, + v: vg, + } + }() + } + + v2, err := slot.Get(1) + expectNoErr(t, err) + checkValue(v2) + + for i := 0; i < nGoroutines; i++ { + // check all results + gres := <-resChan + expectNoErr(t, gres.err) + checkValue(gres.v) + } + + if len(slot.values) != 1 { + t.Fatal("slot values incorrect") + } + + if fetchCounter != 1 { + t.Fatal("fetch counter is not 1") + } +} + +// this tests to make sure we can fire multiple concurrent fetches +func TestConcurrentFetch(t *testing.T) { + man := NewManager(time.Second) + + fetchCounter := 0 + var fetchmu sync.Mutex + var otherWaiting chan bool + slot := man.RegisterSlot("test", func(key interface{}) (interface{}, error) { + fetchmu.Lock() + fetchCounter++ + + if otherWaiting != nil { + otherWaiting <- true + fetchmu.Unlock() + } else { + otherWaiting = make(chan bool) + fetchmu.Unlock() + select { + case <-otherWaiting: + case <-time.After(time.Second): + panic("did not get concurrent fetch after 1 second") + } + } + + // time.Sleep(time.Second) + return true, nil + }, int64(10)) + + checkValue := func(v interface{}) { + cast, ok := v.(bool) + if !ok { + t.Fatal("value is not bool: ", reflect.TypeOf(v)) + } + + if !cast { + t.Fatal("value is not true") + } + } + + resChan := make(chan resEntry) + nGoroutines := 100 + + for i := 0; i < nGoroutines; i++ { + go func(id int) { + vg, errg := slot.Get(id) + resChan <- resEntry{ + err: errg, + v: vg, + } + }(i % 2) + } + + v2, err := slot.Get(1) + expectNoErr(t, err) + checkValue(v2) + + for i := 0; i < nGoroutines; i++ { + // check all results + gres := <-resChan + expectNoErr(t, gres.err) + checkValue(gres.v) + } + + if len(slot.values) != 2 { + t.Fatal("slot values incorrect: ", len(slot.values)) + } + + if fetchCounter != 2 { + t.Fatal("fetch counter is not 1: ", fetchCounter) + } +} diff --git a/common/common.go b/common/common.go index 7445a17735..dc28c0889e 100644 --- a/common/common.go +++ b/common/common.go @@ -19,6 +19,7 @@ import ( _ "github.com/jinzhu/gorm/dialects/postgres" "github.com/jmoiron/sqlx" "github.com/jonas747/discordgo" + "github.com/jonas747/yagpdb/common/cacheset" "github.com/mediocregopher/radix/v3" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -35,6 +36,7 @@ var ( SQLX *sqlx.DB RedisPool *radix.Pool + CacheSet = cacheset.NewManager(time.Hour) BotSession *discordgo.Session BotUser *discordgo.User @@ -81,6 +83,7 @@ func CoreInit() error { // Init initializes the rest of the bot func Init() error { + go CacheSet.RunGCLoop() err := setupGlobalDGoSession() if err != nil { diff --git a/common/mqueue/bot.go b/common/mqueue/bot.go index aa2c1b5a34..853f76d44d 100644 --- a/common/mqueue/bot.go +++ b/common/mqueue/bot.go @@ -33,6 +33,13 @@ var ( numWorkers = new(int32) ) +// type WebhookCacheKey struct { +// GuildID int64 `json:"guild_id"` +// ChannelID int64 `json:"channel_id"` +// } + +var webhookCache = common.CacheSet.RegisterSlot("mqueue_webhook", nil, int64(0)) + var _ bot.BotInitHandler = (*Plugin)(nil) func (p *Plugin) BotInit() { @@ -408,7 +415,7 @@ func trySendWebhook(l *logrus.Entry, elem *QueuedElement) (err error) { } } - gs := bot.State.Guild(true, elem.Guild) + gs := bot.State.GetGuild(elem.Guild) if gs == nil { // another check just in case if onGuild, err := common.BotIsOnGuild(elem.Guild); err == nil && !onGuild { @@ -418,21 +425,13 @@ func trySendWebhook(l *logrus.Entry, elem *QueuedElement) (err error) { } } - var whI interface{} - // in some cases guild state may not be available (starting up and the like) - if gs != nil { - whI, err = gs.UserCacheFetch(cacheKeyWebhook(elem.Channel), func() (interface{}, error) { - return findCreateWebhook(elem.Guild, elem.Channel, elem.Source, avatar) - }) - } else { - // fallback if no gs is available - whI, err = findCreateWebhook(elem.Guild, elem.Channel, elem.Source, avatar) - logger.WithField("guild", elem.Guild).Warn("Guild state not available, ignoring cache") - } - + whI, err := webhookCache.GetCustomFetch(elem.Channel, func(key interface{}) (interface{}, error) { + return findCreateWebhook(elem.Guild, elem.Channel, elem.Source, avatar) + }) if err != nil { return err } + wh := whI.(*webhook) webhookParams := &discordgo.WebhookParams{ @@ -453,9 +452,7 @@ func trySendWebhook(l *logrus.Entry, elem *QueuedElement) (err error) { return errors.WrapIf(err, "sql.delete") } - if gs != nil { - gs.UserCacheDel(cacheKeyWebhook(elem.Channel)) - } + webhookCache.Delete(elem.Channel) return errors.New("deleted webhook") } diff --git a/common/mqueue/mqueue.go b/common/mqueue/mqueue.go index e752ce2cb1..13558a5952 100644 --- a/common/mqueue/mqueue.go +++ b/common/mqueue/mqueue.go @@ -22,7 +22,7 @@ var ( confMaxWorkers = config.RegisterOption("yagpdb.mqueue.max_workers", "Max mqueue sending workers", 2) ) -// PluginWithSourceDisabler todo +// PluginWithSourceDisabler type PluginWithSourceDisabler interface { DisableFeed(elem *QueuedElement, err error) } diff --git a/common/pubsub/buildin_events.go b/common/pubsub/buildin_events.go index 5a4007f62f..8511eaefcb 100644 --- a/common/pubsub/buildin_events.go +++ b/common/pubsub/buildin_events.go @@ -1,10 +1,12 @@ package pubsub import ( + "encoding/json" "time" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/common" + "github.com/jonas747/yagpdb/common/cacheset" ) // PublishRatelimit publishes a new global ratelimit hit on discord @@ -34,3 +36,42 @@ func handleGlobalRatelimtPusub(evt *Event) { func handleEvictCoreConfigCache(evt *Event) { common.CoreServerConfigCache.Delete(int(evt.TargetGuildInt)) } + +type evictCacheSetData struct { + Name string `json:"name"` + Key json.RawMessage `json:"key"` +} + +func handleEvictCacheSet(evt *Event) { + cast := evt.Data.(*evictCacheSetData) + if slot := common.CacheSet.FindSlot(cast.Name); slot != nil { + t := slot.NewKey() + err := json.Unmarshal(cast.Key, &t) + if err != nil { + logger.WithError(err).Error("failed unmarshaling CacheSet key") + } + + slot.Delete(t) + } +} + +// EvictCacheSet sends a pubsub to evict the key on slot on all nodes if guildID is set to -1, otherwise the bot worker for that guild is the only one that handles it +func EvictCacheSet(slot *cacheset.Slot, key interface{}) { + // key := slot.Name() + // common.CacheSet.EvictSlotEntry(slot.Name(), key) + slot.Delete(key) + + marshalledKey, err := json.Marshal(key) + if err != nil { + logger.WithError(err).Error("failed marshaling CacheSet key") + return + } + + err = Publish("evict_guild_cache", -1, &evictCacheSetData{ + Name: slot.Name(), + Key: marshalledKey, + }) + if err != nil { + logger.WithError(err).Error("failed publishing guild cache eviction") + } +} diff --git a/common/pubsub/pubsub.go b/common/pubsub/pubsub.go index 6b7260eb52..c007e01d98 100644 --- a/common/pubsub/pubsub.go +++ b/common/pubsub/pubsub.go @@ -90,6 +90,7 @@ func PublishLogErr(evt string, target int64, data interface{}) { func PollEvents() { AddHandler("global_ratelimit", handleGlobalRatelimtPusub, globalRatelimitTriggeredEventData{}) AddHandler("evict_core_config_cache", handleEvictCoreConfigCache, nil) + AddHandler("evict_cache_set", handleEvictCacheSet, evictCacheSetData{}) common.BotSession.AddHandler(func(s *discordgo.Session, r *discordgo.RateLimit) { if r.Global { diff --git a/common/run/gen-docs.go b/common/run/gen-docs.go index 27a4e70e91..1cfdc78b98 100644 --- a/common/run/gen-docs.go +++ b/common/run/gen-docs.go @@ -9,7 +9,7 @@ import ( "github.com/jonas747/yagpdb/common/config" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/common/scheduledevents2/scheduledevents.go b/common/scheduledevents2/scheduledevents.go index 5d6aaa089b..88121a24d3 100644 --- a/common/scheduledevents2/scheduledevents.go +++ b/common/scheduledevents2/scheduledevents.go @@ -219,7 +219,7 @@ func (se *ScheduledEvents) checkShouldSkipRemove(id int64, guildID int64) (skip } // make sure the guild is available - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) if gs == nil { onGuild, err := common.BotIsOnGuild(guildID) if err != nil { @@ -232,11 +232,7 @@ func (se *ScheduledEvents) checkShouldSkipRemove(id int64, guildID int64) (skip return true, false } - gs.RLock() - unavailable := gs.Guild.Unavailable - gs.RUnlock() - - if unavailable { + if !gs.Available { // wait until the guild is available before handling this event return true, false } diff --git a/common/templates/context.go b/common/templates/context.go index 72bcd60ad0..7d1758fbfa 100644 --- a/common/templates/context.go +++ b/common/templates/context.go @@ -13,7 +13,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/template" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" @@ -82,16 +82,6 @@ var ( "currentTime": tmplCurrentTime, "newDate": tmplNewDate, - "escapeHere": func(s string) (string, error) { - return "", errors.New("function is removed in favor of better direct control over mentions, join support server and read the announcements for more info.") - }, - "escapeEveryone": func(s string) (string, error) { - return "", errors.New("function is removed in favor of better direct control over mentions, join support server and read the announcements for more info.") - }, - "escapeEveryoneHere": func(s string) (string, error) { - return "", errors.New("function is removed in favor of better direct control over mentions, join support server and read the announcements for more info.") - }, - "humanizeDurationHours": tmplHumanizeDurationHours, "humanizeDurationMinutes": tmplHumanizeDurationMinutes, "humanizeDurationSeconds": tmplHumanizeDurationSeconds, @@ -125,7 +115,7 @@ var GuildPremiumFunc func(guildID int64) (bool, error) type Context struct { Name string - GS *dstate.GuildState + GS *dstate.GuildSet MS *dstate.MemberState Msg *discordgo.Message BotUser *discordgo.User @@ -165,7 +155,7 @@ type contextFrame struct { SendResponseInDM bool } -func NewContext(gs *dstate.GuildState, cs *dstate.ChannelState, ms *dstate.MemberState) *Context { +func NewContext(gs *dstate.GuildSet, cs *dstate.ChannelState, ms *dstate.MemberState) *Context { ctx := &Context{ GS: gs, MS: ms, @@ -199,10 +189,9 @@ func (c *Context) setupContextFuncs() { func (c *Context) setupBaseData() { if c.GS != nil { - guild := c.GS.DeepCopy(false, true, true, false) - c.Data["Guild"] = guild - c.Data["Server"] = guild - c.Data["server"] = guild + c.Data["Guild"] = c.GS + c.Data["Server"] = c.GS + c.Data["server"] = c.GS } if c.CurrentFrame.CS != nil { @@ -212,8 +201,8 @@ func (c *Context) setupBaseData() { } if c.MS != nil { - c.Data["Member"] = c.MS.DGoCopy() - c.Data["User"] = c.MS.DGoUser() + c.Data["Member"] = c.MS.DgoMember() + c.Data["User"] = &c.MS.User c.Data["user"] = c.Data["User"] } @@ -266,18 +255,12 @@ func (c *Context) Execute(source string) (string, error) { return "", errors.WithMessage(err, "ctx.Execute") } - c.Msg.Member = member.DGoCopy() + c.Msg.Member = member.DgoMember() } } - if c.GS != nil { - c.GS.RLock() - } c.setupBaseData() - if c.GS != nil { - c.GS.RUnlock() - } parsed, err := c.Parse(source) if err != nil { @@ -383,7 +366,7 @@ func (c *Context) SendResponse(content string) (*discordgo.Message, error) { if c.CurrentFrame.CS != nil && c.CurrentFrame.CS.Type == discordgo.ChannelTypeDM { channelID = c.CurrentFrame.CS.ID } else { - privChannel, err := common.BotSession.UserChannelCreate(c.MS.ID) + privChannel, err := common.BotSession.UserChannelCreate(c.MS.User.ID) if err != nil { return nil, err } @@ -465,7 +448,7 @@ func (c *Context) LogEntry() *logrus.Entry { }) if c.MS != nil { - f = f.WithField("user", c.MS.ID) + f = f.WithField("user", c.MS.User.ID) } if c.CurrentFrame.CS != nil { diff --git a/common/templates/context_funcs.go b/common/templates/context_funcs.go index ca512c1ebf..e44880cebc 100644 --- a/common/templates/context_funcs.go +++ b/common/templates/context_funcs.go @@ -11,28 +11,24 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/scheduledevents2" ) -var ErrTooManyCalls = errors.New("Too many calls to this function") -var ErrTooManyAPICalls = errors.New("Too many potential discord api calls function") +var ErrTooManyCalls = errors.New("too many calls to this function") +var ErrTooManyAPICalls = errors.New("too many potential discord api calls function") func (c *Context) tmplSendDM(s ...interface{}) string { if len(s) < 1 || c.IncreaseCheckCallCounter("send_dm", 1) || c.MS == nil { return "" } - c.GS.RLock() - gName := c.GS.Guild.Name - gIcon := discordgo.EndpointGuildIcon(c.GS.Guild.ID, c.GS.Guild.Icon) - memberID := c.MS.ID - c.GS.RUnlock() + gIcon := discordgo.EndpointGuildIcon(c.GS.ID, c.GS.Icon) - info := fmt.Sprintf("Custom Command DM from the server **%s**", gName) - embedInfo := fmt.Sprintf("Custom Command DM from the server %s", gName) + info := fmt.Sprintf("Custom Command DM from the server **%s**", c.GS.Name) + embedInfo := fmt.Sprintf("Custom Command DM from the server %s", c.GS.Name) msgSend := &discordgo.MessageSend{ AllowedMentions: discordgo.AllowedMentions{ Parse: []discordgo.AllowedMentionType{discordgo.AllowedMentionTypeUsers}, @@ -63,7 +59,7 @@ func (c *Context) tmplSendDM(s ...interface{}) string { msgSend.Content = fmt.Sprintf("%s\n%s", info, fmt.Sprint(s...)) } - channel, err := common.BotSession.UserChannelCreate(memberID) + channel, err := common.BotSession.UserChannelCreate(c.MS.User.ID) if err != nil { return "" } @@ -74,9 +70,6 @@ func (c *Context) tmplSendDM(s ...interface{}) string { // ChannelArg converts a verity of types of argument into a channel, verifying that it exists func (c *Context) ChannelArg(v interface{}) int64 { - c.GS.RLock() - defer c.GS.RUnlock() - // Look for the channel if v == nil && c.CurrentFrame.CS != nil { // No channel passed, assume current channel @@ -110,11 +103,8 @@ func (c *Context) ChannelArg(v interface{}) int64 { if !verifiedExistence { // Make sure the channel is part of the guild - for k, _ := range c.GS.Channels { - if k == cid { - verifiedExistence = true - break - } + if channel := c.GS.GetChannel(cid); channel != nil { + verifiedExistence = true } } @@ -128,9 +118,6 @@ func (c *Context) ChannelArg(v interface{}) int64 { // ChannelArgNoDM is the same as ChannelArg but will not accept DM channels func (c *Context) ChannelArgNoDM(v interface{}) int64 { - c.GS.RLock() - defer c.GS.RUnlock() - // Look for the channel if v == nil && c.CurrentFrame.CS != nil { // No channel passed, assume current channel @@ -164,11 +151,8 @@ func (c *Context) ChannelArgNoDM(v interface{}) int64 { if !verifiedExistence { // Make sure the channel is part of the guild - for k, _ := range c.GS.Channels { - if k == cid { - verifiedExistence = true - break - } + if channel := c.GS.GetChannel(cid); channel != nil { + verifiedExistence = true } } @@ -192,15 +176,15 @@ func (c *Context) sendNestedTemplate(channel interface{}, dm bool, name string, return "", ErrTooManyCalls } if name == "" { - return "", errors.New("No template name passed") + return "", errors.New("no template name passed") } if c.CurrentFrame.isNestedTemplate { - return "", errors.New("Can't call this in a nested template") + return "", errors.New("can't call this in a nested template") } t := c.CurrentFrame.parsedTemplate.Lookup(name) if t == nil { - return "", errors.New("Unknown template") + return "", errors.New("unknown template") } var cs *dstate.ChannelState @@ -211,29 +195,28 @@ func (c *Context) sendNestedTemplate(channel interface{}, dm bool, name string, } else { cID := c.ChannelArg(channel) if cID == 0 { - return "", errors.New("Unknown channel") + return "", errors.New("unknown channel") } - cs = c.GS.ChannelCopy(true, cID) + cs = c.GS.GetChannel(cID) if cs == nil { - return "", errors.New("Unknown channel") + return "", errors.New("unknown channel") } } } else { if c.CurrentFrame.SendResponseInDM { cs = c.CurrentFrame.CS } else { - ch, err := common.BotSession.UserChannelCreate(c.MS.ID) + ch, err := common.BotSession.UserChannelCreate(c.MS.User.ID) if err != nil { return "", err } cs = &dstate.ChannelState{ - Owner: c.GS, - Guild: c.GS, - ID: ch.ID, - Name: c.MS.Username, - Type: discordgo.ChannelTypeDM, + GuildID: c.GS.ID, + ID: ch.ID, + Name: c.MS.User.Username, + Type: discordgo.ChannelTypeDM, } } } @@ -389,7 +372,7 @@ func (c *Context) tmplEditMessage(filterSpecialMentions bool) func(channel inter cid := c.ChannelArgNoDM(channel) if cid == 0 { - return "", errors.New("Unknown channel") + return "", errors.New("unknown channel") } mID := ToInt64(msgID) @@ -452,7 +435,7 @@ func (c *Context) tmplMentionRoleID(roleID interface{}) string { return "" } - r := c.GS.RoleCopy(true, role) + r := c.GS.GetRole(role) if r == nil { return "(role not found)" } @@ -471,16 +454,17 @@ func (c *Context) tmplMentionRoleName(role string) string { } var found *discordgo.Role - c.GS.RLock() - for _, r := range c.GS.Guild.Roles { + for _, r := range c.GS.Roles { if r.Name == role { if !common.ContainsInt64Slice(c.CurrentFrame.MentionRoles, r.ID) { c.CurrentFrame.MentionRoles = append(c.CurrentFrame.MentionRoles, r.ID) - found = r + + // make a copy as the looping var is changing + cop := r + found = &cop } } } - c.GS.RUnlock() if found == nil { return "(role not found)" } @@ -494,7 +478,7 @@ func (c *Context) tmplHasRoleID(roleID interface{}) bool { return false } - contains := common.ContainsInt64Slice(c.MS.Roles, role) + contains := common.ContainsInt64Slice(c.MS.Member.Roles, role) return contains } @@ -503,12 +487,9 @@ func (c *Context) tmplHasRoleName(name string) (bool, error) { return false, ErrTooManyCalls } - c.GS.RLock() - defer c.GS.RUnlock() - - for _, r := range c.GS.Guild.Roles { + for _, r := range c.GS.Roles { if strings.EqualFold(r.Name, name) { - if common.ContainsInt64Slice(c.MS.Roles, r.ID) { + if common.ContainsInt64Slice(c.MS.Member.Roles, r.ID) { return true, nil } @@ -561,7 +542,7 @@ func (c *Context) tmplTargetHasRoleID(target interface{}, roleID interface{}) bo return false } - contains := common.ContainsInt64Slice(ts.Roles, role) + contains := common.ContainsInt64Slice(ts.Member.Roles, role) return contains @@ -582,21 +563,12 @@ func (c *Context) tmplTargetHasRoleName(target interface{}, name string) bool { return false } - c.GS.RLock() - - for _, r := range c.GS.Guild.Roles { + for _, r := range c.GS.Roles { if strings.EqualFold(r.Name, name) { - if common.ContainsInt64Slice(ts.Roles, r.ID) { - c.GS.RUnlock() - return true - } - - c.GS.RUnlock() - return false + return common.ContainsInt64Slice(ts.Member.Roles, r.ID) } } - c.GS.RUnlock() return false } @@ -616,20 +588,7 @@ func (c *Context) tmplGiveRoleID(target interface{}, roleID interface{}) string return "" } - // Check to see if we can save a API request here - c.GS.RLock() - ms := c.GS.Member(false, targetID) - hasRole := false - if ms != nil { - hasRole = common.ContainsInt64Slice(ms.Roles, role) - } - c.GS.RUnlock() - - if !hasRole { - common.BotSession.GuildMemberRoleAdd(c.GS.ID, targetID, role) - } - - return "" + return c.giveRole(targetID, role) } func (c *Context) tmplGiveRoleName(target interface{}, name string) string { @@ -647,15 +606,23 @@ func (c *Context) tmplGiveRoleName(target interface{}, name string) string { return "no role by the name of " + name + " found" } - // Maybe save a api request - ms := c.GS.Member(false, targetID) - if ms != nil { - if common.ContainsInt64Slice(ms.Roles, role.ID) { - return "" - } + return c.giveRole(targetID, role.ID) +} + +func (c *Context) giveRole(targetID int64, roleID int64) string { + if c.GS.GetRole(roleID) == nil { + return "" // role does not exist } - common.BotSession.GuildMemberRoleAdd(c.GS.ID, targetID, role.ID) + // Check to see if we can save a API request here + ms, err := bot.GetMember(c.GS.ID, targetID) + if err != nil { + return "" + } + + if !common.ContainsInt64Slice(ms.Member.Roles, roleID) { + common.BotSession.GuildMemberRoleAdd(c.GS.ID, targetID, roleID) + } return "" } @@ -680,28 +647,7 @@ func (c *Context) tmplTakeRoleID(target interface{}, roleID interface{}, optiona return "" } - // Check to see if we can save a API request here, if this isn't delayed - if delay <= 0 { - c.GS.RLock() - ms := c.GS.Member(false, targetID) - hasRole := true - if ms != nil && ms.MemberSet { - hasRole = common.ContainsInt64Slice(ms.Roles, role) - } - c.GS.RUnlock() - - if !hasRole { - return "" - } - } - - if delay > 0 { - scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, targetID, role, time.Now().Add(time.Second*time.Duration(delay))) - } else { - common.BotSession.GuildMemberRoleRemove(c.GS.ID, targetID, role) - } - - return "" + return c.takeRole(targetID, role, time.Second*time.Duration(delay)) } func (c *Context) tmplTakeRoleName(target interface{}, name string, optionalArgs ...interface{}) string { @@ -719,39 +665,32 @@ func (c *Context) tmplTakeRoleName(target interface{}, name string, optionalArgs return "" } - role := int64(0) - c.GS.RLock() - for _, r := range c.GS.Guild.Roles { - if strings.EqualFold(r.Name, name) { - role = r.ID + role := c.findRoleByName(name) + if role != nil { + return c.takeRole(targetID, role.ID, time.Second*time.Duration(delay)) + } - // Maybe save a api request, but only if this is not delayed - if delay <= 0 { - ms := c.GS.Member(false, targetID) - hasRole := true - if ms != nil && ms.MemberSet { - hasRole = common.ContainsInt64Slice(ms.Roles, role) - } + return "" +} - if !hasRole { - c.GS.RUnlock() - return "" - } - } +func (c *Context) takeRole(targetID int64, roleID int64, delay time.Duration) string { + if c.GS.GetRole(roleID) == nil { + return "" // role does not exist + } - break - } + ms, err := bot.GetMember(c.GS.ID, targetID) + if err != nil { + return "" } - c.GS.RUnlock() - if role == 0 { + if common.ContainsInt64Slice(ms.Member.Roles, roleID) { return "" } if delay > 0 { - scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, targetID, role, time.Now().Add(time.Second*time.Duration(delay))) + scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, targetID, roleID, time.Now().Add(delay)) } else { - common.BotSession.GuildMemberRoleRemove(c.GS.ID, targetID, role) + common.BotSession.GuildMemberRoleRemove(c.GS.ID, targetID, roleID) } return "" @@ -813,7 +752,7 @@ func (c *Context) tmplAddRoleID(role interface{}) (string, error) { rid := ToInt64(role) if rid == 0 { - return "", errors.New("No role id specified") + return "", errors.New("no role id specified") } err := common.AddRoleDS(c.MS, rid) @@ -834,14 +773,12 @@ func (c *Context) tmplAddRoleName(name string) (string, error) { } role := int64(0) - c.GS.RLock() - for _, r := range c.GS.Guild.Roles { + for _, r := range c.GS.Roles { if strings.EqualFold(r.Name, name) { role = r.ID break } } - c.GS.RUnlock() if role == 0 { return "", errors.New("No Role with name " + name + " found") @@ -864,17 +801,17 @@ func (c *Context) tmplRemoveRoleID(role interface{}, optionalArgs ...interface{} delay = tmplToInt(optionalArgs[0]) } - if c.MS == nil { - return "", nil - } - rid := ToInt64(role) if rid == 0 { - return "", errors.New("No role id specified") + return "", errors.New("no role id specified") + } + + if c.GS.GetRole(rid) == nil { + return "", errors.New("unknown role") } if delay > 0 { - scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, c.MS.ID, rid, time.Now().Add(time.Second*time.Duration(delay))) + scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, c.MS.User.ID, rid, time.Now().Add(time.Second*time.Duration(delay))) } else { common.RemoveRoleDS(c.MS, rid) } @@ -902,7 +839,7 @@ func (c *Context) tmplRemoveRoleName(name string, optionalArgs ...interface{}) ( } if delay > 0 { - scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, c.MS.ID, role.ID, time.Now().Add(time.Second*time.Duration(delay))) + scheduledevents2.ScheduleRemoveRole(context.Background(), c.GS.ID, c.MS.User.ID, role.ID, time.Now().Add(time.Second*time.Duration(delay))) } else { if err := common.RemoveRoleDS(c.MS, role.ID); err != nil { return "", err @@ -913,12 +850,9 @@ func (c *Context) tmplRemoveRoleName(name string, optionalArgs ...interface{}) ( } func (c *Context) findRoleByName(name string) *discordgo.Role { - c.GS.RLock() - defer c.GS.RUnlock() - - for _, r := range c.GS.Guild.Roles { + for _, r := range c.GS.Roles { if strings.EqualFold(r.Name, name) { - return r + return &r } } @@ -976,7 +910,7 @@ func (c *Context) tmplDelMessageReaction(values ...reflect.Value) (reflect.Value f := func(args []reflect.Value) (reflect.Value, error) { if len(args) < 4 { - return reflect.Value{}, errors.New("Not enough arguments (need channelID, messageID, userID, emoji)") + return reflect.Value{}, errors.New("not enough arguments (need channelID, messageID, userID, emoji)") } var cArg interface{} @@ -1012,7 +946,7 @@ func (c *Context) tmplDelAllMessageReactions(values ...reflect.Value) (reflect.V f := func(args []reflect.Value) (reflect.Value, error) { if len(args) < 2 { - return reflect.Value{}, errors.New("Not enough arguments (need channelID, messageID, emojis[optional])") + return reflect.Value{}, errors.New("not enough arguments (need channelID, messageID, emojis[optional])") } var cArg interface{} @@ -1081,7 +1015,7 @@ func (c *Context) tmplGetMember(target interface{}) (*discordgo.Member, error) { return nil, nil } - return member.DGoCopy(), nil + return member.DgoMember(), nil } func (c *Context) tmplGetChannel(channel interface{}) (*CtxChannel, error) { @@ -1095,10 +1029,10 @@ func (c *Context) tmplGetChannel(channel interface{}) (*CtxChannel, error) { return nil, nil //dont send an error , a nil output would indicate invalid/unknown channel } - cstate := c.GS.ChannelCopy(true, cID) + cstate := c.GS.GetChannel(cID) if cstate == nil { - return nil, errors.New("Channel not in state") + return nil, errors.New("channel not in state") } return CtxChannelFromCS(cstate), nil @@ -1143,7 +1077,7 @@ func (c *Context) tmplAddResponseReactions(values ...reflect.Value) (reflect.Val func (c *Context) tmplAddMessageReactions(values ...reflect.Value) (reflect.Value, error) { f := func(args []reflect.Value) (reflect.Value, error) { if len(args) < 2 { - return reflect.Value{}, errors.New("Not enough arguments (need channel and message-id)") + return reflect.Value{}, errors.New("not enough arguments (need channel and message-id)") } // cArg := args[0].Interface() @@ -1179,7 +1113,7 @@ func (c *Context) tmplAddMessageReactions(values ...reflect.Value) (reflect.Valu } func (c *Context) tmplCurrentUserAgeHuman() string { - t := bot.SnowflakeToTime(c.MS.ID) + t := bot.SnowflakeToTime(c.MS.User.ID) humanized := common.HumanizeDuration(common.DurationPrecisionHours, time.Since(t)) if humanized == "" { @@ -1190,14 +1124,14 @@ func (c *Context) tmplCurrentUserAgeHuman() string { } func (c *Context) tmplCurrentUserAgeMinutes() int { - t := bot.SnowflakeToTime(c.MS.ID) + t := bot.SnowflakeToTime(c.MS.User.ID) d := time.Since(t) return int(d.Seconds() / 60) } func (c *Context) tmplCurrentUserCreated() time.Time { - t := bot.SnowflakeToTime(c.MS.ID) + t := bot.SnowflakeToTime(c.MS.User.ID) return t } @@ -1315,7 +1249,7 @@ func (c *Context) tmplEditChannelName(channel interface{}, newName string) (stri cID := c.ChannelArgNoDM(channel) if cID == 0 { - return "", errors.New("Unknown channel") + return "", errors.New("unknown channel") } if c.IncreaseCheckCallCounter("edit_channel_"+strconv.FormatInt(cID, 10), 2) { @@ -1333,7 +1267,7 @@ func (c *Context) tmplEditChannelTopic(channel interface{}, newTopic string) (st cID := c.ChannelArgNoDM(channel) if cID == 0 { - return "", errors.New("Unknown channel") + return "", errors.New("unknown channel") } if c.IncreaseCheckCallCounter("edit_channel_"+strconv.FormatInt(cID, 10), 2) { @@ -1348,40 +1282,36 @@ func (c *Context) tmplEditChannelTopic(channel interface{}, newTopic string) (st return "", err } +// DEPRECATED: this function will return unreliable numbers anyways func (c *Context) tmplOnlineCount() (int, error) { - if c.IncreaseCheckCallCounter("online_users", 1) { - return 0, ErrTooManyCalls - } - - online := 0 - c.GS.RLock() - for _, v := range c.GS.Members { - if v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { - online++ - } - } - c.GS.RUnlock() - - return online, nil + // if c.IncreaseCheckCallCounter("online_users", 1) { + // return 0, ErrTooManyCalls + // } + + // online := 0 + // for _, v := range c.GS.Members { + // if v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { + // online++ + // } + // } + + return 0, nil } func (c *Context) tmplOnlineCountBots() (int, error) { - if c.IncreaseCheckCallCounter("online_bots", 1) { - return 0, ErrTooManyCalls - } - - botCount := 0 + // if c.IncreaseCheckCallCounter("online_bots", 1) { + // return 0, ErrTooManyCalls + // } - c.GS.RLock() - defer c.GS.RUnlock() + // botCount := 0 - for _, v := range c.GS.Members { - if v.Bot && v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { - botCount++ - } - } + // for _, v := range c.GS.Members { + // if v.Bot && v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { + // botCount++ + // } + // } - return botCount, nil + return 0, nil } func (c *Context) tmplEditNickname(Nickname string) (string, error) { @@ -1394,13 +1324,13 @@ func (c *Context) tmplEditNickname(Nickname string) (string, error) { return "", nil } - if strings.Compare(c.MS.Nick, Nickname) == 0 { + if strings.Compare(c.MS.Member.Nick, Nickname) == 0 { return "", nil } - err := common.BotSession.GuildMemberNickname(c.GS.ID, c.MS.ID, Nickname) + err := common.BotSession.GuildMemberNickname(c.GS.ID, c.MS.User.ID, Nickname) if err != nil { return "", err } diff --git a/common/templates/general.go b/common/templates/general.go index b3f86ca9ca..d8adc04b8d 100644 --- a/common/templates/general.go +++ b/common/templates/general.go @@ -980,7 +980,6 @@ func slice(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) // Both start and end index provided startIndex = args[0] endIndex = args[1] - break default: return reflect.Value{}, errors.Errorf("unexpected slice arguments %d", len(args)) } diff --git a/common/templates/structs.go b/common/templates/structs.go index 7173ab4efd..b43e6076c5 100644 --- a/common/templates/structs.go +++ b/common/templates/structs.go @@ -2,7 +2,7 @@ package templates import ( "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" ) // CtxChannel is almost a 1:1 copy of dstate.ChannelState, its needed because we cant axpose all those state methods @@ -16,7 +16,6 @@ type CtxChannel struct { Name string `json:"name"` Type discordgo.ChannelType `json:"type"` Topic string `json:"topic"` - LastMessageID int64 `json:"last_message_id"` NSFW bool `json:"nsfw"` Position int `json:"position"` Bitrate int `json:"bitrate"` @@ -29,48 +28,25 @@ func (c *CtxChannel) Mention() string { } func CtxChannelFromCS(cs *dstate.ChannelState) *CtxChannel { - ctxChannel := &CtxChannel{ - ID: cs.ID, - IsPrivate: cs.IsPrivate, - Name: cs.Name, - Type: cs.Type, - Topic: cs.Topic, - LastMessageID: cs.LastMessageID, - NSFW: cs.NSFW, - Position: cs.Position, - Bitrate: cs.Bitrate, - PermissionOverwrites: cs.PermissionOverwrites, - ParentID: cs.ParentID, - } - if !cs.IsPrivate { - ctxChannel.GuildID = cs.Guild.ID + cop := make([]*discordgo.PermissionOverwrite, len(cs.PermissionOverwrites)) + for i := 0; i < len(cs.PermissionOverwrites); i++ { + cop[i] = &cs.PermissionOverwrites[i] } - return ctxChannel -} - -func CtxChannelFromCSLocked(cs *dstate.ChannelState) *CtxChannel { - cs.Owner.RLock() - defer cs.Owner.RUnlock() - ctxChannel := &CtxChannel{ ID: cs.ID, - IsPrivate: cs.IsPrivate, + IsPrivate: cs.IsPrivate(), + GuildID: cs.GuildID, Name: cs.Name, Type: cs.Type, Topic: cs.Topic, - LastMessageID: cs.LastMessageID, NSFW: cs.NSFW, Position: cs.Position, Bitrate: cs.Bitrate, - PermissionOverwrites: cs.PermissionOverwrites, + PermissionOverwrites: cop, ParentID: cs.ParentID, } - if !cs.IsPrivate { - ctxChannel.GuildID = cs.Guild.ID - } - return ctxChannel } diff --git a/common/util.go b/common/util.go index 9ae84d41d3..061383a4bf 100644 --- a/common/util.go +++ b/common/util.go @@ -12,9 +12,9 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/lib/pq" "github.com/mediocregopher/radix/v3" "github.com/sirupsen/logrus" @@ -253,14 +253,14 @@ func AddRole(member *discordgo.Member, role int64, guildID int64) error { } func AddRoleDS(ms *dstate.MemberState, role int64) error { - for _, v := range ms.Roles { + for _, v := range ms.Member.Roles { if v == role { // Already has the role return nil } } - return BotSession.GuildMemberRoleAdd(ms.Guild.ID, ms.ID, role) + return BotSession.GuildMemberRoleAdd(ms.GuildID, ms.User.ID, role) } func RemoveRole(member *discordgo.Member, role int64, guildID int64) error { @@ -275,9 +275,9 @@ func RemoveRole(member *discordgo.Member, role int64, guildID int64) error { } func RemoveRoleDS(ms *dstate.MemberState, role int64) error { - for _, r := range ms.Roles { + for _, r := range ms.Member.Roles { if r == role { - return BotSession.GuildMemberRoleRemove(ms.Guild.ID, ms.ID, r) + return BotSession.GuildMemberRoleRemove(ms.GuildID, ms.User.ID, r) } } diff --git a/customcommands/bot.go b/customcommands/bot.go index f9a3b389d3..e570ba14ea 100644 --- a/customcommands/bot.go +++ b/customcommands/bot.go @@ -20,9 +20,9 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" @@ -67,17 +67,7 @@ func (p *Plugin) BotInit() { eventsystem.AddHandlerAsyncLastLegacy(p, bot.ConcurrentEventHandler(HandleMessageCreate), eventsystem.EventMessageCreate) eventsystem.AddHandlerAsyncLastLegacy(p, bot.ConcurrentEventHandler(handleMessageReactions), eventsystem.EventMessageReactionAdd, eventsystem.EventMessageReactionRemove) - // add the pubsub handler for cache eviction - pubsub.AddHandler("custom_commands_clear_cache", func(event *pubsub.Event) { - gs := bot.State.Guild(true, event.TargetGuildInt) - if gs == nil { - return - } - - gs.UserCacheDel(CacheKeyCommands) - }, nil) pubsub.AddHandler("custom_commands_run_now", handleCustomCommandsRunNow, models.CustomCommand{}) - scheduledevents2.RegisterHandler("cc_next_run", NextRunScheduledEvent{}, handleNextRunScheduledEVent) scheduledevents2.RegisterHandler("cc_delayed_run", DelayedRunCCData{}, handleDelayedRunCC) } @@ -89,13 +79,13 @@ func handleCustomCommandsRunNow(event *pubsub.Event) { "cmd_id": dataCast.LocalID, }) - gs := bot.State.Guild(true, dataCast.GuildID) + gs := bot.State.GetGuild(dataCast.GuildID) if gs == nil { f.Error("failed fetching active guild from state") return } - cs := gs.Channel(true, dataCast.ContextChannel) + cs := gs.GetChannel(dataCast.ContextChannel) if cs == nil { f.Error("failed finding channel to run cc in") return @@ -185,16 +175,11 @@ var cmdListCommands = &commands.YAGCommand{ var msg *discordgo.MessageSend if data.Switches["file"].Value != nil { - - data.GuildData.GS.Lock() - gName := data.GuildData.GS.Guild.Name - data.GuildData.GS.Unlock() - var buf bytes.Buffer buf.WriteString(strings.Join(cc.Responses, "\nAdditional response:\n")) ccFile = &discordgo.File{ - Name: fmt.Sprintf("%s_CC_%d.%s", gName, cc.LocalID, highlight), + Name: fmt.Sprintf("%s_CC_%d.%s", data.GuildData.GS.Name, cc.LocalID, highlight), Reader: &buf, } } @@ -285,7 +270,7 @@ func handleDelayedRunCC(evt *schEventsModels.ScheduledEvent, data interface{}) ( return false, nil } - gs := bot.State.Guild(true, evt.GuildID) + gs := bot.State.GetGuild(evt.GuildID) if gs == nil { // in case the bot left in the meantime if onGuild, err := common.BotIsOnGuild(evt.GuildID); !onGuild && err == nil { @@ -297,10 +282,10 @@ func handleDelayedRunCC(evt *schEventsModels.ScheduledEvent, data interface{}) ( return true, nil } - cs := gs.Channel(true, dataCast.ChannelID) + cs := gs.GetChannel(dataCast.ChannelID) if cs == nil { // don't reschedule if channel is deleted, make sure its actually not there, and not just a discord downtime - if !gs.IsAvailable(true) { + if !gs.Available { return true, nil } @@ -309,7 +294,7 @@ func handleDelayedRunCC(evt *schEventsModels.ScheduledEvent, data interface{}) ( // attempt to get up to date member information if dataCast.Member != nil { - updatedMS, _ := bot.GetMember(gs.ID, dataCast.Member.ID) + updatedMS, _ := bot.GetMember(gs.ID, dataCast.Member.User.ID) if updatedMS != nil { dataCast.Member = updatedMS } @@ -349,7 +334,7 @@ func handleNextRunScheduledEVent(evt *schEventsModels.ScheduledEvent, data inter } - gs := bot.State.Guild(true, evt.GuildID) + gs := bot.State.GetGuild(evt.GuildID) if gs == nil { if onGuild, err := common.BotIsOnGuild(evt.GuildID); !onGuild && err == nil { return false, nil @@ -360,10 +345,10 @@ func handleNextRunScheduledEVent(evt *schEventsModels.ScheduledEvent, data inter return true, nil } - cs := gs.Channel(true, cmd.ContextChannel) + cs := gs.GetChannel(cmd.ContextChannel) if cs == nil { // don't reschedule if channel is deleted, make sure its actually not there, and not just a discord downtime - if !gs.IsAvailable(true) { + if !gs.Available { return true, nil } @@ -385,7 +370,7 @@ func handleNextRunScheduledEVent(evt *schEventsModels.ScheduledEvent, data inter return false, nil } -func shouldIgnoreChannel(evt *discordgo.MessageCreate, cState *dstate.ChannelState) bool { +func shouldIgnoreChannel(evt *discordgo.MessageCreate, gs *dstate.GuildSet, cState *dstate.ChannelState) bool { if evt.GuildID == 0 { return true } @@ -403,7 +388,7 @@ func shouldIgnoreChannel(evt *discordgo.MessageCreate, cState *dstate.ChannelSta return true } - if !bot.BotProbablyHasPermissionGS(cState.Guild, cState.ID, discordgo.PermissionSendMessages) { + if !bot.BotProbablyHasPermissionGS(gs, cState.ID, discordgo.PermissionSendMessages) { return true } @@ -447,7 +432,7 @@ func handleMessageReactions(evt *eventsystem.EventData) { return } - if !bot.BotProbablyHasPermissionGS(cState.Guild, cState.ID, discordgo.PermissionSendMessages) { + if !bot.BotProbablyHasPermissionGS(evt.GS, cState.ID, discordgo.PermissionSendMessages) { // don't run in channel we don't have perms in return } @@ -475,22 +460,22 @@ func handleMessageReactions(evt *eventsystem.EventData) { logger.WithField("guild", evt.GS.ID).WithError(err).Error("failed finding reaction ccs") return } - rMessage.GuildID = cState.Guild.ID + rMessage.GuildID = cState.GuildID for _, matched := range triggeredCmds { - err = ExecuteCustomCommandFromReaction(matched.CC, ms, cState, reaction, added, rMessage) + err = ExecuteCustomCommandFromReaction(matched.CC, evt.GS, ms, cState, reaction, added, rMessage) if err != nil { - logger.WithField("guild", cState.Guild.ID).WithField("cc_id", matched.CC.LocalID).WithError(err).Error("Error executing custom command") + logger.WithField("guild", cState.GuildID).WithField("cc_id", matched.CC.LocalID).WithError(err).Error("Error executing custom command") } } } -func ExecuteCustomCommandFromReaction(cc *models.CustomCommand, ms *dstate.MemberState, cs *dstate.ChannelState, reaction *discordgo.MessageReaction, added bool, message *discordgo.Message) error { - tmplCtx := templates.NewContext(cs.Guild, cs, ms) +func ExecuteCustomCommandFromReaction(cc *models.CustomCommand, gs *dstate.GuildSet, ms *dstate.MemberState, cs *dstate.ChannelState, reaction *discordgo.MessageReaction, added bool, message *discordgo.Message) error { + tmplCtx := templates.NewContext(gs, cs, ms) // to make sure the message is in the proper context of the user reacting we set the mssage context to a fake message fakeMsg := *message - fakeMsg.Member = ms.DGoCopy() + fakeMsg.Member = ms.DgoMember() fakeMsg.Author = fakeMsg.Member.User tmplCtx.Msg = &fakeMsg @@ -510,11 +495,12 @@ func HandleMessageCreate(evt *eventsystem.EventData) { return } - if shouldIgnoreChannel(mc, cs) { + if shouldIgnoreChannel(mc, evt.GS, cs) { return } - member := dstate.MSFromDGoMember(evt.GS, mc.Member) + member := dstate.MemberStateFromMember(mc.Member) + member.GuildID = evt.GS.ID var matchedCustomCommands []*TriggeredCC var err error @@ -533,7 +519,7 @@ func HandleMessageCreate(evt *eventsystem.EventData) { metricsExecutedCommands.With(prometheus.Labels{"trigger": "message"}).Inc() for _, matched := range matchedCustomCommands { - err = ExecuteCustomCommandFromMessage(matched.CC, member, cs, matched.Args, matched.Stripped, mc.Message) + err = ExecuteCustomCommandFromMessage(evt.GS, matched.CC, member, cs, matched.Args, matched.Stripped, mc.Message) if err != nil { logger.WithField("guild", mc.GuildID).WithField("cc_id", matched.CC.LocalID).WithError(err).Error("Error executing custom command") } @@ -547,7 +533,7 @@ type TriggeredCC struct { } func findMessageTriggerCustomCommands(ctx context.Context, cs *dstate.ChannelState, ms *dstate.MemberState, evt *eventsystem.EventData) (matches []*TriggeredCC, err error) { - cmds, err := BotCachedGetCommandsWithMessageTriggers(cs.Guild, ctx) + cmds, err := BotCachedGetCommandsWithMessageTriggers(cs.GuildID, ctx) if err != nil { return nil, errors.WrapIf(err, "BotCachedGetCommandsWithMessageTriggers") } @@ -589,7 +575,7 @@ func findMessageTriggerCustomCommands(ctx context.Context, cs *dstate.ChannelSta } func findReactionTriggerCustomCommands(ctx context.Context, cs *dstate.ChannelState, userID int64, reaction *discordgo.MessageReaction, add bool) (ms *dstate.MemberState, matches []*TriggeredCC, err error) { - cmds, err := BotCachedGetCommandsWithMessageTriggers(cs.Guild, ctx) + cmds, err := BotCachedGetCommandsWithMessageTriggers(cs.GuildID, ctx) if err != nil { return nil, nil, errors.WrapIf(err, "BotCachedGetCommandsWithMessageTriggers") } @@ -613,7 +599,7 @@ func findReactionTriggerCustomCommands(ctx context.Context, cs *dstate.ChannelSt return nil, matched, nil } - ms, err = bot.GetMember(cs.Guild.ID, userID) + ms, err = bot.GetMember(cs.GuildID, userID) if err != nil { return nil, nil, errors.WithStackIf(err) } @@ -631,7 +617,7 @@ func findReactionTriggerCustomCommands(ctx context.Context, cs *dstate.ChannelSt sortTriggeredCCs(filtered) limit := CCMessageExecLimitNormal - if isPremium, _ := premium.IsGuildPremiumCached(cs.Guild.ID); isPremium { + if isPremium, _ := premium.IsGuildPremiumCached(cs.GuildID); isPremium { limit = CCMessageExecLimitPremium } @@ -663,8 +649,8 @@ func sortTriggeredCCs(ccs []*TriggeredCC) { }) } -func ExecuteCustomCommandFromMessage(cmd *models.CustomCommand, member *dstate.MemberState, cs *dstate.ChannelState, cmdArgs []string, stripped string, m *discordgo.Message) error { - tmplCtx := templates.NewContext(cs.Guild, cs, member) +func ExecuteCustomCommandFromMessage(gs *dstate.GuildSet, cmd *models.CustomCommand, member *dstate.MemberState, cs *dstate.ChannelState, cmdArgs []string, stripped string, m *discordgo.Message) error { + tmplCtx := templates.NewContext(gs, cs, member) tmplCtx.Msg = m // preapre message specific data @@ -706,11 +692,11 @@ func ExecuteCustomCommand(cmd *models.CustomCommand, tmplCtx *templates.Context) tmplCtx.Data["CCID"] = cmd.LocalID tmplCtx.Data["CCRunCount"] = cmd.RunCount + 1 - csCop := tmplCtx.CurrentFrame.CS.Copy(true) + csCop := tmplCtx.CurrentFrame.CS f := logger.WithFields(logrus.Fields{ "trigger": cmd.TextTrigger, "trigger_type": CommandTriggerType(cmd.TriggerType).String(), - "guild": csCop.Guild.ID, + "guild": csCop.GuildID, "channel_name": csCop.Name, }) @@ -1018,22 +1004,15 @@ func CheckMatchReaction(cmd *models.CustomCommand, reaction *discordgo.MessageRe return false } -type CacheKey int - -const ( - CacheKeyCommands CacheKey = iota - CacheKeyReactionCommands - - CacheKeyDBLimits -) +var cachedCommandsMessage = common.CacheSet.RegisterSlot("custom_commands_message_trigger", nil, int64(0)) -func BotCachedGetCommandsWithMessageTriggers(gs *dstate.GuildState, ctx context.Context) ([]*models.CustomCommand, error) { - v, err := gs.UserCacheFetch(CacheKeyCommands, func() (interface{}, error) { +func BotCachedGetCommandsWithMessageTriggers(guildID int64, ctx context.Context) ([]*models.CustomCommand, error) { + v, err := cachedCommandsMessage.GetCustomFetch(guildID, func(key interface{}) (interface{}, error) { var cmds []*models.CustomCommand var err error - common.LogLongCallTime(time.Second, true, "Took longer than a second to fetch custom commands from db", logrus.Fields{"guild": gs.ID}, func() { - cmds, err = models.CustomCommands(qm.Where("guild_id = ? AND trigger_type IN (0,1,2,3,4,6)", gs.Guild.ID), qm.OrderBy("local_id desc"), qm.Load("Group")).AllG(ctx) + common.LogLongCallTime(time.Second, true, "Took longer than a second to fetch custom commands from db", logrus.Fields{"guild": guildID}, func() { + cmds, err = models.CustomCommands(qm.Where("guild_id = ? AND trigger_type IN (0,1,2,3,4,6)", guildID), qm.OrderBy("local_id desc"), qm.Load("Group")).AllG(ctx) }) return cmds, err diff --git a/customcommands/customcommands.go b/customcommands/customcommands.go index 0604c3681b..ade15966ee 100644 --- a/customcommands/customcommands.go +++ b/customcommands/customcommands.go @@ -12,7 +12,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/featureflags" "github.com/jonas747/yagpdb/customcommands/models" @@ -248,11 +248,11 @@ func CmdRunsInChannel(cc *models.CustomCommand, channel int64) bool { func CmdRunsForUser(cc *models.CustomCommand, ms *dstate.MemberState) bool { if cc.GroupID.Valid { // check group restrictions - if common.ContainsInt64SliceOneOf(cc.R.Group.IgnoreRoles, ms.Roles) { + if common.ContainsInt64SliceOneOf(cc.R.Group.IgnoreRoles, ms.Member.Roles) { return false } - if len(cc.R.Group.WhitelistRoles) > 0 && !common.ContainsInt64SliceOneOf(cc.R.Group.WhitelistRoles, ms.Roles) { + if len(cc.R.Group.WhitelistRoles) > 0 && !common.ContainsInt64SliceOneOf(cc.R.Group.WhitelistRoles, ms.Member.Roles) { return false } } @@ -268,7 +268,7 @@ func CmdRunsForUser(cc *models.CustomCommand, ms *dstate.MemberState) bool { } for _, v := range cc.Roles { - if common.ContainsInt64Slice(ms.Roles, v) { + if common.ContainsInt64Slice(ms.Member.Roles, v) { if cc.RolesWhitelistMode { return true } diff --git a/customcommands/tmplextensions.go b/customcommands/tmplextensions.go index 24c9ddbe6b..96e7c543ca 100644 --- a/customcommands/tmplextensions.go +++ b/customcommands/tmplextensions.go @@ -7,9 +7,9 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -145,7 +145,7 @@ func (pa *ParsedArgs) Get(index int) interface{} { } c := i.(*dstate.ChannelState) - return templates.CtxChannelFromCSLocked(c) + return templates.CtxChannelFromCS(c) case *commands.MemberArg: i := pa.parsed[index].Value if i == nil { @@ -153,7 +153,7 @@ func (pa *ParsedArgs) Get(index int) interface{} { } m := i.(*dstate.MemberState) - return m.DGoCopy() + return m.DgoMember() case *commands.RoleArg: i := pa.parsed[index].Value if i == nil { @@ -188,7 +188,7 @@ func tmplRunCC(ctx *templates.Context) interface{} { return "", errors.New("Unknown channel") } - cs := ctx.GS.Channel(true, channelID) + cs := ctx.GS.GetChannel(channelID) if cs == nil { return "", errors.New("Channel not in state") } @@ -265,7 +265,7 @@ func tmplScheduleUniqueCC(ctx *templates.Context) interface{} { return "", errors.New("Unknown channel") } - cs := ctx.GS.Channel(true, channelID) + cs := ctx.GS.GetChannel(channelID) if cs == nil { return "", errors.New("Channel not in state") } @@ -496,7 +496,7 @@ func tmplDBDel(ctx *templates.Context) interface{} { return "", templates.ErrTooManyCalls } - ctx.GS.UserCacheDel(CacheKeyDBLimits) + cachedDBLimits.Delete(ctx.GS.ID) keyStr := limitString(templates.ToString(key), 256) _, err := models.TemplatesUserDatabases(qm.Where("guild_id = ? AND user_id = ? AND key = ?", ctx.GS.ID, userID, keyStr)).DeleteAll(context.Background(), common.PQ) @@ -511,7 +511,7 @@ func tmplDBDelById(ctx *templates.Context) interface{} { return "", templates.ErrTooManyCalls } - ctx.GS.UserCacheDel(CacheKeyDBLimits) + cachedDBLimits.Delete(ctx.GS.ID) _, err := models.TemplatesUserDatabases(qm.Where("guild_id = ? AND user_id = ? AND id = ?", ctx.GS.ID, userID, id)).DeleteAll(context.Background(), common.PQ) @@ -599,15 +599,13 @@ func serializeValue(v interface{}) ([]byte, error) { } // returns true if were above db limit for the specified guild -func CheckGuildDBLimit(gs *dstate.GuildState) (bool, error) { +func CheckGuildDBLimit(gs *dstate.GuildSet) (bool, error) { limitMuliplier := 1 if isPremium, _ := premium.IsGuildPremium(gs.ID); isPremium { limitMuliplier = 10 } - gs.RLock() - limit := gs.Guild.MemberCount * 50 * limitMuliplier - gs.RUnlock() + limit := gs.MemberCount * 50 * int64(limitMuliplier) curValues, err := cacheCheckDBLimit(gs) if err != nil { @@ -622,8 +620,10 @@ func getGuildCCDBNumValues(guildID int64) (int64, error) { return count, err } -func cacheCheckDBLimit(gs *dstate.GuildState) (int64, error) { - v, err := gs.UserCacheFetch(CacheKeyDBLimits, func() (interface{}, error) { +var cachedDBLimits = common.CacheSet.RegisterSlot("custom_commands_db_limits", nil, int64(0)) + +func cacheCheckDBLimit(gs *dstate.GuildSet) (int64, error) { + v, err := cachedDBLimits.GetCustomFetch(gs.ID, func(key interface{}) (interface{}, error) { n, err := getGuildCCDBNumValues(gs.ID) return n, err }) @@ -642,7 +642,7 @@ func limitString(s string, l int) string { } lastValidLoc := 0 - for i, _ := range s { + for i := range s { if i > l { break } @@ -750,7 +750,7 @@ func newDecoder(buf *bytes.Buffer) *msgpack.Decoder { return dec } -func tmplResultSetToLightDBEntries(ctx *templates.Context, gs *dstate.GuildState, rs []*models.TemplatesUserDatabase) []*LightDBEntry { +func tmplResultSetToLightDBEntries(ctx *templates.Context, gs *dstate.GuildSet, rs []*models.TemplatesUserDatabase) []*LightDBEntry { // convert them into lightdb entries and decode their values entries := make([]*LightDBEntry, 0, len(rs)) for _, v := range rs { @@ -781,9 +781,8 @@ func tmplResultSetToLightDBEntries(ctx *templates.Context, gs *dstate.GuildState return entries } - cop := member.DGoUser() for _, v := range entries { - v.User = *cop + v.User = member.User } return entries @@ -797,8 +796,8 @@ func tmplResultSetToLightDBEntries(ctx *templates.Context, gs *dstate.GuildState for _, v := range entries { for _, m := range members { - if m.ID == v.UserID { - v.User = *(m.DGoUser()) + if m.User.ID == v.UserID { + v.User = m.User break } } diff --git a/customcommands/web.go b/customcommands/web.go index d735d295b8..075c5e9a46 100644 --- a/customcommands/web.go +++ b/customcommands/web.go @@ -242,7 +242,7 @@ func handleNewCommand(w http.ResponseWriter, r *http.Request) (web.TemplateData, go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyNewCommand, &cplogs.Param{Type: cplogs.ParamTypeInt, Value: dbModel.LocalID})) - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, nil } @@ -308,7 +308,7 @@ func handleUpdateCommand(w http.ResponseWriter, r *http.Request) (web.TemplateDa go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedCommand, &cplogs.Param{Type: cplogs.ParamTypeInt, Value: dbModel.LocalID})) - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, err } @@ -340,7 +340,7 @@ func handleDeleteCommand(w http.ResponseWriter, r *http.Request) (web.TemplateDa err = DelNextRunEvent(cmd.GuildID, cmd.LocalID) featureflags.MarkGuildDirty(activeGuild.ID) - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, err } @@ -439,7 +439,7 @@ func handleNewGroup(w http.ResponseWriter, r *http.Request) (web.TemplateData, e templateData["CurrentGroupID"] = dbModel.ID - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, nil } @@ -466,7 +466,7 @@ func handleUpdateGroup(w http.ResponseWriter, r *http.Request) (web.TemplateData go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedGroup, &cplogs.Param{Type: cplogs.ParamTypeString, Value: model.Name})) } - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, err } @@ -488,7 +488,7 @@ func handleDeleteGroup(w http.ResponseWriter, r *http.Request) (web.TemplateData go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyRemovedGroup, &cplogs.Param{Type: cplogs.ParamTypeInt, Value: id})) } - common.LogIgnoreError(pubsub.Publish("custom_commands_clear_cache", activeGuild.ID, nil), "failed creating pubsub cache eviction event", web.CtxLogger(ctx).Data) + pubsub.EvictCacheSet(cachedCommandsMessage, activeGuild.ID) return templateData, err } diff --git a/go.mod b/go.mod index e2cb2464fe..8eabe29577 100644 --- a/go.mod +++ b/go.mod @@ -26,11 +26,11 @@ require ( github.com/jmoiron/sqlx v1.2.0 github.com/jonas747/cardsagainstdiscord v1.1.1-0.20200617121715-2c969a853358 github.com/jonas747/dca v0.0.0-20190317094138-10e959e9d3e8 - github.com/jonas747/dcmd/v2 v2.0.4 + github.com/jonas747/dcmd/v3 v3.0.0-alpha4 github.com/jonas747/dice v0.0.0-20170619144252-7735f6ee7b69 - github.com/jonas747/discordgo v1.6.2 - github.com/jonas747/dshardorchestrator/v2 v2.0.1 - github.com/jonas747/dstate/v2 v2.0.5 + github.com/jonas747/discordgo v1.5.7 + github.com/jonas747/dshardorchestrator/v2 v2.0.2 + github.com/jonas747/dstate/v3 v3.0.0-alpha8 github.com/jonas747/dutil v0.0.3 github.com/jonas747/go-reddit v0.1.2 github.com/jonas747/go-twitter v0.0.0-20200706234916-1d17060b92bc @@ -82,4 +82,8 @@ require ( // replace github.com/jonas747/discordgo => ../discordgo -// replace github.com/jonas747/dcmd/v2 => ../dcmd +// replace github.com/jonas747/dcmd/v3 => ../dcmd + +// replace github.com/jonas747/dstate/v3 => ../dstate + +// replace github.com/jonas747/dshardorchestrator/v2 => ../dshardorchestrator diff --git a/go.sum b/go.sum index 36eb64b5c4..88e8114d19 100644 --- a/go.sum +++ b/go.sum @@ -94,7 +94,6 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bwmarrin/discordgo v0.20.1/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= -github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= @@ -335,11 +334,10 @@ github.com/jonas747/cardsagainstdiscord v1.1.1-0.20200617121715-2c969a853358 h1: github.com/jonas747/cardsagainstdiscord v1.1.1-0.20200617121715-2c969a853358/go.mod h1:y11G2XTZehiSHDdy8w/8IVQsY3ilvdLmNshuo1TSEek= github.com/jonas747/dca v0.0.0-20190317094138-10e959e9d3e8 h1:k/3mvr7ImDZ8Ig/qcLVnvNSW99wlkbVyPDv4erwSQPQ= github.com/jonas747/dca v0.0.0-20190317094138-10e959e9d3e8/go.mod h1:rxjYX9OJU81unMxQDHChU/lAiOhlY9MV+faPX/NmwLk= +github.com/jonas747/dcmd v1.1.0 h1:zn5qVNxyz08cgAdjvAGoG2WwY3GbxnzImRynV0Atja0= github.com/jonas747/dcmd v1.1.0/go.mod h1:pfeU2I/OynOj5NXiyavaMb4MqKq7rbjHj1hPe44YJ1A= -github.com/jonas747/dcmd v1.2.4 h1:+3Uxk7yGxuVrVqK4CAXK9ASK7yHzUE7accU5lESHb+A= -github.com/jonas747/dcmd v1.2.4/go.mod h1:dOpx8IdbmRPYsv9f4wFQXRxGYrA2lTBmSA4xM9fARiI= -github.com/jonas747/dcmd/v2 v2.0.4 h1:q7yLLO8P9N3It+sZqKwQXj0p+nfZRL1Ueqh/neJRJz0= -github.com/jonas747/dcmd/v2 v2.0.4/go.mod h1:+0HJW5l5pvz90uklMbQqnzNMzQx8+ihvJKvh9qDrawo= +github.com/jonas747/dcmd/v3 v3.0.0-alpha4 h1:NJjupKNr2dwAIsRYS33BMIl1i1KdXr6cGHCVYjKuRwM= +github.com/jonas747/dcmd/v3 v3.0.0-alpha4/go.mod h1:uSdqrXIL5BFtussX6EhRQLFRNRnZWyv5c8731OeAaKk= github.com/jonas747/dice v0.0.0-20170619144252-7735f6ee7b69 h1:npnwg0oLHdEZs4KXXk2092gEuHx2jePVyrNIJXMsCBw= github.com/jonas747/dice v0.0.0-20170619144252-7735f6ee7b69/go.mod h1:m242ZgUE5Rw5Pmc35yiSSyGAxXCiO/QWJZ244MDpanI= github.com/jonas747/discordgo v1.1.1/go.mod h1:Mq9l7/Kn7L8Np5p//vuxKfnZohrX70XYx9cGoa+19J8= @@ -347,20 +345,18 @@ github.com/jonas747/discordgo v1.1.7/go.mod h1:Mq9l7/Kn7L8Np5p//vuxKfnZohrX70XYx github.com/jonas747/discordgo v1.1.9/go.mod h1:Mq9l7/Kn7L8Np5p//vuxKfnZohrX70XYx9cGoa+19J8= github.com/jonas747/discordgo v1.5.1/go.mod h1:AtjBAWqqaBsSj2dgxKaeQ3Gy8GFlDRg+PZiFcsqav00= github.com/jonas747/discordgo v1.5.5/go.mod h1:AtjBAWqqaBsSj2dgxKaeQ3Gy8GFlDRg+PZiFcsqav00= -github.com/jonas747/discordgo v1.6.2 h1:M4JxiuNGie0pGW99DkSd83qXyqakbCOJJ/tvyr7VOuY= -github.com/jonas747/discordgo v1.6.2/go.mod h1:AtjBAWqqaBsSj2dgxKaeQ3Gy8GFlDRg+PZiFcsqav00= +github.com/jonas747/discordgo v1.5.7 h1:xSCtppOnF43SwichY3sEd8ZWYoQZYXubWfdFUGXA938= +github.com/jonas747/discordgo v1.5.7/go.mod h1:AtjBAWqqaBsSj2dgxKaeQ3Gy8GFlDRg+PZiFcsqav00= github.com/jonas747/dshardorchestrator v0.1.0 h1:3/v+lx0gLupm7/VZr7wbfvUGhpxvuNC5JHaS+OnWjCc= github.com/jonas747/dshardorchestrator v0.1.0/go.mod h1:ZlA6h9kj2H5q2aQkZgBiROp8zpIjIUMJQ3SOpVdWYfQ= -github.com/jonas747/dshardorchestrator/v2 v2.0.1 h1:80ymqyYNTucNPe0oEvpiGWeZiJdiRsdmUDXiiefpo68= -github.com/jonas747/dshardorchestrator/v2 v2.0.1/go.mod h1:wrqNpMI8eZ2mtiQU3ZL1mdPNt43demnxzR+uvbWQcTU= +github.com/jonas747/dshardorchestrator/v2 v2.0.2 h1:O7YjxZsuB2jmtulJxESpjYw6GBF3HYpe6eMU1sfFfU0= +github.com/jonas747/dshardorchestrator/v2 v2.0.2/go.mod h1:wrqNpMI8eZ2mtiQU3ZL1mdPNt43demnxzR+uvbWQcTU= github.com/jonas747/dstate v0.0.0-20190905110424-f2ed8c6c957d/go.mod h1:5xeu+zy7jwCfnCA+A9CaWOV4UhQzGHvBVZ381l+jarM= +github.com/jonas747/dstate v1.0.4 h1:TS1YcPJelsVOqgU8u+SKkQevEMi0klG9Agugj+TE8+0= github.com/jonas747/dstate v1.0.4/go.mod h1:6EQjQnyxbdNzQJS9wANzHVzKCyTO+lsmH6t3aqZRNI8= -github.com/jonas747/dstate v1.0.5 h1:HgHXWurXFtQRAp+3fTIDW/ukHlKsEYe0lJjbC1mQoKc= -github.com/jonas747/dstate v1.0.5/go.mod h1:6EQjQnyxbdNzQJS9wANzHVzKCyTO+lsmH6t3aqZRNI8= -github.com/jonas747/dstate/v2 v2.0.0/go.mod h1:xuGgbuvq9CgnxYrHfNzT9Zvc8bVRCZHMnBekL0c79fY= -github.com/jonas747/dstate/v2 v2.0.2/go.mod h1:xuGgbuvq9CgnxYrHfNzT9Zvc8bVRCZHMnBekL0c79fY= -github.com/jonas747/dstate/v2 v2.0.5 h1:tbjlccTmKbonxIQnMctUULXbSAIj3MFFvS45fStRHbI= -github.com/jonas747/dstate/v2 v2.0.5/go.mod h1:xuGgbuvq9CgnxYrHfNzT9Zvc8bVRCZHMnBekL0c79fY= +github.com/jonas747/dstate/v3 v3.0.0-alpha7/go.mod h1:ZLma2mC/JA8G1Snz80Az6HWq7wXxzDqq+71DIPx9wBc= +github.com/jonas747/dstate/v3 v3.0.0-alpha8 h1:sWFQ8Kp3jYdJi+UzgF50A6Q6TzwvlrO+wPG52WdzD9E= +github.com/jonas747/dstate/v3 v3.0.0-alpha8/go.mod h1:ZLma2mC/JA8G1Snz80Az6HWq7wXxzDqq+71DIPx9wBc= github.com/jonas747/dutil v0.0.2/go.mod h1:FwfIRC0ShAEHhwUJ6JppfciiFrfOA/cAoyCECsfib8c= github.com/jonas747/dutil v0.0.3 h1:Qe4Qls2sBjAvBOqregFLyDuWG/gonK8BiqT9DHRhIg0= github.com/jonas747/dutil v0.0.3/go.mod h1:oMJ++hvPrAb7Kl2aAHntvjiMXQWfja5t8ZvX13RMbuE= @@ -693,7 +689,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI= golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= diff --git a/logs/logs.go b/logs/logs.go index f5a6eb8d1b..37a9ec9990 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -84,18 +84,22 @@ func CreateChannelLog(ctx context.Context, config *models.GuildLoggingConfig, gu count = 300 } + gs := bot.State.GetGuild(guildID) + if gs == nil { + return nil, bot.ErrGuildNotFound + } + // Make a light copy of the channel - channel := bot.State.ChannelCopy(true, channelID) + channel := gs.GetChannel(channelID) if channel == nil { return nil, errors.New("Unknown channel") } - msgs, err := bot.GetMessages(channel.ID, count, true) + msgs, err := bot.GetMessages(guildID, channel.ID, count, true) if err != nil { return nil, err } - logMsgs := make([]*models.Messages2, 0, len(msgs)) logIds := make([]int64, 0, len(msgs)) tx, err := common.PQ.Begin() @@ -121,8 +125,8 @@ func CreateChannelLog(ctx context.Context, config *models.GuildLoggingConfig, gu GuildID: guildID, Content: body, - CreatedAt: v.ParsedCreated, - UpdatedAt: v.ParsedCreated, + CreatedAt: v.ParsedCreatedAt, + UpdatedAt: v.ParsedCreatedAt, AuthorUsername: v.Author.Username + "#" + v.Author.Discriminator, AuthorID: v.Author.ID, @@ -135,18 +139,17 @@ func CreateChannelLog(ctx context.Context, config *models.GuildLoggingConfig, gu return nil, errors.WrapIf(err, "message.insert") } - logMsgs = append(logMsgs, messageModel) logIds = append(logIds, v.ID) } - id, err := common.GenLocalIncrID(channel.Guild.ID, "message_logs") + id, err := common.GenLocalIncrID(guildID, "message_logs") if err != nil { tx.Rollback() return nil, errors.WrapIf(err, "log.gen_id") } log := &models.MessageLogs2{ - GuildID: channel.Guild.ID, + GuildID: guildID, ID: int(id), LegacyID: 0, diff --git a/logs/plugin_bot.go b/logs/plugin_bot.go index 677c941c05..10f8787bdc 100644 --- a/logs/plugin_bot.go +++ b/logs/plugin_bot.go @@ -11,9 +11,9 @@ import ( "github.com/jonas747/yagpdb/bot/paginatedmessages" "github.com/jonas747/yagpdb/common/config" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" @@ -49,7 +49,7 @@ var cmdLogs = &commands.YAGCommand{ Description: "Creates a log of the last messages in the current channel.", LongDescription: "This includes deleted messages within an hour (or 12 hours for premium servers)", Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "Count", Default: 100, Type: &dcmd.IntArg{Min: 2, Max: 250}}, + {Name: "Count", Default: 100, Type: &dcmd.IntArg{Min: 2, Max: 250}}, }, SlashCommandEnabled: true, DefaultEnabled: false, @@ -91,25 +91,26 @@ var cmdWhois = &commands.YAGCommand{ member = parsed.Args[0].Value.(*dstate.MemberState) } else { member = parsed.GuildData.MS - if sm := parsed.GuildData.GS.MemberCopy(true, member.ID); sm != nil { + if sm := bot.State.GetMember(parsed.GuildData.GS.ID, member.User.ID); sm != nil { // Prefer state member over the one provided in the message, since it may have presence data member = sm } } nick := "" - if member.Nick != "" { - nick = " (" + member.Nick + ")" + if member.Member.Nick != "" { + nick = " (" + member.Member.Nick + ")" } joinedAtStr := "" joinedAtDurStr := "" - if !member.MemberSet { + if member.Member == nil { joinedAtStr = "Couldn't find out" joinedAtDurStr = "Couldn't find out" } else { - joinedAtStr = member.JoinedAt.UTC().Format(time.RFC822) - dur := time.Since(member.JoinedAt) + parsedJoinedAt, _ := member.Member.JoinedAt.Parse() + joinedAtStr = parsedJoinedAt.UTC().Format(time.RFC822) + dur := time.Since(parsedJoinedAt) joinedAtDurStr = common.HumanizeDuration(common.DurationPrecisionHours, dur) } @@ -117,7 +118,7 @@ var cmdWhois = &commands.YAGCommand{ joinedAtDurStr = "Less than an hour ago" } - t := bot.SnowflakeToTime(member.ID) + t := bot.SnowflakeToTime(member.User.ID) createdDurStr := common.HumanizeDuration(common.DurationPrecisionHours, time.Since(t)) if createdDurStr == "" { createdDurStr = "Less than an hour ago" @@ -125,66 +126,67 @@ var cmdWhois = &commands.YAGCommand{ var memberStatus string state := [6]string{"Playing", "Streaming", "Listening", "Watching", "Custom", "Competing"} - if !member.PresenceSet || member.PresenceGame == nil { + if member.Presence == nil || member.Presence.Game == nil { memberStatus = "Has no active status, is invisible/offline or is not in the bot's cache." } else { - if member.PresenceGame.Type == 4 { - memberStatus = fmt.Sprintf("%s: %s", member.PresenceGame.Name, member.PresenceGame.State) + if member.Presence.Game.Type == 4 { + memberStatus = fmt.Sprintf("%s: %s", member.Presence.Game.Name, member.Presence.Game.State) } else { presenceName := "Unknown" - if member.PresenceGame.Type >= 0 && len(state) > int(member.PresenceGame.Type) { - presenceName = state[member.PresenceGame.Type] + if member.Presence.Game.Type >= 0 && len(state) > int(member.Presence.Game.Type) { + presenceName = state[member.Presence.Game.Type] } - memberStatus = fmt.Sprintf("%s: %s", presenceName, member.PresenceGame.Name) + memberStatus = fmt.Sprintf("%s: %s", presenceName, member.Presence.Game.Name) } } embed := &discordgo.MessageEmbed{ - Title: fmt.Sprintf("%s#%04d%s", member.Username, member.Discriminator, nick), + Title: fmt.Sprintf("%s#%s%s", member.User.Username, member.User.Discriminator, nick), Fields: []*discordgo.MessageEmbedField{ - &discordgo.MessageEmbedField{ + { Name: "ID", - Value: discordgo.StrID(member.ID), + Value: discordgo.StrID(member.User.ID), Inline: true, }, - &discordgo.MessageEmbedField{ + { Name: "Avatar", - Value: "[Link](" + discordgo.EndpointUserAvatar(member.ID, member.StrAvatar()) + ")", + Value: "[Link](" + discordgo.EndpointUserAvatar(member.User.ID, member.User.Avatar) + ")", Inline: true, }, - &discordgo.MessageEmbedField{ + { Name: "Account Created", Value: t.UTC().Format(time.RFC822), Inline: true, }, - &discordgo.MessageEmbedField{ + { Name: "Account Age", Value: createdDurStr, Inline: true, }, - &discordgo.MessageEmbedField{ + { Name: "Joined Server At", Value: joinedAtStr, Inline: true, - }, &discordgo.MessageEmbedField{ + }, + { Name: "Join Server Age", Value: joinedAtDurStr, Inline: true, }, - &discordgo.MessageEmbedField{ + { Name: "Status", Value: memberStatus, Inline: true, }, }, Thumbnail: &discordgo.MessageEmbedThumbnail{ - URL: discordgo.EndpointUserAvatar(member.ID, member.StrAvatar()), + URL: discordgo.EndpointUserAvatar(member.User.ID, member.User.Avatar), }, } if config.UsernameLoggingEnabled.Bool { - usernames, err := GetUsernames(parsed.Context(), member.ID, 5, 0) + usernames, err := GetUsernames(parsed.Context(), member.User.ID, 5, 0) if err != nil { return err, err } @@ -208,7 +210,7 @@ var cmdWhois = &commands.YAGCommand{ if config.NicknameLoggingEnabled.Bool { - nicknames, err := GetNicknames(parsed.Context(), member.ID, parsed.GuildData.GS.ID, 5, 0) + nicknames, err := GetNicknames(parsed.Context(), member.User.ID, parsed.GuildData.GS.ID, 5, 0) if err != nil { return err, err } @@ -416,21 +418,18 @@ func HandlePresenceUpdate(evt *eventsystem.EventData) { pu := evt.PresenceUpdate() gs := evt.GS - gs.RLock() - defer gs.RUnlock() - - ms := gs.Member(false, pu.User.ID) - if ms == nil || !ms.PresenceSet || !ms.MemberSet { + ms := bot.State.GetMember(gs.ID, pu.User.ID) + if ms == nil || ms.Presence == nil || ms.Member == nil { queueEvt(pu) return } - if pu.User.Username != "" && pu.User.Username != ms.Username { + if pu.User.Username != "" && pu.User.Username != ms.User.Username { queueEvt(pu) return } - if pu.Nick != ms.Nick { + if pu.Nick != ms.Member.Nick { queueEvt(pu) return } @@ -875,15 +874,15 @@ func EvtProcesserGCs() { } } -const CacheKeyConfig bot.GSCacheKey = "logs_config" +var configCache = common.CacheSet.RegisterSlot("logs_config", nil, int64(0)) func GetConfigCached(exec boil.ContextExecutor, gID int64) (*models.GuildLoggingConfig, error) { - gs := bot.State.Guild(true, gID) + gs := bot.State.GetGuild(gID) if gs == nil { return GetConfig(exec, context.Background(), gID) } - v, err := gs.UserCacheFetch(CacheKeyConfig, func() (interface{}, error) { + v, err := configCache.GetCustomFetch(gs.ID, func(key interface{}) (interface{}, error) { conf, err := GetConfig(exec, context.Background(), gID) return conf, err }) diff --git a/logs/web.go b/logs/web.go index 11ef2579fc..fbf1a70082 100644 --- a/logs/web.go +++ b/logs/web.go @@ -10,10 +10,10 @@ import ( "strings" "github.com/jonas747/discordgo" - "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/cplogs" + "github.com/jonas747/yagpdb/common/pubsub" "github.com/jonas747/yagpdb/logs/models" "github.com/jonas747/yagpdb/web" "github.com/volatiletech/null/v8" @@ -168,7 +168,7 @@ func HandleLogsCPSaveGeneral(w http.ResponseWriter, r *http.Request) (web.Templa err := config.UpsertG(ctx, true, []string{"guild_id"}, boil.Infer(), boil.Infer()) if err == nil { - bot.EvictGSCache(g.ID, CacheKeyConfig) + pubsub.EvictCacheSet(configCache, g.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKeyUpdatedSettings)) } @@ -181,7 +181,7 @@ func HandleLogsCPDelete(w http.ResponseWriter, r *http.Request) (web.TemplateDat data := ctx.Value(common.ContextKeyParsedForm).(*DeleteData) if data.ID == 0 { - return tmpl, errors.New("ID is blank!") + return tmpl, errors.New("id is blank") } _, err := models.MessageLogs2s( diff --git a/moderation/commands.go b/moderation/commands.go index 40b9c8c0bf..48d24ce4fe 100644 --- a/moderation/commands.go +++ b/moderation/commands.go @@ -9,9 +9,9 @@ import ( "emperror.dev/errors" "github.com/jinzhu/gorm" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/paginatedmessages" @@ -31,15 +31,13 @@ func MBaseCmd(cmdData *dcmd.Data, targetID int64) (config *Config, targetUser *d if targetMember != nil { gs := cmdData.GuildData.GS - gs.RLock() above := bot.IsMemberAbove(gs, cmdData.GuildData.MS, targetMember) - gs.RUnlock() if !above { - return config, targetMember.DGoUser(), commands.NewUserError("Can't use moderation commands on users ranked the same or higher than you") + return config, &targetMember.User, commands.NewUserError("Can't use moderation commands on users ranked the same or higher than you") } - return config, targetMember.DGoUser(), nil + return config, &targetMember.User, nil } } @@ -72,7 +70,7 @@ func MBaseCmdSecond(cmdData *dcmd.Data, reason string, reasonArgOptional bool, n permsMet := false if len(additionalPermRoles) > 0 { // Check if the user has one of the required roles - for _, r := range member.Roles { + for _, r := range member.Member.Roles { if common.ContainsInt64Slice(additionalPermRoles, r) { permsMet = true break @@ -82,7 +80,7 @@ func MBaseCmdSecond(cmdData *dcmd.Data, reason string, reasonArgOptional bool, n if !permsMet && neededPerm != 0 { // Fallback to legacy permissions - hasPerms, err := bot.AdminOrPermMS(cmdData.ChannelID, member, neededPerm) + hasPerms, err := bot.AdminOrPermMS(cmdData.GuildData.GS.ID, cmdData.ChannelID, member, neededPerm) if err != nil || !hasPerms { return oreason, commands.NewUserErrorf("The **%s** command requires the **%s** permission in this channel or additional roles set up by admins, you don't have it. (if you do contact bot support)", cmdName, common.StringPerms[neededPerm]) } @@ -384,7 +382,7 @@ var ModerationCommands = []*commands.YAGCommand{ return nil, err } - target := temp.DGoUser() + target := temp.User if target.ID == parsed.Author.ID { return "You can't report yourself, silly.", nil @@ -464,7 +462,7 @@ var ModerationCommands = []*commands.YAGCommand{ return "Failed fetching bot member to check permissions", nil } - canClear, err := bot.AdminOrPermMS(parsed.ChannelID, botMember, discordgo.PermissionManageMessages) + canClear, err := bot.AdminOrPermMS(parsed.GuildData.GS.ID, parsed.ChannelID, botMember, discordgo.PermissionManageMessages) if err != nil || !canClear { return "I need the `Manage Messages` permission to be able to clear messages", nil } @@ -559,7 +557,7 @@ var ModerationCommands = []*commands.YAGCommand{ // Wait a second so the client dosen't gltich out time.Sleep(time.Second) - numDeleted, err := AdvancedDeleteMessages(parsed.ChannelID, userFilter, re, invertRegexMatch, toID, ma, minAge, pe, attachments, num, limitFetch) + numDeleted, err := AdvancedDeleteMessages(parsed.GuildData.GS.ID, parsed.ChannelID, userFilter, re, invertRegexMatch, toID, ma, minAge, pe, attachments, num, limitFetch) return dcmd.NewTemporaryResponse(time.Second*5, fmt.Sprintf("Deleted %d message(s)! :')", numDeleted), true), err }, }, @@ -899,17 +897,14 @@ var ModerationCommands = []*commands.YAGCommand{ return "Couldn't find the specified role", nil } - parsed.GuildData.GS.RLock() if !bot.IsMemberAboveRole(parsed.GuildData.GS, parsed.GuildData.MS, role) { - parsed.GuildData.GS.RUnlock() return "Can't give roles above you", nil } - parsed.GuildData.GS.RUnlock() dur := parsed.Switches["d"].Value.(time.Duration) // no point if the user has the role and is not updating the expiracy - if common.ContainsInt64Slice(member.Roles, role.ID) && dur <= 0 { + if common.ContainsInt64Slice(member.Member.Roles, role.ID) && dur <= 0 { return "That user already has that role", nil } @@ -977,12 +972,9 @@ var ModerationCommands = []*commands.YAGCommand{ return "Couldn't find the specified role", nil } - parsed.GuildData.GS.RLock() if !bot.IsMemberAboveRole(parsed.GuildData.GS, parsed.GuildData.MS, role) { - parsed.GuildData.GS.RUnlock() return "Can't remove roles above you", nil } - parsed.GuildData.GS.RUnlock() err = common.RemoveRoleDS(member, role.ID) if err != nil { @@ -1003,7 +995,7 @@ var ModerationCommands = []*commands.YAGCommand{ }, } -func AdvancedDeleteMessages(channelID int64, filterUser int64, regex string, invertRegexMatch bool, toID int64, maxAge time.Duration, minAge time.Duration, pinFilterEnable bool, attachmentFilterEnable bool, deleteNum, fetchNum int) (int, error) { +func AdvancedDeleteMessages(guildID, channelID int64, filterUser int64, regex string, invertRegexMatch bool, toID int64, maxAge time.Duration, minAge time.Duration, pinFilterEnable bool, attachmentFilterEnable bool, deleteNum, fetchNum int) (int, error) { var compiledRegex *regexp.Regexp if regex != "" { // Start by compiling the regex @@ -1027,20 +1019,20 @@ func AdvancedDeleteMessages(channelID int64, filterUser int64, regex string, inv } } - msgs, err := bot.GetMessages(channelID, fetchNum, false) + msgs, err := bot.GetMessages(guildID, channelID, fetchNum, false) if err != nil { return 0, err } toDelete := make([]int64, 0) now := time.Now() - for i := len(msgs) - 1; i >= 0; i-- { + for i := 0; i < len(msgs); i++ { if filterUser != 0 && msgs[i].Author.ID != filterUser { continue } // Can only bulk delete messages up to 2 weeks (but add 1 minute buffer account for time sync issues and other smallies) - if now.Sub(msgs[i].ParsedCreated) > (time.Hour*24*14)-time.Minute { + if now.Sub(msgs[i].ParsedCreatedAt) > (time.Hour*24*14)-time.Minute { continue } @@ -1056,12 +1048,12 @@ func AdvancedDeleteMessages(channelID int64, filterUser int64, regex string, inv } // Check max age - if maxAge != 0 && now.Sub(msgs[i].ParsedCreated) > maxAge { + if maxAge != 0 && now.Sub(msgs[i].ParsedCreatedAt) > maxAge { continue } // Check min age - if minAge != 0 && now.Sub(msgs[i].ParsedCreated) < minAge { + if minAge != 0 && now.Sub(msgs[i].ParsedCreatedAt) < minAge { continue } @@ -1105,26 +1097,23 @@ func AdvancedDeleteMessages(channelID int64, filterUser int64, regex string, inv return len(toDelete), err } -func FindRole(gs *dstate.GuildState, roleS string) *discordgo.Role { +func FindRole(gs *dstate.GuildSet, roleS string) *discordgo.Role { parsedNumber, parseErr := strconv.ParseInt(roleS, 10, 64) - gs.RLock() - defer gs.RUnlock() if parseErr == nil { - // was a number, try looking by id - r := gs.RoleCopy(false, parsedNumber) + r := gs.GetRole(parsedNumber) if r != nil { return r } } // otherwise search by name - for _, v := range gs.Guild.Roles { + for _, v := range gs.Roles { trimmed := strings.TrimSpace(v.Name) if strings.EqualFold(trimmed, roleS) { - return v + return &v } } diff --git a/moderation/plugin_bot.go b/moderation/plugin_bot.go index 65920d6e13..18cabad026 100644 --- a/moderation/plugin_bot.go +++ b/moderation/plugin_bot.go @@ -10,7 +10,7 @@ import ( "github.com/jinzhu/gorm" "github.com/jonas747/discordgo" "github.com/jonas747/dshardorchestrator/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" @@ -72,7 +72,7 @@ type ScheduledUnbanData struct { func (p *Plugin) ShardMigrationReceive(evt dshardorchestrator.EventType, data interface{}) { if evt == bot.EvtGuildState { - gs := data.(*dstate.GuildState) + gs := data.(*dstate.GuildSet) go RefreshMuteOverrides(gs.ID, false) } } @@ -131,29 +131,22 @@ func RefreshMuteOverrides(guildID int64, createRole bool) { return } - guild := bot.State.Guild(true, guildID) + guild := bot.State.GetGuild(guildID) if guild == nil { return // Still starting up and haven't received the guild yet } - if guild.RoleCopy(true, config.IntMuteRole()) == nil { + if guild.GetRole(config.IntMuteRole()) == nil { return } - guild.RLock() - channelsCopy := make([]*discordgo.Channel, 0, len(guild.Channels)) for _, v := range guild.Channels { - channelsCopy = append(channelsCopy, v.DGoCopy()) - } - guild.RUnlock() - - for _, v := range channelsCopy { RefreshMuteOverrideForChannel(config, v) } } func createMuteRole(config *Config, guildID int64) (int64, error) { - guild := bot.State.Guild(true, guildID) + guild := bot.State.GetGuild(guildID) if guild == nil { return 0, errors.New("failed finding guild") } @@ -205,12 +198,12 @@ func HandleChannelCreateUpdate(evt *eventsystem.EventData) (retry bool, err erro return false, nil } - RefreshMuteOverrideForChannel(config, channel) + RefreshMuteOverrideForChannel(config, dstate.ChannelStateFromDgo(channel)) return false, nil } -func RefreshMuteOverrideForChannel(config *Config, channel *discordgo.Channel) { +func RefreshMuteOverrideForChannel(config *Config, channel dstate.ChannelState) { // Ignore the channel if common.ContainsInt64Slice(config.MuteIgnoreChannels, channel.ID) { return @@ -225,7 +218,7 @@ func RefreshMuteOverrideForChannel(config *Config, channel *discordgo.Channel) { // Check for existing override for _, v := range channel.PermissionOverwrites { if v.Type == "role" && v.ID == config.IntMuteRole() { - override = v + override = &v break } } @@ -290,7 +283,7 @@ func HandleGuildBanAddRemove(evt *eventsystem.EventData) { var i int common.RedisPool.Do(radix.Cmd(&i, "GET", RedisKeyUnbannedUser(guildID, user.ID))) if i > 0 { - // The bot was the one that performed the unban + // The bot was the one that performed the unban common.RedisPool.Do(radix.Cmd(nil, "DEL", RedisKeyUnbannedUser(guildID, user.ID))) if i == 2 { //Bot performed non-scheduled unban, don't make duplicate entries in the modlog @@ -461,7 +454,7 @@ func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) guild := evt.GS - role := guild.RoleCopy(true, config.IntMuteRole()) + role := guild.GetRole(config.IntMuteRole()) if role == nil { return false, nil // Probably deleted the mute role, do nothing then } @@ -580,7 +573,7 @@ func handleScheduledUnban(evt *seventsmodels.ScheduledEvent, data interface{}) ( guildID := evt.GuildID userID := unbanData.UserID - g := bot.State.Guild(true, guildID) + g := bot.State.GetGuild(guildID) if g == nil { logger.WithField("guild", guildID).Error("Unban scheduled for guild not in state") return false, nil diff --git a/moderation/punishments.go b/moderation/punishments.go index a3bb3b4758..a5fb01466b 100644 --- a/moderation/punishments.go +++ b/moderation/punishments.go @@ -9,7 +9,7 @@ import ( "emperror.dev/errors" "github.com/jinzhu/gorm" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" @@ -33,22 +33,16 @@ const ( {{if .Reason}}**Reason:** {{.Reason}}{{end}}` ) -func getMemberWithFallback(gs *dstate.GuildState, user *discordgo.User) (ms *dstate.MemberState, notFound bool) { +func getMemberWithFallback(gs *dstate.GuildSet, user *discordgo.User) (ms *dstate.MemberState, notFound bool) { ms, err := bot.GetMember(gs.ID, user.ID) if err != nil { // Fallback logger.WithError(err).WithField("guild", gs.ID).Info("Failed retrieving member") ms = &dstate.MemberState{ - ID: user.ID, - Guild: gs, - Username: user.Username, - Bot: user.Bot, + User: *user, + GuildID: gs.ID, } - parsedDiscrim, _ := strconv.ParseInt(user.Discriminator, 10, 32) - ms.Discriminator = int32(parsedDiscrim) - ms.ParseAvatar(user.Avatar) - return ms, true } @@ -78,7 +72,7 @@ func punish(config *Config, p Punishment, guildID int64, channel *dstate.Channel channelID = channel.ID } - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) member, memberNotFound := getMemberWithFallback(gs, user) if !memberNotFound { @@ -147,7 +141,7 @@ func punish(config *Config, p Punishment, guildID int64, channel *dstate.Channel return err } -func sendPunishDM(config *Config, dmMsg string, action ModlogAction, gs *dstate.GuildState, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, member *dstate.MemberState, duration time.Duration, reason string, warningID int) { +func sendPunishDM(config *Config, dmMsg string, action ModlogAction, gs *dstate.GuildSet, channel *dstate.ChannelState, message *discordgo.Message, author *discordgo.User, member *dstate.MemberState, duration time.Duration, reason string, warningID int) { if dmMsg == "" { dmMsg = DefaultDMMessage } @@ -181,7 +175,7 @@ func sendPunishDM(config *Config, dmMsg string, action ModlogAction, gs *dstate. } if strings.TrimSpace(executed) != "" { - err = bot.SendDM(member.ID, "**"+bot.GuildName(gs.ID)+":** "+executed) + err = bot.SendDM(member.User.ID, "**"+gs.Name+":** "+executed) if err != nil { logger.WithError(err).Error("failed sending punish DM") } @@ -204,24 +198,24 @@ func KickUser(config *Config, guildID int64, channel *dstate.ChannelState, messa } if channel != nil { - _, err = DeleteMessages(channel.ID, user.ID, 100, 100) + _, err = DeleteMessages(guildID, channel.ID, user.ID, 100, 100) } return err } -func DeleteMessages(channelID int64, filterUser int64, deleteNum, fetchNum int) (int, error) { - msgs, err := bot.GetMessages(channelID, fetchNum, false) +func DeleteMessages(guildID, channelID int64, filterUser int64, deleteNum, fetchNum int) (int, error) { + msgs, err := bot.GetMessages(guildID, channelID, fetchNum, false) if err != nil { return 0, err } toDelete := make([]int64, 0) now := time.Now() - for i := len(msgs) - 1; i >= 0; i-- { + for i := 0; i < len(msgs); i++ { if filterUser == 0 || msgs[i].Author.ID == filterUser { // Can only bulk delete messages up to 2 weeks (but add 1 minute buffer account for time sync issues and other smallies) - if now.Sub(msgs[i].ParsedCreated) > (time.Hour*24*14)-time.Minute { + if now.Sub(msgs[i].ParsedCreatedAt) > (time.Hour*24*14)-time.Minute { continue } @@ -356,12 +350,12 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch } // To avoid unexpected things from happening, make sure were only updating the mute of the player 1 place at a time - LockMute(member.ID) - defer UnlockMute(member.ID) + LockMute(member.User.ID) + defer UnlockMute(member.User.ID) // Look for existing mute currentMute := MuteModel{} - err = common.GORM.Where(&MuteModel{UserID: member.ID, GuildID: guildID}).First(¤tMute).Error + err = common.GORM.Where(&MuteModel{UserID: member.User.ID, GuildID: guildID}).First(¤tMute).Error alreadyMuted := err != gorm.ErrRecordNotFound if err != nil && err != gorm.ErrRecordNotFound { return common.ErrWithCaller(err) @@ -370,7 +364,7 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch // Insert/update the mute entry in the database if !alreadyMuted { currentMute = MuteModel{ - UserID: member.ID, + UserID: member.User.ID, GuildID: guildID, } } @@ -385,12 +379,12 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch } // no matter what, if were unmuting or muting, we wanna make sure we dont have duplicated unmute events - _, err = seventsmodels.ScheduledEvents(qm.Where("event_name='moderation_unmute' AND guild_id = ? AND (data->>'user_id')::bigint = ?", guildID, member.ID)).DeleteAll(context.Background(), common.PQ) + _, err = seventsmodels.ScheduledEvents(qm.Where("event_name='moderation_unmute' AND guild_id = ? AND (data->>'user_id')::bigint = ?", guildID, member.User.ID)).DeleteAll(context.Background(), common.PQ) common.LogIgnoreError(err, "[moderation] failed clearing unban events", nil) if mute { // Apply the roles to the user - removedRoles, err := AddMemberMuteRole(config, member.ID, member.Roles) + removedRoles, err := AddMemberMuteRole(config, member.User.ID, member.Member.Roles) if err != nil { return errors.WithMessage(err, "AddMemberMuteRole") } @@ -420,7 +414,7 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch if duration > 0 { err = scheduledevents2.ScheduleEvent("moderation_unmute", guildID, time.Now().Add(time.Minute*time.Duration(duration)), &ScheduledUnmuteData{ - UserID: member.ID, + UserID: member.User.ID, }) if err != nil { return errors.WithMessage(err, "failed scheduling unmute") @@ -428,14 +422,14 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch } } else { // Remove the mute role, and give back the role the bot took - err = RemoveMemberMuteRole(config, member.ID, member.Roles, currentMute) + err = RemoveMemberMuteRole(config, member.User.ID, member.Member.Roles, currentMute) if err != nil { return errors.WithMessage(err, "failed removing mute role") } if alreadyMuted { common.GORM.Delete(¤tMute) - common.RedisPool.Do(radix.Cmd(nil, "DEL", RedisKeyMutedUser(guildID, member.ID))) + common.RedisPool.Do(radix.Cmd(nil, "DEL", RedisKeyMutedUser(guildID, member.User.ID))) } } @@ -458,13 +452,13 @@ func MuteUnmuteUser(config *Config, mute bool, guildID int64, channel *dstate.Ch dmMsg = config.MuteMessage } - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) if gs != nil { sendPunishDM(config, dmMsg, action, gs, channel, message, author, member, time.Duration(duration)*time.Minute, reason, -1) } // Create the modlog entry - return CreateModlogEmbed(config, author, action, member.DGoUser(), reason, logLink) + return CreateModlogEmbed(config, author, action, &member.User, reason, logLink) } func AddMemberMuteRole(config *Config, id int64, currentRoles []int64) (removedRoles []int64, err error) { @@ -504,17 +498,9 @@ func RemoveMemberMuteRole(config *Config, id int64, currentRoles []int64, mute M func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []string { newMemberRoles := make([]string, 0) - gs := bot.State.Guild(true, config.GuildID) + gs := bot.State.GetGuild(config.GuildID) botState, err := bot.GetMember(gs.ID, common.BotUser.ID) - gs.RLock() - defer gs.RUnlock() - - guildRoles := make([]int64, len(gs.Guild.Roles)) - for k, e := range gs.Guild.Roles { - guildRoles[k] = e.ID - } - if err != nil || botState == nil { // We couldn't find the bot on state, so keep old behaviour for _, r := range currentRoles { if r != config.IntMuteRole() { @@ -523,7 +509,7 @@ func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []s } for _, r := range mute.RemovedRoles { - if !common.ContainsInt64Slice(currentRoles, r) && common.ContainsInt64Slice(guildRoles, r) { + if !common.ContainsInt64Slice(currentRoles, r) && gs.GetRole(r) != nil { newMemberRoles = append(newMemberRoles, strconv.FormatInt(r, 10)) } } @@ -540,7 +526,8 @@ func decideUnmuteRoles(config *Config, currentRoles []int64, mute MuteModel) []s } for _, v := range mute.RemovedRoles { - if !common.ContainsInt64Slice(currentRoles, v) && common.ContainsInt64Slice(guildRoles, v) && dutil.IsRoleAbove(yagHighest, gs.Role(false, v)) { + r := gs.GetRole(v) + if !common.ContainsInt64Slice(currentRoles, v) && r != nil && dutil.IsRoleAbove(yagHighest, r) { newMemberRoles = append(newMemberRoles, strconv.FormatInt(v, 10)) } } @@ -578,7 +565,7 @@ func WarnUser(config *Config, guildID int64, channel *dstate.ChannelState, msg * return common.ErrWithCaller(err) } - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) ms, _ := bot.GetMember(guildID, target.ID) if gs != nil && ms != nil { sendPunishDM(config, config.WarnMessage, MAWarned, gs, channel, msg, author, ms, -1, message, int(warning.ID)) diff --git a/moderation/topwarnings.go b/moderation/topwarnings.go index a104d69d54..c3bb92e09c 100644 --- a/moderation/topwarnings.go +++ b/moderation/topwarnings.go @@ -5,7 +5,7 @@ import ( "fmt" //"github.com/jonas747/discordgo" - //"github.com/jonas747/dstate/v2" + //"github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" ) diff --git a/notifications/plugin_bot.go b/notifications/plugin_bot.go index 6d7ffeedd2..a611ee6a2c 100644 --- a/notifications/plugin_bot.go +++ b/notifications/plugin_bot.go @@ -7,7 +7,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -39,9 +39,9 @@ func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error return } - gs := bot.State.Guild(true, evt.GuildID) - - ms := dstate.MSFromDGoMember(gs, evt.Member) + gs := evtData.GS + ms := dstate.MemberStateFromMember(evt.Member) + ms.GuildID = evt.GuildID // Beware of the pyramid and its curses if config.JoinDMEnabled && !evt.User.Bot { @@ -54,23 +54,22 @@ func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error logger.WithError(err).WithField("user", evt.User.ID).Error("Failed retrieving user channel") } else { thinCState := &dstate.ChannelState{ - Owner: gs, - Guild: gs, - ID: cid.ID, - Name: evt.User.Username, - Type: discordgo.ChannelTypeDM, + ID: cid.ID, + Name: evt.User.Username, + Type: discordgo.ChannelTypeDM, + GuildID: gs.ID, } go analytics.RecordActiveUnit(gs.ID, &Plugin{}, "posted_join_server_msg") - if sendTemplate(thinCState, config.JoinDMMsg, ms, "join dm", false, true) { + if sendTemplate(gs, thinCState, config.JoinDMMsg, ms, "join dm", false, true) { return true, nil } } } if config.JoinServerEnabled && len(config.JoinServerMsgs) > 0 { - channel := gs.Channel(true, config.JoinServerChannelInt()) + channel := gs.GetChannel(config.JoinServerChannelInt()) if channel == nil { return } @@ -78,7 +77,7 @@ func HandleGuildMemberAdd(evtData *eventsystem.EventData) (retry bool, err error go analytics.RecordActiveUnit(gs.ID, &Plugin{}, "posted_join_server_dm") chanMsg := config.JoinServerMsgs[rand.Intn(len(config.JoinServerMsgs))] - if sendTemplate(channel, chanMsg, ms, "join server msg", config.CensorInvites, true) { + if sendTemplate(gs, channel, chanMsg, ms, "join server msg", config.CensorInvites, true) { return true, nil } } @@ -98,23 +97,23 @@ func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) return } - gs := bot.State.Guild(true, memberRemove.GuildID) + gs := bot.State.GetGuild(memberRemove.GuildID) if gs == nil { return } - channel := gs.Channel(true, config.LeaveChannelInt()) + channel := gs.GetChannel(config.LeaveChannelInt()) if channel == nil { return } - ms := dstate.MSFromDGoMember(gs, memberRemove.Member) + ms := dstate.MemberStateFromMember(memberRemove.Member) chanMsg := config.LeaveMsgs[rand.Intn(len(config.LeaveMsgs))] go analytics.RecordActiveUnit(gs.ID, &Plugin{}, "posted_leave_server_msg") - if sendTemplate(channel, chanMsg, ms, "leave", config.CensorInvites, false) { + if sendTemplate(gs, channel, chanMsg, ms, "leave", config.CensorInvites, false) { return true, nil } @@ -122,15 +121,19 @@ func HandleGuildMemberRemove(evt *eventsystem.EventData) (retry bool, err error) } // sendTemplate parses and executes the provided template, returns wether an error occured that we can retry from (temporary network failures and the like) -func sendTemplate(cs *dstate.ChannelState, tmpl string, ms *dstate.MemberState, name string, censorInvites bool, enableSendDM bool) bool { - ctx := templates.NewContext(cs.Guild, cs, ms) +func sendTemplate(gs *dstate.GuildSet, cs *dstate.ChannelState, tmpl string, ms *dstate.MemberState, name string, censorInvites bool, enableSendDM bool) bool { + ctx := templates.NewContext(gs, cs, ms) ctx.CurrentFrame.SendResponseInDM = cs.Type == discordgo.ChannelTypeDM - ctx.Data["RealUsername"] = ms.Username + // since were changing the fields, we need a copy + msCop := *ms + ms = &msCop + + ctx.Data["RealUsername"] = ms.User.Username if censorInvites { - newUsername := common.ReplaceServerInvites(ms.Username, ms.Guild.ID, "[removed-server-invite]") - if newUsername != ms.Username { - ms.Username = newUsername + fmt.Sprintf("(user ID: %d)", ms.ID) + newUsername := common.ReplaceServerInvites(ms.User.Username, gs.ID, "[removed-server-invite]") + if newUsername != ms.User.Username { + ms.User.Username = newUsername + fmt.Sprintf("(user ID: %d)", ms.User.ID) ctx.Data["UsernameHasInvite"] = true } } @@ -145,7 +148,7 @@ func sendTemplate(cs *dstate.ChannelState, tmpl string, ms *dstate.MemberState, msg, err := ctx.Execute(tmpl) if err != nil { - logger.WithError(err).WithField("guild", cs.Guild.ID).Warnf("Failed parsing/executing %s template", name) + logger.WithError(err).WithField("guild", gs.ID).Warnf("Failed parsing/executing %s template", name) return false } @@ -163,12 +166,12 @@ func sendTemplate(cs *dstate.ChannelState, tmpl string, ms *dstate.MemberState, var m *discordgo.Message m, err = common.BotSession.ChannelMessageSendComplex(cs.ID, ctx.MessageSend(msg)) if err == nil && ctx.CurrentFrame.DelResponse { - templates.MaybeScheduledDeleteMessage(cs.Guild.ID, cs.ID, m.ID, ctx.CurrentFrame.DelResponseDelay) + templates.MaybeScheduledDeleteMessage(gs.ID, cs.ID, m.ID, ctx.CurrentFrame.DelResponseDelay) } } if err != nil { - l := logger.WithError(err).WithField("guild", cs.Guild.ID) + l := logger.WithError(err).WithField("guild", gs.ID) if common.IsDiscordErr(err, discordgo.ErrCodeCannotSendMessagesToThisUser) { l.Warn("Failed sending " + name) } else { @@ -182,7 +185,12 @@ func sendTemplate(cs *dstate.ChannelState, tmpl string, ms *dstate.MemberState, func HandleChannelUpdate(evt *eventsystem.EventData) (retry bool, err error) { cu := evt.ChannelUpdate() - curChannel := bot.State.ChannelCopy(true, cu.ID) + gs := bot.State.GetGuild(cu.GuildID) + if gs == nil { + return + } + + curChannel := gs.GetChannel(cu.ID) if curChannel == nil { return } @@ -204,7 +212,7 @@ func HandleChannelUpdate(evt *eventsystem.EventData) (retry bool, err error) { topicChannel := cu.Channel.ID if config.TopicChannelInt() != 0 { - c := curChannel.Guild.Channel(true, config.TopicChannelInt()) + c := gs.GetChannel(config.TopicChannelInt()) if c != nil { topicChannel = c.ID } diff --git a/premium/codepremiumsource.go b/premium/codepremiumsource.go index 92acfd68d9..3f4744b244 100644 --- a/premium/codepremiumsource.go +++ b/premium/codepremiumsource.go @@ -11,7 +11,7 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/premium/plugin_bot.go b/premium/plugin_bot.go index 0e3b2c1743..d1526ac723 100644 --- a/premium/plugin_bot.go +++ b/premium/plugin_bot.go @@ -3,7 +3,6 @@ package premium import ( "time" - "github.com/jonas747/dstate/v2" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" ) @@ -11,8 +10,25 @@ import ( var _ bot.BotInitHandler = (*Plugin)(nil) var _ commands.CommandProvider = (*Plugin)(nil) +func init() { + oldF := bot.StateLimitsF + bot.StateLimitsF = func(guildID int64) (int, time.Duration) { + premium, err := IsGuildPremiumCached(guildID) + if err != nil { + logger.WithError(err).WithField("guild", guildID).Error("Failed checking if guild is premium") + return oldF(guildID) + } + + if premium { + return PremiumStateMaxMessags, PremiumStateMaxMessageAge + } + + return oldF(guildID) + } +} + func (p *Plugin) BotInit() { - bot.State.CustomLimitProvider = p + // bot.State.CustomLimitProvider = p } func (p *Plugin) AddCommands() { @@ -20,26 +36,6 @@ func (p *Plugin) AddCommands() { } const ( - NormalStateMaxMessages = 1000 - NormalStateMaxMessageAge = time.Hour - PremiumStateMaxMessags = 10000 PremiumStateMaxMessageAge = time.Hour * 12 ) - -func (p *Plugin) MessageLimits(gs *dstate.GuildState) (maxMessages int, maxMessageAge time.Duration) { - if gs == nil { - return NormalStateMaxMessages, NormalStateMaxMessageAge - } - - premium, err := IsGuildPremiumCached(gs.ID) - if err != nil { - logger.WithError(err).WithField("guild", gs.ID).Error("Failed checking if guild is premium") - } - - if premium { - return PremiumStateMaxMessags, PremiumStateMaxMessageAge - } - - return NormalStateMaxMessages, NormalStateMaxMessageAge -} diff --git a/reddit/bot.go b/reddit/bot.go index fcb597d1f0..f189cf1492 100644 --- a/reddit/bot.go +++ b/reddit/bot.go @@ -6,7 +6,7 @@ import ( "strings" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/reddit/models" diff --git a/reminders/plugin_bot.go b/reminders/plugin_bot.go index 38f7dc62dd..cefdc930d5 100644 --- a/reminders/plugin_bot.go +++ b/reminders/plugin_bot.go @@ -8,7 +8,7 @@ import ( "unicode/utf8" "github.com/jinzhu/gorm" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" @@ -95,7 +95,7 @@ var cmds = []*commands.YAGCommand{ SlashCommandEnabled: true, DefaultEnabled: true, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { - ok, err := bot.AdminOrPermMS(parsed.ChannelID, parsed.GuildData.MS, discordgo.PermissionManageChannels) + ok, err := bot.AdminOrPermMS(parsed.GuildData.GS.ID, parsed.ChannelID, parsed.GuildData.MS, discordgo.PermissionManageChannels) if err != nil { return nil, err } @@ -163,7 +163,7 @@ var cmds = []*commands.YAGCommand{ if reminder.GuildID != parsed.GuildData.GS.ID { return "You can only delete reminders that are not your own in the guild the reminder was originally created", nil } - ok, err := bot.AdminOrPermMS(reminder.ChannelIDInt(), parsed.GuildData.MS, discordgo.PermissionManageChannels) + ok, err := bot.AdminOrPermMS(reminder.GuildID, reminder.ChannelIDInt(), parsed.GuildData.MS, discordgo.PermissionManageChannels) if err != nil { return nil, err } @@ -213,7 +213,7 @@ func stringReminders(reminders []*Reminder, displayUsernames bool) string { member, _ := bot.GetMember(v.GuildID, v.UserIDInt()) username := "Unknown user" if member != nil { - username = member.Username + username = member.User.Username } out += fmt.Sprintf("**%d**: %s: '%s' - %s from now (%s)\n", v.ID, username, limitString(v.Message), timeFromNow, tStr) } diff --git a/reputation/plugin_bot.go b/reputation/plugin_bot.go index 5f0b46d2e8..4b8797c5ae 100644 --- a/reputation/plugin_bot.go +++ b/reputation/plugin_bot.go @@ -6,11 +6,11 @@ import ( "strconv" "strings" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot/paginatedmessages" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -64,7 +64,7 @@ func handleMessageCreate(evt *eventsystem.EventData) { } target, err := bot.GetMember(msg.GuildID, who.ID) - sender := dstate.MSFromDGoMember(evt.GS, msg.Member) + sender := dstate.MemberStateFromMember(msg.Member) if err != nil { logger.WithError(err).Error("Failed retrieving target member") return @@ -148,10 +148,10 @@ var cmds = []*commands.YAGCommand{ targetUsername := strconv.FormatInt(targetID, 10) targetMember, _ := bot.GetMember(parsed.GuildData.GS.ID, targetID) if targetMember != nil { - targetUsername = targetMember.Username + targetUsername = targetMember.User.Username } - err = SetRep(parsed.Context(), parsed.GuildData.GS.ID, parsed.GuildData.MS.ID, targetID, int64(parsed.Args[1].Int())) + err = SetRep(parsed.Context(), parsed.GuildData.GS.ID, parsed.GuildData.MS.User.ID, targetID, int64(parsed.Args[1].Int())) if err != nil { return nil, err } @@ -260,11 +260,11 @@ var cmds = []*commands.YAGCommand{ sender := entry.SenderUsername for _, v := range members { - if v.ID == entry.ReceiverID { - receiver = v.Username + "#" + v.StrDiscriminator() + if v.User.ID == entry.ReceiverID { + receiver = v.User.Username + "#" + v.User.Discriminator } - if v.ID == entry.SenderID { - sender = v.Username + "#" + v.StrDiscriminator() + if v.User.ID == entry.SenderID { + sender = v.User.Username + "#" + v.User.Discriminator } } diff --git a/reputation/reputation.go b/reputation/reputation.go index fc291284a2..3a4dd355bf 100644 --- a/reputation/reputation.go +++ b/reputation/reputation.go @@ -11,7 +11,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/common" @@ -163,7 +163,7 @@ func ModifyRep(ctx context.Context, conf *models.ReputationConfig, guildID int64 return nil } - ok, err := CheckSetCooldown(conf, sender.ID) + ok, err := CheckSetCooldown(conf, sender.User.ID) if err != nil || !ok { if err == nil { err = ErrCooldown @@ -171,24 +171,22 @@ func ModifyRep(ctx context.Context, conf *models.ReputationConfig, guildID int64 return } - err = insertUpdateUserRep(ctx, guildID, receiver.ID, amount) + err = insertUpdateUserRep(ctx, guildID, receiver.User.ID, amount) if err != nil { // Clear the cooldown since it failed updating the rep - ClearCooldown(guildID, sender.ID) + ClearCooldown(guildID, sender.User.ID) return } - receiver.Guild.RLock() - defer receiver.Guild.RUnlock() - receiverUsername := receiver.Username + "#" + receiver.StrDiscriminator() - senderUsername := sender.Username + "#" + sender.StrDiscriminator() + receiverUsername := receiver.User.Username + "#" + receiver.User.Discriminator + senderUsername := sender.User.Username + "#" + sender.User.Discriminator // Add the log element entry := &models.ReputationLog{ GuildID: guildID, - SenderID: sender.ID, + SenderID: sender.User.ID, SenderUsername: senderUsername, - ReceiverID: receiver.ID, + ReceiverID: receiver.User.ID, ReceiverUsername: receiverUsername, SetFixedAmount: false, Amount: amount, @@ -218,38 +216,38 @@ DO UPDATE SET points = reputation_users.points + $4; // Returns a user error if the sender can not modify the rep of receiver // Admins are always able to modify the rep of everyone func CanModifyRep(conf *models.ReputationConfig, sender, receiver *dstate.MemberState) error { - if common.ContainsInt64SliceOneOf(sender.Roles, conf.AdminRoles) { + if common.ContainsInt64SliceOneOf(sender.Member.Roles, conf.AdminRoles) { return nil } - if len(conf.RequiredGiveRoles) > 0 && !common.ContainsInt64SliceOneOf(sender.Roles, conf.RequiredGiveRoles) { + if len(conf.RequiredGiveRoles) > 0 && !common.ContainsInt64SliceOneOf(sender.Member.Roles, conf.RequiredGiveRoles) { return ErrMissingRequiredGiveRole } - if len(conf.RequiredReceiveRoles) > 0 && !common.ContainsInt64SliceOneOf(receiver.Roles, conf.RequiredReceiveRoles) { + if len(conf.RequiredReceiveRoles) > 0 && !common.ContainsInt64SliceOneOf(receiver.Member.Roles, conf.RequiredReceiveRoles) { return ErrMissingRequiredReceiveRole } - if common.ContainsInt64SliceOneOf(sender.Roles, conf.BlacklistedGiveRoles) { + if common.ContainsInt64SliceOneOf(sender.Member.Roles, conf.BlacklistedGiveRoles) { return ErrBlacklistedGive } - if common.ContainsInt64SliceOneOf(receiver.Roles, conf.BlacklistedReceiveRoles) { + if common.ContainsInt64SliceOneOf(receiver.Member.Roles, conf.BlacklistedReceiveRoles) { return ErrBlacklistedReceive } return nil } -func IsAdmin(gs *dstate.GuildState, member *dstate.MemberState, config *models.ReputationConfig) bool { +func IsAdmin(gs *dstate.GuildSet, member *dstate.MemberState, config *models.ReputationConfig) bool { - memberPerms, _ := gs.MemberPermissions(false, gs.ID, member.ID) + memberPerms, _ := gs.GetMemberPermissions(0, member.User.ID, member.Member.Roles) if memberPerms&discordgo.PermissionManageServer != 0 { return true } - if common.ContainsInt64SliceOneOf(member.Roles, config.AdminRoles) { + if common.ContainsInt64SliceOneOf(member.Member.Roles, config.AdminRoles) { return true } @@ -343,7 +341,7 @@ func DetailedLeaderboardEntries(guildID int64, ranks []*RankEntry) ([]*Leaderboa tmp, err = bot.GetMembers(guildID, userIDs...) if tmp != nil { for _, v := range tmp { - members = append(members, v.DGoCopy()) + members = append(members, v.DgoMember()) } } } else { diff --git a/rolecommands/bot.go b/rolecommands/bot.go index 35adc06a2b..509fc67a6a 100644 --- a/rolecommands/bot.go +++ b/rolecommands/bot.go @@ -4,11 +4,10 @@ import ( "context" "database/sql" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" - "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -146,7 +145,7 @@ func (p *Plugin) AddCommands() { menuContainer.AddCommand(cmdResetReactions, cmdResetReactions.GetTrigger()) menuContainer.AddCommand(cmdEditOption, cmdEditOption.GetTrigger()) menuContainer.AddCommand(cmdFinishSetup, cmdFinishSetup.GetTrigger()) - commands.RegisterSlashCommandsContainer(menuContainer, true, func(gs *dstate.GuildState) ([]int64, error) { + commands.RegisterSlashCommandsContainer(menuContainer, true, func(gs *dstate.GuildSet) ([]int64, error) { return nil, nil }) } @@ -204,13 +203,10 @@ func CmdFuncRole(parsed *dcmd.Data) (interface{}, error) { return "Took away your role!", nil } -func HumanizeAssignError(guild *dstate.GuildState, err error) (string, error) { +func HumanizeAssignError(guild *dstate.GuildSet, err error) (string, error) { if IsRoleCommandError(err) { if roleError, ok := err.(*RoleError); ok { - guild.RLock() - defer guild.RUnlock() - - return roleError.PrettyError(guild.Guild.Roles), nil + return roleError.PrettyError(guild.Roles), nil } return err.Error(), nil } @@ -342,10 +338,10 @@ OUTER: return scheduledevents2.CheckDiscordErrRetry(err), err } -type MenuCacheKey int64 +var menuCache = common.CacheSet.RegisterSlot("rolecommands_menus", nil, int64(0)) -func GetRolemenuCached(ctx context.Context, gs *dstate.GuildState, messageID int64) (*models.RoleMenu, error) { - result, err := gs.UserCacheFetch(MenuCacheKey(messageID), func() (interface{}, error) { +func GetRolemenuCached(ctx context.Context, gs *dstate.GuildSet, messageID int64) (*models.RoleMenu, error) { + result, err := menuCache.GetCustomFetch(messageID, func(key interface{}) (interface{}, error) { menu, err := FindRolemenuFull(ctx, messageID, gs.ID) if err != nil { if err != sql.ErrNoRows { @@ -369,12 +365,8 @@ func GetRolemenuCached(ctx context.Context, gs *dstate.GuildState, messageID int } func ClearRolemenuCache(gID int64) { - gs := bot.State.Guild(true, gID) - if gs != nil { - ClearRolemenuCacheGS(gs) - } -} - -func ClearRolemenuCacheGS(gs *dstate.GuildState) { - gs.UserCacheDellAllKeysType(MenuCacheKey(0)) + menuCache.DeleteFunc(func(key interface{}, value interface{}) bool { + valueCast := value.(*models.RoleMenu) + return valueCast.GuildID == gID + }) } diff --git a/rolecommands/commonrole.go b/rolecommands/commonrole.go index 0bc57e266f..6aacf9735f 100644 --- a/rolecommands/commonrole.go +++ b/rolecommands/commonrole.go @@ -5,7 +5,7 @@ import ( "time" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/scheduledevents2" schEvtsModels "github.com/jonas747/yagpdb/common/scheduledevents2/models" @@ -137,7 +137,7 @@ func (c *CommonRoleSettings) CanRole(ctx context.Context, ms *dstate.MemberState // First check cooldown if c.ParentGroupMode == GroupModeSingle { err = cooldownsDB.Update(func(tx *buntdb.Tx) error { - _, replaced, _ := tx.Set(discordgo.StrID(ms.ID), "1", &buntdb.SetOptions{Expires: true, TTL: time.Second * 1}) + _, replaced, _ := tx.Set(discordgo.StrID(ms.User.ID), "1", &buntdb.SetOptions{Expires: true, TTL: time.Second * 1}) if replaced { onCD = true } @@ -150,13 +150,13 @@ func (c *CommonRoleSettings) CanRole(ctx context.Context, ms *dstate.MemberState } if len(c.WhitelistRoles) > 0 { - if !CheckRequiredRoles(c.WhitelistRoles, ms.Roles) { + if !CheckRequiredRoles(c.WhitelistRoles, ms.Member.Roles) { return false, NewSimpleError("This self assignable role has been configured to require another role by the server admins.") } } if len(c.BlacklistRoles) > 0 { - if err := CheckIgnoredRoles(c.BlacklistRoles, ms.Roles); err != nil { + if err := CheckIgnoredRoles(c.BlacklistRoles, ms.Member.Roles); err != nil { return false, err } } @@ -175,14 +175,14 @@ func (c *CommonRoleSettings) CanRole(ctx context.Context, ms *dstate.MemberState func (c *CommonRoleSettings) ParentCanRole(ctx context.Context, ms *dstate.MemberState) (can bool, err error) { if len(c.ParentWhitelistRoles) > 0 { - if !CheckRequiredRoles(c.ParentWhitelistRoles, ms.Roles) { + if !CheckRequiredRoles(c.ParentWhitelistRoles, ms.Member.Roles) { err = NewSimpleError("You don't have a required role for this self-assignable role group.") return false, err } } if len(c.ParentBlacklistRoles) > 0 { - if err = CheckIgnoredRoles(c.ParentBlacklistRoles, ms.Roles); err != nil { + if err = CheckIgnoredRoles(c.ParentBlacklistRoles, ms.Member.Roles); err != nil { return false, err } } @@ -198,7 +198,7 @@ func (c *CommonRoleSettings) ParentCanRole(ctx context.Context, ms *dstate.Membe if c.ParentGroupMode == GroupModeSingle { // If user already has role it's attempting to give itself, assume were trying to remove it - if common.ContainsInt64Slice(ms.Roles, c.RoleId) { + if common.ContainsInt64Slice(ms.Member.Roles, c.RoleId) { if modeSettings.SingleRequireOne { return false, NewSimpleError("Need at least one role in this group/rolemenu") } @@ -208,7 +208,7 @@ func (c *CommonRoleSettings) ParentCanRole(ctx context.Context, ms *dstate.Membe // Check if the user has any other role commands in this group for _, v := range commands { - if common.ContainsInt64Slice(ms.Roles, v.RoleId) { + if common.ContainsInt64Slice(ms.Member.Roles, v.RoleId) { if !modeSettings.SingleAutoToggleOff { return false, NewSimpleError("Max 1 role in this group/rolemenu is allowed") } @@ -226,7 +226,7 @@ func (c *CommonRoleSettings) ParentCanRole(ctx context.Context, ms *dstate.Membe hasRoles := 0 hasTargetRole := false for _, role := range commands { - if common.ContainsInt64Slice(ms.Roles, role.RoleId) { + if common.ContainsInt64Slice(ms.Member.Roles, role.RoleId) { hasRoles++ if role.RoleId == c.RoleId { hasTargetRole = true @@ -269,12 +269,12 @@ func (c *CommonRoleSettings) CheckToggleRole(ctx context.Context, ms *dstate.Mem // ToggleRole toggles the role of a guildmember, adding it if the member does not have the role and removing it if they do func (c *CommonRoleSettings) ToggleRole(ms *dstate.MemberState) (gaveRole bool, err error) { - if common.ContainsInt64Slice(ms.Roles, c.RoleId) { - err = common.BotSession.GuildMemberRoleRemove(ms.Guild.ID, ms.ID, c.RoleId) + if common.ContainsInt64Slice(ms.Member.Roles, c.RoleId) { + err = common.BotSession.GuildMemberRoleRemove(ms.GuildID, ms.User.ID, c.RoleId) return false, err } - err = common.BotSession.GuildMemberRoleAdd(ms.Guild.ID, ms.ID, c.RoleId) + err = common.BotSession.GuildMemberRoleAdd(ms.GuildID, ms.User.ID, c.RoleId) return true, err } @@ -290,17 +290,17 @@ func (c *CommonRoleSettings) GroupToggleRole(ctx context.Context, ms *dstate.Mem } // If user already has role it's attempting to give itself - if common.ContainsInt64Slice(ms.Roles, c.RoleId) { - err = common.BotSession.GuildMemberRoleRemove(ms.Guild.ID, ms.ID, c.RoleId) + if common.ContainsInt64Slice(ms.Member.Roles, c.RoleId) { + err = common.BotSession.GuildMemberRoleRemove(ms.GuildID, ms.User.ID, c.RoleId) return false, err } // Check if the user has any other role commands in this group commands := c.AllGroupRoles(ctx) for _, v := range commands { - if common.ContainsInt64Slice(ms.Roles, v.RoleId) { + if common.ContainsInt64Slice(ms.Member.Roles, v.RoleId) { if c.ModeSettings().SingleAutoToggleOff { - common.BotSession.GuildMemberRoleRemove(ms.Guild.ID, ms.ID, v.RoleId) + common.BotSession.GuildMemberRoleRemove(ms.GuildID, ms.User.ID, v.RoleId) } else { return false, NewCommonRoleError("Max 1 role in **%s** is allowed", c) } @@ -308,7 +308,7 @@ func (c *CommonRoleSettings) GroupToggleRole(ctx context.Context, ms *dstate.Mem } // Finally give the role - err = common.BotSession.GuildMemberRoleAdd(ms.Guild.ID, ms.ID, c.RoleId) + err = common.BotSession.GuildMemberRoleAdd(ms.GuildID, ms.User.ID, c.RoleId) if err == nil { err = c.MaybeScheduleRoleRemoval(ctx, ms) } @@ -316,7 +316,7 @@ func (c *CommonRoleSettings) GroupToggleRole(ctx context.Context, ms *dstate.Mem } func (c *CommonRoleSettings) AssignRole(ctx context.Context, ms *dstate.MemberState) (gaveRole bool, err error) { - if common.ContainsInt64Slice(ms.Roles, c.RoleId) { + if common.ContainsInt64Slice(ms.Member.Roles, c.RoleId) { return false, nil } @@ -324,7 +324,7 @@ func (c *CommonRoleSettings) AssignRole(ctx context.Context, ms *dstate.MemberSt } func (c *CommonRoleSettings) RemoveRole(ctx context.Context, ms *dstate.MemberState) (removedRole bool, err error) { - if !common.ContainsInt64Slice(ms.Roles, c.RoleId) { + if !common.ContainsInt64Slice(ms.Member.Roles, c.RoleId) { return false, nil } @@ -340,16 +340,16 @@ func (c *CommonRoleSettings) MaybeScheduleRoleRemoval(ctx context.Context, ms *d } // remove existing role removal events for this role - _, err := schEvtsModels.ScheduledEvents(v3_qm.Where("event_name='remove_member_role' AND guild_id = ? AND (data->>'user_id')::bigint = ? AND (data->>'role_id')::bigint = ?", ms.Guild.ID, ms.ID, c.RoleId)).DeleteAll(ctx, common.PQ) + _, err := schEvtsModels.ScheduledEvents(v3_qm.Where("event_name='remove_member_role' AND guild_id = ? AND (data->>'user_id')::bigint = ? AND (data->>'role_id')::bigint = ?", ms.GuildID, ms.User.ID, c.RoleId)).DeleteAll(ctx, common.PQ) if err != nil { return err } // add the scheduled event for it - err = scheduledevents2.ScheduleEvent("remove_member_role", ms.Guild.ID, time.Now().Add(time.Duration(temporaryDuration)*time.Minute), &ScheduledMemberRoleRemoveData{ - GuildID: ms.Guild.ID, + err = scheduledevents2.ScheduleEvent("remove_member_role", ms.GuildID, time.Now().Add(time.Duration(temporaryDuration)*time.Minute), &ScheduledMemberRoleRemoveData{ + GuildID: ms.GuildID, GroupID: c.ParentGroup.ID, - UserID: ms.ID, + UserID: ms.User.ID, RoleID: c.RoleId, }) diff --git a/rolecommands/menu.go b/rolecommands/menu.go index 9b659dd5be..fd6093de6f 100644 --- a/rolecommands/menu.go +++ b/rolecommands/menu.go @@ -10,9 +10,9 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -99,7 +99,7 @@ func cmdFuncRoleMenuCreate(parsed *dcmd.Data) (interface{}, error) { model.R = model.R.NewStruct() model.R.RoleGroup = group - ClearRolemenuCacheGS(parsed.GuildData.GS) + ClearRolemenuCache(parsed.GuildData.GS.ID) recentMenusTracker.AddMenu(model.MessageID) resp, err := NextRoleMenuSetupStep(parsed.Context(), model, true) updateSetupMessage(parsed.Context(), model, resp) @@ -152,7 +152,7 @@ func UpdateMenu(parsed *dcmd.Data, menu *models.RoleMenu) (interface{}, error) { if resp != "" { createSetupMessage(parsed.Context(), menu, resp, true) } - ClearRolemenuCacheGS(parsed.GuildData.GS) + ClearRolemenuCache(parsed.GuildData.GS.ID) return nil, err } @@ -229,7 +229,7 @@ func UpdateRoleMenuMessage(ctx context.Context, rm *models.RoleMenu) error { opts := rm.R.RoleMenuOptions sort.Slice(opts, OptionsLessFunc(!rm.RoleGroupID.Valid, opts)) - gs := bot.State.Guild(true, rm.GuildID) + gs := bot.State.GetGuild(rm.GuildID) if gs == nil { return errors.New("Guild not found") } @@ -469,9 +469,6 @@ func handleReactionAddRemove(evt *eventsystem.EventData) { } gs := evt.GS - gs.RLock() - name := gs.Guild.Name - gs.RUnlock() resp, err := MemberChooseOption(evt.Context(), menu, gs, option, uID, emoji, raAdd) if err != nil && !common.IsDiscordErr(err, discordgo.ErrCodeUnknownRole, discordgo.ErrCodeMissingPermissions) { @@ -479,11 +476,11 @@ func handleReactionAddRemove(evt *eventsystem.EventData) { } if resp != "" { - bot.SendDM(uID, "**"+name+"**: "+resp) + bot.SendDM(uID, "**"+gs.Name+"**: "+resp) } } -func MemberChooseOption(ctx context.Context, rm *models.RoleMenu, gs *dstate.GuildState, option *models.RoleMenuOption, userID int64, emoji *discordgo.Emoji, raAdd bool) (resp string, err error) { +func MemberChooseOption(ctx context.Context, rm *models.RoleMenu, gs *dstate.GuildSet, option *models.RoleMenuOption, userID int64, emoji *discordgo.Emoji, raAdd bool) (resp string, err error) { member, err := bot.GetMember(gs.ID, userID) if err != nil { if common.IsDiscordErr(err, discordgo.ErrCodeUnknownMember) { @@ -493,7 +490,7 @@ func MemberChooseOption(ctx context.Context, rm *models.RoleMenu, gs *dstate.Gui return "An error occurred giving you the role", err } - if member.Bot { + if member.User.Bot { // ignore bots return "", nil } @@ -724,7 +721,7 @@ func cmdFuncRoleMenuRemove(data *dcmd.Data) (interface{}, error) { if err != nil { return nil, err } - ClearRolemenuCacheGS(data.GuildData.GS) + ClearRolemenuCache(data.GuildData.GS.ID) return "Deleted. The bot will no longer listen for reactions on this message, you can even make another menu on it.", nil } @@ -748,7 +745,7 @@ func cmdFuncRoleMenuEditOption(data *dcmd.Data) (interface{}, error) { return "", err } - ClearRolemenuCacheGS(data.GuildData.GS) + ClearRolemenuCache(data.GuildData.GS.ID) createSetupMessage(data.Context(), menu, "React on the emoji for the option you want to change", true) return nil, nil @@ -776,12 +773,12 @@ func cmdFuncRoleMenuComplete(data *dcmd.Data) (interface{}, error) { if err != nil { return nil, err } - ClearRolemenuCacheGS(data.GuildData.GS) + ClearRolemenuCache(data.GuildData.GS.ID) return "Menu marked as done", nil } -func MenuReactedNotDone(ctx context.Context, gs *dstate.GuildState, rm *models.RoleMenu, emoji *discordgo.Emoji, userID int64) (resp string, err error) { +func MenuReactedNotDone(ctx context.Context, gs *dstate.GuildSet, rm *models.RoleMenu, emoji *discordgo.Emoji, userID int64) (resp string, err error) { if userID != rm.OwnerID { return "Someone is currently editing or setting up this menu, please wait", nil } @@ -892,16 +889,15 @@ func createSetupMessage(ctx context.Context, rm *models.RoleMenu, msgContents st } } -func OptionName(gs *dstate.GuildState, opt *models.RoleMenuOption) string { +func OptionName(gs *dstate.GuildSet, opt *models.RoleMenuOption) string { if opt.RoleCommandID.Valid { return opt.R.RoleCommand.Name } else { - r := gs.RoleCopy(true, opt.StandaloneRoleID.Int64) + r := gs.GetRole(opt.StandaloneRoleID.Int64) if r != nil { return r.Name } else { return "unknown role" } } - } diff --git a/rolecommands/rolecommands.go b/rolecommands/rolecommands.go index d6e2eed704..91afc36039 100644 --- a/rolecommands/rolecommands.go +++ b/rolecommands/rolecommands.go @@ -10,7 +10,7 @@ import ( "strconv" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -64,7 +64,7 @@ func RegisterPlugin() { } func FindToggleRole(ctx context.Context, ms *dstate.MemberState, name string) (gaveRole bool, err error) { - cmd, err := models.RoleCommands(qm.Where("guild_id=?", ms.Guild.ID), qm.Where("name ILIKE ?", name), qm.Load("RoleGroup.RoleCommands")).OneG(ctx) + cmd, err := models.RoleCommands(qm.Where("guild_id=?", ms.GuildID), qm.Where("name ILIKE ?", name), qm.Load("RoleGroup.RoleCommands")).OneG(ctx) if err != nil { return false, err } @@ -125,7 +125,7 @@ func (r *RoleError) Error() string { } // Uses the role name from one of the passed roles with matching id instead of the id -func (r *RoleError) PrettyError(roles []*discordgo.Role) string { +func (r *RoleError) PrettyError(roles []discordgo.Role) string { if r.Role == 0 { return r.Message } diff --git a/rsvp/plugin_bot.go b/rsvp/plugin_bot.go index 0bedfa4d85..2cc47560c3 100644 --- a/rsvp/plugin_bot.go +++ b/rsvp/plugin_bot.go @@ -10,9 +10,9 @@ import ( "time" "unicode/utf8" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" @@ -270,12 +270,12 @@ func (p *Plugin) AddCommands() { container.AddCommand(cmdDel, cmdDel.GetTrigger()) container.AddCommand(cmdStopSetup, cmdStopSetup.GetTrigger()) container.Description = "Manage events" - commands.RegisterSlashCommandsContainer(container, true, func(gs *dstate.GuildState) ([]int64, error) { + commands.RegisterSlashCommandsContainer(container, true, func(gs *dstate.GuildSet) ([]int64, error) { return nil, nil }) } -type RolesRunFunc func(gs *dstate.GuildState) ([]int64, error) +type RolesRunFunc func(gs *dstate.GuildSet) ([]int64, error) func (p *Plugin) handleMessageCreate(evt *eventsystem.EventData) { m := evt.MessageCreate() @@ -447,9 +447,8 @@ func UpdateEventEmbed(m *models.RSVPSession) error { func findUser(members []*dstate.MemberState, target int64) *discordgo.User { for _, v := range members { - if v.ID == target { - dgoUser := v.DGoUser() - return dgoUser + if v.User.ID == target { + return &v.User } } @@ -572,11 +571,9 @@ func (p *Plugin) startEvent(m *models.RSVPSession) error { func (p *Plugin) sendReminders(m *models.RSVPSession, title, desc string) { serverName := strconv.FormatInt(m.GuildID, 10) - gs := bot.State.Guild(true, m.GuildID) + gs := bot.State.GetGuild(m.GuildID) if gs != nil { - gs.RLock() - serverName = gs.Guild.Name - gs.RUnlock() + serverName = gs.Name } for _, v := range m.R.RSVPSessionsMessageRSVPParticipants { diff --git a/rsvp/setup.go b/rsvp/setup.go index e3c8666313..1590c6053d 100644 --- a/rsvp/setup.go +++ b/rsvp/setup.go @@ -10,9 +10,9 @@ import ( "time" "unicode/utf8" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/scheduledevents2" @@ -96,7 +96,7 @@ func (s *SetupSession) handleMessage(m *discordgo.Message) { func (s *SetupSession) handleMessageSetupStateChannel(m *discordgo.Message) { targetChannel := int64(0) - gs := bot.State.Guild(true, m.GuildID) + gs := bot.State.GetGuild(m.GuildID) if gs == nil { logger.WithField("guild", m.GuildID).Error("Guild not found") return @@ -109,27 +109,25 @@ func (s *SetupSession) handleMessageSetupStateChannel(m *discordgo.Message) { // channel mention idStr := m.Content[2 : len(m.Content)-1] if parsed, err := strconv.ParseInt(idStr, 10, 64); err == nil { - if gs.Channel(true, parsed) != nil { + if gs.GetChannel(parsed) != nil { targetChannel = parsed } } } else { // search by name nameSearch := strings.ReplaceAll(m.Content, " ", "-") - gs.RLock() for _, v := range gs.Channels { if strings.EqualFold(v.Name, nameSearch) { targetChannel = v.ID break } } - gs.RUnlock() } if targetChannel == 0 { // search by ID if parsed, err := strconv.ParseInt(m.Content, 10, 64); err == nil { - if gs.Channel(true, parsed) != nil { + if gs.GetChannel(parsed) != nil { targetChannel = parsed } } @@ -140,7 +138,7 @@ func (s *SetupSession) handleMessageSetupStateChannel(m *discordgo.Message) { return } - hasPerms, err := bot.AdminOrPermMS(targetChannel, dstate.MSFromDGoMember(gs, m.Member), discordgo.PermissionSendMessages) + hasPerms, err := bot.AdminOrPermMS(m.GuildID, targetChannel, dstate.MemberStateFromMember(m.Member), discordgo.PermissionSendMessages) if err != nil { s.sendMessage("Failed retrieving your pems, check with bot owner") logger.WithError(err).WithField("guild", gs.ID).Error("failed calculating permissions") diff --git a/serverstats/plugin_bot.go b/serverstats/plugin_bot.go index cba80a1e12..ba5a7112aa 100644 --- a/serverstats/plugin_bot.go +++ b/serverstats/plugin_bot.go @@ -7,14 +7,12 @@ import ( "time" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" - "github.com/jonas747/yagpdb/common/pubsub" "github.com/jonas747/yagpdb/serverstats/messagestatscollector" "github.com/jonas747/yagpdb/web" "github.com/mediocregopher/radix/v3" @@ -36,13 +34,6 @@ func (p *Plugin) BotInit() { memberSatatsUpdater = newServerMemberStatsUpdater() go memberSatatsUpdater.run() - pubsub.AddHandler("server_stats_invalidate_cache", func(evt *pubsub.Event) { - gs := bot.State.Guild(true, evt.TargetGuildInt) - if gs != nil { - gs.UserCacheDel(CacheKeyConfig) - } - }, nil) - if !confDeprecated.GetBool() { eventsystem.AddHandlerAsyncLastLegacy(p, handleUpdateMemberStats, eventsystem.EventGuildMemberAdd, eventsystem.EventGuildMemberRemove, eventsystem.EventGuildCreate) eventsystem.AddHandlerAsyncLast(p, eventsystem.RequireCSMW(HandleMessageCreate), eventsystem.EventMessageCreate) @@ -83,11 +74,11 @@ func (p *Plugin) AddCommands() { Title: "Server stats", Description: fmt.Sprintf("[Click here to open in browser](%s/public/%d/stats)", web.BaseURL(), data.GuildData.GS.ID), Fields: []*discordgo.MessageEmbedField{ - &discordgo.MessageEmbedField{Name: "Members joined 24h", Value: fmt.Sprint(stats.JoinedDay), Inline: true}, - &discordgo.MessageEmbedField{Name: "Members Left 24h", Value: fmt.Sprint(stats.LeftDay), Inline: true}, - &discordgo.MessageEmbedField{Name: "Total Messages 24h", Value: fmt.Sprint(total), Inline: true}, - &discordgo.MessageEmbedField{Name: "Members Online", Value: fmt.Sprint(stats.Online), Inline: true}, - &discordgo.MessageEmbedField{Name: "Total Members", Value: fmt.Sprint(stats.TotalMembers), Inline: true}, + {Name: "Members joined 24h", Value: fmt.Sprint(stats.JoinedDay), Inline: true}, + {Name: "Members Left 24h", Value: fmt.Sprint(stats.LeftDay), Inline: true}, + {Name: "Total Messages 24h", Value: fmt.Sprint(total), Inline: true}, + {Name: "Members Online", Value: fmt.Sprint(stats.Online), Inline: true}, + {Name: "Total Members", Value: fmt.Sprint(stats.TotalMembers), Inline: true}, }, } @@ -115,7 +106,7 @@ func HandleMessageCreate(evt *eventsystem.EventData) (retry bool, err error) { channel := evt.CS() - config, err := BotCachedFetchGuildConfig(evt.Context(), channel.Guild) + config, err := BotCachedFetchGuildConfig(evt.Context(), channel.GuildID) if err != nil { return true, errors.WithStackIf(err) } @@ -128,15 +119,11 @@ func HandleMessageCreate(evt *eventsystem.EventData) (retry bool, err error) { return false, nil } -type CacheKey int - -const ( - CacheKeyConfig CacheKey = iota -) +var cachedConfig = common.CacheSet.RegisterSlot("serverstats_config", nil, int64(0)) -func BotCachedFetchGuildConfig(ctx context.Context, gs *dstate.GuildState) (*ServerStatsConfig, error) { - v, err := gs.UserCacheFetch(CacheKeyConfig, func() (interface{}, error) { - return GetConfig(ctx, gs.ID) +func BotCachedFetchGuildConfig(ctx context.Context, guildID int64) (*ServerStatsConfig, error) { + v, err := cachedConfig.GetCustomFetch(guildID, func(key interface{}) (interface{}, error) { + return GetConfig(ctx, guildID) }) if err != nil { @@ -154,22 +141,19 @@ func (p *Plugin) runOnlineUpdater() { time.Sleep(time.Minute * 1) // relieve startup preasure ticker := time.NewTicker(time.Second * 10) - state := bot.State - var guildsToCheck []*dstate.GuildState + var guildsToCheck []int64 var i int var numToCheckPerRun int for { - select { - case <-ticker.C: - } + <-ticker.C if len(guildsToCheck) < 0 || i >= len(guildsToCheck) { // Copy the list of guilds so that we dont need to keep the entire state locked i = 0 - guildsToCheck = state.GuildsSlice(true) + guildsToCheck = guildSlice() // Hit each guild once per hour more or less numToCheckPerRun = len(guildsToCheck) / 250 @@ -187,7 +171,7 @@ func (p *Plugin) runOnlineUpdater() { g := guildsToCheck[i] online, total := p.checkGuildOnlineCount(g) - totalCounts[g.ID] = [2]int{online, total} + totalCounts[g] = [2]int{online, total} checkedThisRound++ } @@ -212,16 +196,33 @@ func (p *Plugin) runOnlineUpdater() { } } -func (p *Plugin) checkGuildOnlineCount(guild *dstate.GuildState) (online int, total int) { +func guildSlice() []int64 { + result := make([]int64, 0, 100) - guild.RLock() - total = guild.Guild.MemberCount - for _, v := range guild.Members { - if v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { - online++ + shards := bot.ReadyTracker.GetProcessShards() + for _, shard := range shards { + guilds := bot.State.GetShardGuilds(int64(shard)) + for _, g := range guilds { + result = append(result, g.ID) } } - guild.RUnlock() + + return result +} + +func (p *Plugin) checkGuildOnlineCount(guildID int64) (online int, total int) { + + g := bot.State.GetGuild(guildID) + if g != nil { + return 0, int(g.MemberCount) + } + + // total = guild.MemberCount + // for _, v := range guild.Members { + // if v.PresenceSet && v.PresenceStatus != dstate.StatusOffline { + // online++ + // } + // } return online, total } diff --git a/serverstats/plugin_web.go b/serverstats/plugin_web.go index 4f10a71b28..737138671b 100644 --- a/serverstats/plugin_web.go +++ b/serverstats/plugin_web.go @@ -135,7 +135,7 @@ OUTER: err := model.UpsertG(r.Context(), true, []string{"guild_id"}, boil.Whitelist("public", "ignore_channels"), boil.Infer()) if err == nil { - go pubsub.Publish("server_stats_invalidate_cache", ag.ID, nil) + pubsub.EvictCacheSet(cachedConfig, ag.ID) go cplogs.RetryAddEntry(web.NewLogEntryFromContext(r.Context(), panelLogKey)) } diff --git a/soundboard/bot.go b/soundboard/bot.go index 6c183f8298..f58f3516dd 100644 --- a/soundboard/bot.go +++ b/soundboard/bot.go @@ -4,8 +4,8 @@ import ( "strings" "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/soundboard/models" @@ -46,7 +46,7 @@ func (p *Plugin) AddCommands() { if sound == nil { return "Sound not found, " + ListSounds(sounds, member), nil - } else if !CanPlaySound(sound, member.Roles) { + } else if !CanPlaySound(sound, member.Member.Roles) { return "You can't play that sound, either you have a blacklisted role or missing a required role for this sound", nil } @@ -64,11 +64,8 @@ func (p *Plugin) AddCommands() { } } - data.GuildData.GS.RLock() - defer data.GuildData.GS.RUnlock() - var voiceChannel int64 - vs := data.GuildData.GS.VoiceState(false, data.Author.ID) + vs := data.GuildData.GS.GetVoiceState(data.Author.ID) if vs != nil { voiceChannel = vs.ChannelID } @@ -109,7 +106,7 @@ func ListSounds(sounds []*models.SoundboardSound, ms *dstate.MemberState) string restricted := "" for _, sound := range sounds { - if CanPlaySound(sound, ms.Roles) { + if CanPlaySound(sound, ms.Member.Roles) { canPlay += "`" + sound.Name + "`, " } else { restricted += "`" + sound.Name + "`, " diff --git a/stdcommands/advice/advice.go b/stdcommands/advice/advice.go index 3a57b44464..bf0c6b960e 100644 --- a/stdcommands/advice/advice.go +++ b/stdcommands/advice/advice.go @@ -5,7 +5,7 @@ import ( "net/http" "net/url" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/allocstat/measure_allocs.go b/stdcommands/allocstat/measure_allocs.go index c8dda2d302..3b39761cb9 100644 --- a/stdcommands/allocstat/measure_allocs.go +++ b/stdcommands/allocstat/measure_allocs.go @@ -5,7 +5,7 @@ import ( "runtime" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/banserver/banserver.go b/stdcommands/banserver/banserver.go index a74e0b264f..fdd89a4fab 100644 --- a/stdcommands/banserver/banserver.go +++ b/stdcommands/banserver/banserver.go @@ -1,7 +1,7 @@ package banserver import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/calc/calc.go b/stdcommands/calc/calc.go index 151c3eded1..12f7283899 100644 --- a/stdcommands/calc/calc.go +++ b/stdcommands/calc/calc.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/alfredxing/calc/compute" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/catfact/catfact.go b/stdcommands/catfact/catfact.go index 74501b199e..c19a775435 100644 --- a/stdcommands/catfact/catfact.go +++ b/stdcommands/catfact/catfact.go @@ -3,7 +3,7 @@ package catfact import ( "math/rand" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/ccreqs/ccreqs.go b/stdcommands/ccreqs/ccreqs.go index 3283615db1..02588dcf81 100644 --- a/stdcommands/ccreqs/ccreqs.go +++ b/stdcommands/ccreqs/ccreqs.go @@ -3,7 +3,7 @@ package ccreqs import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/createinvite/createinvite.go b/stdcommands/createinvite/createinvite.go index 6756e55176..1fe27c9d49 100644 --- a/stdcommands/createinvite/createinvite.go +++ b/stdcommands/createinvite/createinvite.go @@ -1,7 +1,7 @@ package createinvite import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" diff --git a/stdcommands/currentshard/currentshard.go b/stdcommands/currentshard/currentshard.go index e3b3098d23..6fab28f689 100644 --- a/stdcommands/currentshard/currentshard.go +++ b/stdcommands/currentshard/currentshard.go @@ -3,7 +3,7 @@ package currentshard import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/commands" diff --git a/stdcommands/currenttime/currenttime.go b/stdcommands/currenttime/currenttime.go index c0f1420b42..aa42b86abf 100644 --- a/stdcommands/currenttime/currenttime.go +++ b/stdcommands/currenttime/currenttime.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/tkuchiki/go-timezone" ) diff --git a/stdcommands/customembed/customembed.go b/stdcommands/customembed/customembed.go index 20a0a56f7d..b31e531199 100644 --- a/stdcommands/customembed/customembed.go +++ b/stdcommands/customembed/customembed.go @@ -3,7 +3,7 @@ package customembed import ( "encoding/json" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/dcallvoice/dcallvoice.go b/stdcommands/dcallvoice/dcallvoice.go index 4af4f6321f..4148340bf2 100644 --- a/stdcommands/dcallvoice/dcallvoice.go +++ b/stdcommands/dcallvoice/dcallvoice.go @@ -3,7 +3,7 @@ package dcallvoice import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" @@ -21,13 +21,15 @@ var Command = &commands.YAGCommand{ vcs := make([]*discordgo.VoiceState, 0) - guilds := bot.State.GuildsSlice(true) - - for _, g := range guilds { - vc := g.VoiceState(true, common.BotUser.ID) - if vc != nil { - vcs = append(vcs, vc) - go bot.ShardManager.SessionForGuild(g.ID).GatewayManager.ChannelVoiceLeave(g.ID) + processShards := bot.ReadyTracker.GetProcessShards() + for _, shard := range processShards { + guilds := bot.State.GetShardGuilds(int64(shard)) + for _, g := range guilds { + vc := g.GetVoiceState(common.BotUser.ID) + if vc != nil { + vcs = append(vcs, vc) + go bot.ShardManager.SessionForGuild(g.ID).GatewayManager.ChannelVoiceLeave(g.ID) + } } } diff --git a/stdcommands/define/define.go b/stdcommands/define/define.go index 3bcb1c7567..c58dfb4bb8 100644 --- a/stdcommands/define/define.go +++ b/stdcommands/define/define.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/dpatrie/urbandictionary" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/dogfact/dogfact.go b/stdcommands/dogfact/dogfact.go index d89c4a334f..c6a199cccd 100644 --- a/stdcommands/dogfact/dogfact.go +++ b/stdcommands/dogfact/dogfact.go @@ -3,7 +3,7 @@ package dogfact import ( "math/rand" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/findserver/findserver.go b/stdcommands/findserver/findserver.go index c3630e408a..c1b65a73f2 100644 --- a/stdcommands/findserver/findserver.go +++ b/stdcommands/findserver/findserver.go @@ -4,9 +4,8 @@ import ( "fmt" "strings" - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot/models" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/stdcommands/util" @@ -66,48 +65,17 @@ var Command = &commands.YAGCommand{ }), } -func CheckGuild(gs *dstate.GuildState, nameToMatch string, userToMatch int64) *Candidate { +func CheckGuild(gs *dstate.GuildSet, nameToMatch string, userToMatch int64) *Candidate { if nameToMatch != "" { - gl := strings.ToLower(gs.Guild.Name) + gl := strings.ToLower(gs.Name) if gl != nameToMatch && !strings.Contains(gl, nameToMatch) { return nil } } - foundUser := false - if userToMatch != 0 { - for _, ms := range gs.Members { - if ms.ID == userToMatch { - foundUser = true - break - } - } - - if !foundUser { - return nil - } - } - candidate := &Candidate{ ID: gs.ID, - Name: gs.Guild.Name, - } - - if foundUser { - if gs.Guild.OwnerID == userToMatch { - candidate.Owner = true - } - - perms, _ := gs.MemberPermissions(false, 0, userToMatch) - if perms&discordgo.PermissionAdministrator != 0 { - candidate.Admin = true - } - - if perms&discordgo.PermissionManageServer != 0 || perms&discordgo.PermissionKickMembers != 0 || perms&discordgo.PermissionBanMembers != 0 { - candidate.Mod = true - } - - candidate.UserMatch = true + Name: gs.Name, } return candidate diff --git a/stdcommands/globalrl/globalrl.go b/stdcommands/globalrl/globalrl.go index 5054884068..d6880e5aa9 100644 --- a/stdcommands/globalrl/globalrl.go +++ b/stdcommands/globalrl/globalrl.go @@ -3,7 +3,7 @@ package globalrl import ( "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/stdcommands/guildunavailable/guildunavilable.go b/stdcommands/guildunavailable/guildunavilable.go index cb62ef4e8c..5389493687 100644 --- a/stdcommands/guildunavailable/guildunavilable.go +++ b/stdcommands/guildunavailable/guildunavilable.go @@ -3,7 +3,7 @@ package guildunavailable import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/commands" ) @@ -23,6 +23,6 @@ var Command = &commands.YAGCommand{ return "Uh oh", err } - return fmt.Sprintf("Guild (%d) unavailable: %v", guild.ID, guild.Unavailable), nil + return fmt.Sprintf("Guild (%d) unavailable: %v", guild.ID, !guild.Available), nil }, } diff --git a/stdcommands/howlongtobeat/howlongtobeat.go b/stdcommands/howlongtobeat/howlongtobeat.go index 80015f0190..afb01e7c9f 100644 --- a/stdcommands/howlongtobeat/howlongtobeat.go +++ b/stdcommands/howlongtobeat/howlongtobeat.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot/paginatedmessages" "github.com/jonas747/yagpdb/commands" @@ -35,11 +35,11 @@ var ( //Command var needs a comment for lint :) var Command = &commands.YAGCommand{ - CmdCategory: commands.CategoryFun, - Name: "HowLongToBeat", - Aliases: []string{"hltb"}, - RequiredArgs: 1, - Description: "Game information based on query from howlongtobeat.com.\nResults are sorted by popularity, it's their default. Without -p returns the first result.\nSwitch -p gives paginated output using Levenshtein distance sorting max 20 results.", + CmdCategory: commands.CategoryFun, + Name: "HowLongToBeat", + Aliases: []string{"hltb"}, + RequiredArgs: 1, + Description: "Game information based on query from howlongtobeat.com.\nResults are sorted by popularity, it's their default. Without -p returns the first result.\nSwitch -p gives paginated output using Levenshtein distance sorting max 20 results.", DefaultEnabled: true, SlashCommandEnabled: true, Arguments: []*dcmd.ArgDef{ @@ -144,4 +144,3 @@ func embedCreator(hltbQuery []hltb, i int, paginated bool) *discordgo.MessageEmb func normaliseTitle(t string) string { return strings.Join(strings.Fields(t), " ") } - diff --git a/stdcommands/info/info.go b/stdcommands/info/info.go index 2763ad5aba..9df5fffa47 100644 --- a/stdcommands/info/info.go +++ b/stdcommands/info/info.go @@ -3,7 +3,7 @@ package info import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" ) diff --git a/stdcommands/invite/invite.go b/stdcommands/invite/invite.go index 977b971c1c..5b9cbb8fd9 100644 --- a/stdcommands/invite/invite.go +++ b/stdcommands/invite/invite.go @@ -1,7 +1,7 @@ package invite import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" ) diff --git a/stdcommands/leaveserver/leaveserver.go b/stdcommands/leaveserver/leaveserver.go index 85ec8ee1e7..3d02c1aa4f 100644 --- a/stdcommands/leaveserver/leaveserver.go +++ b/stdcommands/leaveserver/leaveserver.go @@ -1,7 +1,7 @@ package leaveserver import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/listroles/listroles.go b/stdcommands/listroles/listroles.go index a48a90dc04..075ac2d6e1 100644 --- a/stdcommands/listroles/listroles.go +++ b/stdcommands/listroles/listroles.go @@ -2,11 +2,9 @@ package listroles import ( "fmt" - "sort" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/commands" ) @@ -26,13 +24,10 @@ var Command = &commands.YAGCommand{ noMana = true } - data.GuildData.GS.Lock() - defer data.GuildData.GS.Unlock() - - sort.Sort(dutil.Roles(data.GuildData.GS.Guild.Roles)) + // sort.Sort(dutil.Roles(data.GuildData.GS.Roles)) counter := 0 - for _, r := range data.GuildData.GS.Guild.Roles { + for _, r := range data.GuildData.GS.Roles { if noMana && r.Managed { continue } else { @@ -42,7 +37,7 @@ var Command = &commands.YAGCommand{ } } outFinal = fmt.Sprintf("Total role count: %d\n", counter) - outFinal += fmt.Sprintf("%s", "(ME = mention everyone perms)\n") + outFinal += "(ME = mention everyone perms)\n" outFinal += out return outFinal, nil }, diff --git a/stdcommands/memstats/memstats.go b/stdcommands/memstats/memstats.go index f94b24ed24..7d68c9503c 100644 --- a/stdcommands/memstats/memstats.go +++ b/stdcommands/memstats/memstats.go @@ -5,7 +5,7 @@ import ( "encoding/json" "runtime" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/stdcommands/mentionrole/mentionrole.go b/stdcommands/mentionrole/mentionrole.go deleted file mode 100644 index 3dabee4194..0000000000 --- a/stdcommands/mentionrole/mentionrole.go +++ /dev/null @@ -1,152 +0,0 @@ -package mentionrole - -import ( - "encoding/json" - "strconv" - "strings" - "time" - - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" - "github.com/jonas747/yagpdb/bot" - "github.com/jonas747/yagpdb/commands" - "github.com/jonas747/yagpdb/common" - "github.com/jonas747/yagpdb/common/scheduledevents2" - seventsmodels "github.com/jonas747/yagpdb/common/scheduledevents2/models" - "github.com/sirupsen/logrus" -) - -type EvtData struct { - GuildID int64 - RoleID int64 -} - -func AddScheduledEventListener() { - scheduledevents2.RegisterHandler("reset_mentionable_role", EvtData{}, handleResetMentionableRole) - scheduledevents2.RegisterLegacyMigrater("reset_mentionable_role", handleMigrateResetMentionable) -} - -func handleMigrateResetMentionable(t time.Time, data string) error { - var parsedData EvtData - err := json.Unmarshal([]byte(data), &parsedData) - if err != nil { - logrus.WithError(err).Error("Failed unmarshaling reset_mentionable_role data: ", data) - return nil - } - - return scheduledevents2.ScheduleEvent("reset_mentionable_role", parsedData.GuildID, t, parsedData) -} - -func handleResetMentionableRole(evt *seventsmodels.ScheduledEvent, dataInterface interface{}) (retry bool, err error) { - data := dataInterface.(*EvtData) - - gs := bot.State.Guild(true, evt.GuildID) - if gs == nil { - return false, nil - } - - role := gs.RoleCopy(true, data.RoleID) - - if role == nil { - return false, nil // Assume role was deleted or something in the meantime - } - - _, err = common.BotSession.GuildRoleEdit(gs.ID, role.ID, role.Name, role.Color, role.Hoist, role.Permissions, false) - return scheduledevents2.CheckDiscordErrRetry(err), err -} - -var Command = &commands.YAGCommand{ - CmdCategory: commands.CategoryTool, - Name: "MentionRole", - Aliases: []string{"mrole"}, - Description: "Sets a role to mentionable, mentions the role, and then sets it back", - LongDescription: "Requires the manage roles permission and the bot being above the mentioned role", - HideFromHelp: true, - RequiredArgs: 1, - Arguments: []*dcmd.ArgDef{ - {Name: "Role", Type: dcmd.String}, - {Name: "Message", Type: dcmd.String, Default: ""}, - }, - ArgSwitches: []*dcmd.ArgDef{ - {Name: "channel", Help: "Optional channel to send in", Type: dcmd.Channel}, - }, - RunFunc: cmdFuncMentionRole, - GuildScopeCooldown: 10, -} - -func cmdFuncMentionRole(data *dcmd.Data) (interface{}, error) { - if ok, err := bot.AdminOrPermMS(data.ChannelID, data.GuildData.MS, discordgo.PermissionManageRoles); err != nil { - return "Failed checking perms", err - } else if !ok { - return "You need manage roles perms to use this command", nil - } - - roleS := data.Args[0].Str() - role := findRoleByName(data.GuildData.GS, roleS) - - //if we did not find a match yet try to match ID - if role == nil { - parsedNumber, parseErr := strconv.ParseInt(roleS, 10, 64) - if parseErr == nil { - // was a number, try looking by id - role = data.GuildData.GS.RoleCopy(true, parsedNumber) - } - } - - if role == nil { - return "No role with the name or ID`" + roleS + "` found", nil - } - - cID := data.GuildData.CS.ID - c := data.Switch("channel") - if c.Value != nil { - cID = c.Value.(*dstate.ChannelState).ID - - perms, err := data.GuildData.GS.MemberPermissions(true, cID, data.Author.ID) - if err != nil { - return nil, err - } - - if perms&discordgo.PermissionSendMessages != discordgo.PermissionSendMessages || perms&discordgo.PermissionReadMessages != discordgo.PermissionReadMessages { - return "You do not have permissions to send messages there", nil - } - } - - _, err := common.BotSession.GuildRoleEdit(data.GuildData.GS.ID, role.ID, role.Name, role.Color, role.Hoist, role.Permissions, true) - if err != nil { - return nil, err - } - - _, err = common.BotSession.ChannelMessageSendComplex(cID, &discordgo.MessageSend{ - Content: "<@&" + discordgo.StrID(role.ID) + "> " + data.Args[1].Str(), - AllowedMentions: discordgo.AllowedMentions{ - Roles: []int64{role.ID}, - }, - }) - - if err != nil { - return nil, err - } - - err = scheduledevents2.ScheduleEvent("reset_mentionable_role", data.GuildData.GS.ID, time.Now().Add(time.Second*10), &EvtData{ - GuildID: data.GuildData.GS.ID, - RoleID: role.ID, - }) - return nil, err -} - -func findRoleByName(gs *dstate.GuildState, name string) *discordgo.Role { - var role *discordgo.Role - - gs.RLock() - defer gs.RUnlock() - for _, r := range gs.Guild.Roles { - if strings.EqualFold(r.Name, name) { - role = r - break - } - } - - return role -} diff --git a/stdcommands/ping/ping.go b/stdcommands/ping/ping.go index 8d0853f91d..ac96c08864 100644 --- a/stdcommands/ping/ping.go +++ b/stdcommands/ping/ping.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/stdcommands/poll/poll.go b/stdcommands/poll/poll.go index 8d5dfa12f9..c43b30d990 100644 --- a/stdcommands/poll/poll.go +++ b/stdcommands/poll/poll.go @@ -2,7 +2,7 @@ package poll import ( "emperror.dev/errors" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -54,9 +54,9 @@ func createPoll(data *dcmd.Data) (interface{}, error) { description += pollReactions[i] + " " + option.Str() } - authorName := data.GuildData.MS.Nick + authorName := data.GuildData.MS.Member.Nick if authorName == "" { - authorName = data.GuildData.MS.Username + authorName = data.GuildData.MS.User.Username } response := discordgo.MessageEmbed{ @@ -65,7 +65,7 @@ func createPoll(data *dcmd.Data) (interface{}, error) { Color: 0x65f442, Author: &discordgo.MessageEmbedAuthor{ Name: authorName, - IconURL: discordgo.EndpointUserAvatar(data.GuildData.MS.ID, data.Author.Avatar), + IconURL: discordgo.EndpointUserAvatar(data.GuildData.MS.User.ID, data.Author.Avatar), }, } @@ -77,7 +77,7 @@ func createPoll(data *dcmd.Data) (interface{}, error) { if err != nil { return nil, errors.WrapIf(err, "failed to add poll description") } - for i, _ := range options { + for i := range options { common.BotSession.MessageReactionAdd(pollMsg.ChannelID, pollMsg.ID, pollReactions[i]) } return nil, nil diff --git a/stdcommands/roll/roll.go b/stdcommands/roll/roll.go index ba1edaa3e9..db4871375e 100644 --- a/stdcommands/roll/roll.go +++ b/stdcommands/roll/roll.go @@ -5,7 +5,7 @@ import ( "math/rand" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/dice" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/setstatus/setstatus.go b/stdcommands/setstatus/setstatus.go index 224a733615..8a0a02f139 100644 --- a/stdcommands/setstatus/setstatus.go +++ b/stdcommands/setstatus/setstatus.go @@ -1,7 +1,7 @@ package setstatus import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/simpleembed/simpleembed.go b/stdcommands/simpleembed/simpleembed.go index 45389aa9dd..7c09974fbe 100644 --- a/stdcommands/simpleembed/simpleembed.go +++ b/stdcommands/simpleembed/simpleembed.go @@ -4,9 +4,9 @@ import ( "strconv" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -89,7 +89,7 @@ var Command = &commands.YAGCommand{ if c.Value != nil { cID = c.Value.(*dstate.ChannelState).ID - hasPerms, err := bot.AdminOrPermMS(cID, data.GuildData.MS, discordgo.PermissionSendMessages|discordgo.PermissionReadMessages) + hasPerms, err := bot.AdminOrPermMS(data.GuildData.GS.ID, cID, data.GuildData.MS, discordgo.PermissionSendMessages|discordgo.PermissionReadMessages) if err != nil { return "Failed checking permissions, please try again or join the support server.", err } diff --git a/stdcommands/sleep/sleep.go b/stdcommands/sleep/sleep.go index de36dcf2eb..005078202b 100644 --- a/stdcommands/sleep/sleep.go +++ b/stdcommands/sleep/sleep.go @@ -3,7 +3,7 @@ package sleep import ( "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/stdcommands/util" ) diff --git a/stdcommands/statedbg/statedbg.go b/stdcommands/statedbg/statedbg.go new file mode 100644 index 0000000000..f3b26ebc4d --- /dev/null +++ b/stdcommands/statedbg/statedbg.go @@ -0,0 +1,89 @@ +package statedbg + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/discordgo" + "github.com/jonas747/yagpdb/bot" + "github.com/jonas747/yagpdb/commands" + "github.com/jonas747/yagpdb/stdcommands/util" +) + +func Commands() *dcmd.Container { + container := commands.CommandSystem.Root.Sub("state") + container.Description = "utilities for debugging state stuff" + container.AddMidlewares(util.RequireBotAdmin) + container.AddCommand(getGuild, getGuild.GetTrigger()) + container.AddCommand(getMember, getMember.GetTrigger()) + + return container +} + +var getGuild = &commands.YAGCommand{ + CmdCategory: commands.CategoryDebug, + Name: "guild", + Description: "Responds with state debug info", + HideFromHelp: true, + RunFunc: cmdFuncGetGuild, +} + +func cmdFuncGetGuild(data *dcmd.Data) (interface{}, error) { + serialized, err := json.MarshalIndent(data.GuildData.GS, "", " ") + if err != nil { + return nil, err + } + + send := &discordgo.MessageSend{ + File: &discordgo.File{ + Name: fmt.Sprintf("guild_%d.json", data.GuildData.GS.ID), + ContentType: "application/json", + Reader: bytes.NewReader(serialized), + }, + } + + return send, nil +} + +var getMember = &commands.YAGCommand{ + CmdCategory: commands.CategoryDebug, + Name: "member", + Description: "Responds with state debug info", + Arguments: []*dcmd.ArgDef{ + {Name: "Target", Type: dcmd.BigInt}, + }, + ArgSwitches: []*dcmd.ArgDef{ + {Name: "fetch", Help: "fetch the member if not in state"}, + }, + RequiredArgs: 1, + HideFromHelp: true, + RunFunc: cmdFuncGetMember, +} + +func cmdFuncGetMember(data *dcmd.Data) (interface{}, error) { + + targetID := data.Args[0].Int64() + + ms := bot.State.GetMember(data.GuildData.GS.ID, targetID) + didFetch := false + if ms == nil && data.Switch("fetch").Bool() { + didFetch = true + fms, err := bot.GetMember(data.GuildData.GS.ID, targetID) + if err != nil { + return nil, err + } + + ms = fms + } else if ms == nil { + return "Member not in state :(", nil + } + + serialized, err := json.MarshalIndent(ms, "", " ") + if err != nil { + return nil, err + } + + return fmt.Sprintf("Fetched: %v, ```json\n%s\n```", didFetch, string(serialized)), nil +} diff --git a/stdcommands/stateinfo/stateinfo.go b/stdcommands/stateinfo/stateinfo.go index 077717ac5e..421f5a4d20 100644 --- a/stdcommands/stateinfo/stateinfo.go +++ b/stdcommands/stateinfo/stateinfo.go @@ -3,7 +3,7 @@ package stateinfo import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" @@ -24,39 +24,37 @@ func cmdFuncStateInfo(data *dcmd.Data) (interface{}, error) { guildChannel := 0 totalMessages := 0 - state := bot.State - state.RLock() - totalChannels := len(state.Channels) - totalGuilds = len(state.Guilds) - gCop := state.GuildsSlice(false) - state.RUnlock() + // state := bot.State + // totalChannels := len(state.Channels) + // totalGuilds = len(state.Guilds) + // gCop := state.GuildsSlice(false) - for _, g := range gCop { - g.RLock() + shards := bot.ReadyTracker.GetProcessShards() - guildChannel += len(g.Channels) - totalMembers += len(g.Members) + for _, shard := range shards { - for _, cState := range g.Channels { - totalMessages += len(cState.Messages) + guilds := bot.State.GetShardGuilds(int64(shard)) + + for _, g := range guilds { + + guildChannel += len(g.Channels) } - g.RUnlock() } - stats := bot.State.StateStats() + // stats := bot.State.StateStats() embed := &discordgo.MessageEmbed{ Title: "State size", Fields: []*discordgo.MessageEmbedField{ - &discordgo.MessageEmbedField{Name: "Guilds", Value: fmt.Sprint(totalGuilds), Inline: true}, - &discordgo.MessageEmbedField{Name: "Members", Value: fmt.Sprintf("%d", totalMembers), Inline: true}, - &discordgo.MessageEmbedField{Name: "Messages", Value: fmt.Sprintf("%d", totalMessages), Inline: true}, - &discordgo.MessageEmbedField{Name: "Guild Channels", Value: fmt.Sprintf("%d", guildChannel), Inline: true}, - &discordgo.MessageEmbedField{Name: "Total Channels", Value: fmt.Sprintf("%d", totalChannels), Inline: true}, - &discordgo.MessageEmbedField{Name: "Cache Hits/Misses", Value: fmt.Sprintf("%d - %d", stats.CacheHits, stats.CacheMisses), Inline: true}, - &discordgo.MessageEmbedField{Name: "Members evicted total", Value: fmt.Sprintf("%d", stats.MembersRemovedTotal), Inline: true}, - &discordgo.MessageEmbedField{Name: "Cache evicted total", Value: fmt.Sprintf("%d", stats.UserCachceEvictedTotal), Inline: true}, - &discordgo.MessageEmbedField{Name: "Messages removed total", Value: fmt.Sprintf("%d", stats.MessagesRemovedTotal), Inline: true}, + {Name: "Guilds", Value: fmt.Sprint(totalGuilds), Inline: true}, + {Name: "Members", Value: fmt.Sprintf("%d", totalMembers), Inline: true}, + {Name: "Messages", Value: fmt.Sprintf("%d", totalMessages), Inline: true}, + {Name: "Guild Channels", Value: fmt.Sprintf("%d", guildChannel), Inline: true}, + // {Name: "Total Channels", Value: fmt.Sprintf("%d", totalChannels), Inline: true}, + // {Name: "Cache Hits/Misses", Value: fmt.Sprintf("%d - %d", stats.CacheHits, stats.CacheMisses), Inline: true}, + // {Name: "Members evicted total", Value: fmt.Sprintf("%d", stats.MembersRemovedTotal), Inline: true}, + // {Name: "Cache evicted total", Value: fmt.Sprintf("%d", stats.UserCachceEvictedTotal), Inline: true}, + // {Name: "Messages removed total", Value: fmt.Sprintf("%d", stats.MessagesRemovedTotal), Inline: true}, }, } diff --git a/stdcommands/stdcommands.go b/stdcommands/stdcommands.go index 392a24c01b..0d0035aedb 100644 --- a/stdcommands/stdcommands.go +++ b/stdcommands/stdcommands.go @@ -20,20 +20,20 @@ import ( "github.com/jonas747/yagpdb/stdcommands/dogfact" "github.com/jonas747/yagpdb/stdcommands/findserver" "github.com/jonas747/yagpdb/stdcommands/globalrl" - "github.com/jonas747/yagpdb/stdcommands/howlongtobeat" "github.com/jonas747/yagpdb/stdcommands/guildunavailable" + "github.com/jonas747/yagpdb/stdcommands/howlongtobeat" "github.com/jonas747/yagpdb/stdcommands/info" "github.com/jonas747/yagpdb/stdcommands/invite" "github.com/jonas747/yagpdb/stdcommands/leaveserver" "github.com/jonas747/yagpdb/stdcommands/listroles" "github.com/jonas747/yagpdb/stdcommands/memstats" - "github.com/jonas747/yagpdb/stdcommands/mentionrole" "github.com/jonas747/yagpdb/stdcommands/ping" "github.com/jonas747/yagpdb/stdcommands/poll" "github.com/jonas747/yagpdb/stdcommands/roll" "github.com/jonas747/yagpdb/stdcommands/setstatus" "github.com/jonas747/yagpdb/stdcommands/simpleembed" "github.com/jonas747/yagpdb/stdcommands/sleep" + "github.com/jonas747/yagpdb/stdcommands/statedbg" "github.com/jonas747/yagpdb/stdcommands/stateinfo" "github.com/jonas747/yagpdb/stdcommands/throw" "github.com/jonas747/yagpdb/stdcommands/toggledbg" @@ -86,7 +86,6 @@ func (p *Plugin) AddCommands() { customembed.Command, simpleembed.Command, currenttime.Command, - mentionrole.Command, listroles.Command, memstats.Command, wouldyourather.Command, @@ -119,11 +118,12 @@ func (p *Plugin) AddCommands() { globalrl.Command, ) + statedbg.Commands() + } func (p *Plugin) BotInit() { eventsystem.AddHandlerAsyncLastLegacy(p, ping.HandleMessageCreate, eventsystem.EventMessageCreate) - mentionrole.AddScheduledEventListener() } func RegisterPlugin() { diff --git a/stdcommands/throw/throw.go b/stdcommands/throw/throw.go index d999edfd93..ed2d1d849d 100644 --- a/stdcommands/throw/throw.go +++ b/stdcommands/throw/throw.go @@ -4,7 +4,7 @@ import ( "fmt" "math/rand" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/toggledbg/toggledbg.go b/stdcommands/toggledbg/toggledbg.go index 65229f6e24..42088bc5f3 100644 --- a/stdcommands/toggledbg/toggledbg.go +++ b/stdcommands/toggledbg/toggledbg.go @@ -4,7 +4,7 @@ import ( "github.com/jonas747/yagpdb/common" "github.com/sirupsen/logrus" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/stdcommands/util" ) diff --git a/stdcommands/topcommands/topcommands.go b/stdcommands/topcommands/topcommands.go index f4b0b403f0..1f5a01c4cd 100644 --- a/stdcommands/topcommands/topcommands.go +++ b/stdcommands/topcommands/topcommands.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" ) diff --git a/stdcommands/topevents/topevents.go b/stdcommands/topevents/topevents.go index 5aa0d4be04..bda15fb067 100644 --- a/stdcommands/topevents/topevents.go +++ b/stdcommands/topevents/topevents.go @@ -4,7 +4,7 @@ import ( "fmt" "sort" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" "github.com/jonas747/yagpdb/commands" diff --git a/stdcommands/topgames/topgames.go b/stdcommands/topgames/topgames.go index 118b0491a4..f3f57af870 100644 --- a/stdcommands/topgames/topgames.go +++ b/stdcommands/topgames/topgames.go @@ -4,8 +4,8 @@ import ( "fmt" "sort" - "github.com/jonas747/dcmd/v2" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dcmd/v3" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" ) @@ -41,9 +41,12 @@ func cmdFuncTopCommands(data *dcmd.Data) (interface{}, error) { fastResult := make(map[string]int) if all { - guilds := bot.State.GuildsSlice(true) - for _, g := range guilds { - checkGuild(fastResult, g) + processShards := bot.ReadyTracker.GetProcessShards() + for _, shard := range processShards { + guilds := bot.State.GetShardGuilds(int64(shard)) + for _, g := range guilds { + checkGuild(fastResult, g) + } } } else { checkGuild(fastResult, data.GuildData.GS) @@ -75,22 +78,24 @@ func cmdFuncTopCommands(data *dcmd.Data) (interface{}, error) { return out, nil } -func checkGuild(dst map[string]int, gs *dstate.GuildState) { - gs.RLock() - defer gs.RUnlock() +func checkGuild(dst map[string]int, gs *dstate.GuildSet) { - for _, ms := range gs.Members { - if !ms.PresenceSet || ms.PresenceGame == nil || ms.PresenceGame.Name == "" { - continue - } + bot.State.IterateMembers(gs.ID, func(chunk []*dstate.MemberState) bool { + for _, ms := range chunk { + if ms.Presence == nil || ms.Presence.Game == nil || ms.Presence.Game.Name == "" { + continue + } - if ms.Bot { - continue + if ms.User.Bot { + continue + } + + name := ms.Presence.Game.Name + dst[name]++ } - name := ms.PresenceGame.Name - dst[name]++ - } + return true + }) } type TopGameResult struct { diff --git a/stdcommands/topic/topic.go b/stdcommands/topic/topic.go index d2079d9283..0179dd7209 100644 --- a/stdcommands/topic/topic.go +++ b/stdcommands/topic/topic.go @@ -1,10 +1,9 @@ package topic import ( - "math/rand" - - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" + "math/rand" ) var Command = &commands.YAGCommand{ diff --git a/stdcommands/topservers/topservers.go b/stdcommands/topservers/topservers.go index b7f0ed7553..f8cf229d98 100644 --- a/stdcommands/topservers/topservers.go +++ b/stdcommands/topservers/topservers.go @@ -3,7 +3,7 @@ package topservers import ( "fmt" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot/models" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/stdcommands/unbanserver/unbanserver.go b/stdcommands/unbanserver/unbanserver.go index 5b7a2b8e53..6dfb219112 100644 --- a/stdcommands/unbanserver/unbanserver.go +++ b/stdcommands/unbanserver/unbanserver.go @@ -1,7 +1,7 @@ package unbanserver import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/stdcommands/util" diff --git a/stdcommands/undelete/undelete.go b/stdcommands/undelete/undelete.go index b34ee6deaf..38c1060dfd 100644 --- a/stdcommands/undelete/undelete.go +++ b/stdcommands/undelete/undelete.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -28,11 +28,10 @@ var Command = &commands.YAGCommand{ allUsers := data.Switch("a").Value != nil && data.Switch("a").Value.(bool) targetUser := data.Switch("u").Int64() - channel := data.GuildData.CS if data.Switch("channel").Value != nil { - channel = data.Switch("channel").Value.(*dstate.ChannelState) + channel := data.Switch("channel").Value.(*dstate.ChannelState) - ok, err := bot.AdminOrPermMS(channel.ID, data.GuildData.MS, discordgo.PermissionReadMessages) + ok, err := bot.AdminOrPermMS(data.GuildData.GS.ID, channel.ID, data.GuildData.MS, discordgo.PermissionReadMessages) if err != nil { return nil, err } else if !ok { @@ -41,7 +40,7 @@ var Command = &commands.YAGCommand{ } if allUsers || targetUser != 0 { - ok, err := bot.AdminOrPermMS(channel.ID, data.GuildData.MS, discordgo.PermissionManageMessages) + ok, err := bot.AdminOrPermMS(data.GuildData.GS.ID, data.ChannelID, data.GuildData.MS, discordgo.PermissionManageMessages) if err != nil { return nil, err } else if !ok && targetUser == 0 { @@ -54,12 +53,8 @@ var Command = &commands.YAGCommand{ resp := "Up to 10 last deleted messages (last hour or 12 hours for premium): \n\n" numFound := 0 - data.GuildData.GS.RLock() - defer data.GuildData.GS.RUnlock() - - for i := len(channel.Messages) - 1; i >= 0 && numFound < 10; i-- { - msg := channel.Messages[i] - + messages := bot.State.GetMessages(data.GuildData.GS.ID, data.GuildData.CS.ID, &dstate.MessagesQuery{Limit: 100}) + for _, msg := range messages { if !msg.Deleted { continue } @@ -73,17 +68,17 @@ var Command = &commands.YAGCommand{ } precision := common.DurationPrecisionHours - if time.Since(msg.ParsedCreated) < time.Hour { + if time.Since(msg.ParsedCreatedAt) < time.Hour { precision = common.DurationPrecisionMinutes - if time.Since(msg.ParsedCreated) < time.Minute { + if time.Since(msg.ParsedCreatedAt) < time.Minute { precision = common.DurationPrecisionSeconds } } // Match found! - timeSince := common.HumanizeDuration(precision, time.Since(msg.ParsedCreated)) + timeSince := common.HumanizeDuration(precision, time.Since(msg.ParsedCreatedAt)) - resp += fmt.Sprintf("`%s ago (%s)` **%s**#%s (ID %d): %s\n\n", timeSince, msg.ParsedCreated.UTC().Format(time.ANSIC), msg.Author.Username, msg.Author.Discriminator, msg.Author.ID, msg.ContentWithMentionsReplaced()) + resp += fmt.Sprintf("`%s ago (%s)` **%s**#%s (ID %d): %s\n\n", timeSince, msg.ParsedCreatedAt.UTC().Format(time.ANSIC), msg.Author.Username, msg.Author.Discriminator, msg.Author.ID, msg.ContentWithMentionsReplaced()) numFound++ } diff --git a/stdcommands/util/debugcmd.go b/stdcommands/util/debugcmd.go index 7ad9461161..2328b46a4a 100644 --- a/stdcommands/util/debugcmd.go +++ b/stdcommands/util/debugcmd.go @@ -1,7 +1,7 @@ package util import ( - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" diff --git a/stdcommands/viewperms/viewperms.go b/stdcommands/viewperms/viewperms.go index f88ae6cc32..4f350f2908 100644 --- a/stdcommands/viewperms/viewperms.go +++ b/stdcommands/viewperms/viewperms.go @@ -4,9 +4,9 @@ import ( "fmt" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" @@ -17,7 +17,7 @@ var Command = &commands.YAGCommand{ Name: "ViewPerms", Description: "Shows you or the target's permissions in this channel", Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "target", Type: dcmd.UserID, Default: int64(0)}, + {Name: "target", Type: dcmd.UserID, Default: int64(0)}, }, RunFunc: func(data *dcmd.Data) (interface{}, error) { var target *dstate.MemberState @@ -36,12 +36,12 @@ var Command = &commands.YAGCommand{ } } - perms, err := data.GuildData.GS.MemberPermissionsMS(true, data.GuildData.CS.ID, target) + perms, err := data.GuildData.GS.GetMemberPermissions(data.GuildData.CS.ID, target.User.ID, target.Member.Roles) if err != nil { return "Unable to calculate perms", err } humanized := common.HumanizePermissions(int64(perms)) - return fmt.Sprintf("Perms of %s in this channel\n`%d`\n%s", target.Username, perms, strings.Join(humanized, ", ")), nil + return fmt.Sprintf("Perms of %s in this channel\n`%d`\n%s", target.User.Username, perms, strings.Join(humanized, ", ")), nil }, } diff --git a/stdcommands/weather/weather.go b/stdcommands/weather/weather.go index 851420a948..c13cd3de57 100644 --- a/stdcommands/weather/weather.go +++ b/stdcommands/weather/weather.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/lunixbochs/vtclean" ) diff --git a/stdcommands/wouldyourather/wouldyourather.go b/stdcommands/wouldyourather/wouldyourather.go index 27e22af685..9736441fb9 100644 --- a/stdcommands/wouldyourather/wouldyourather.go +++ b/stdcommands/wouldyourather/wouldyourather.go @@ -6,7 +6,7 @@ import ( "emperror.dev/errors" "github.com/PuerkitoBio/goquery" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/yagpdb/commands" "github.com/jonas747/yagpdb/common" ) diff --git a/stdcommands/xkcd/xkcd.go b/stdcommands/xkcd/xkcd.go index d9a4f4a7db..a671448010 100644 --- a/stdcommands/xkcd/xkcd.go +++ b/stdcommands/xkcd/xkcd.go @@ -7,7 +7,7 @@ import ( "math/rand" "net/http" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/commands" ) diff --git a/stdcommands/yagstatus/yagstatus.go b/stdcommands/yagstatus/yagstatus.go index 89cc4e468b..a964173cfa 100644 --- a/stdcommands/yagstatus/yagstatus.go +++ b/stdcommands/yagstatus/yagstatus.go @@ -5,7 +5,7 @@ import ( "runtime" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" diff --git a/streaming/bot.go b/streaming/bot.go index 82949a9c26..6f9f77f087 100644 --- a/streaming/bot.go +++ b/streaming/bot.go @@ -9,7 +9,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -35,17 +35,16 @@ func (p *Plugin) BotInit() { func HandleUpdateStreaming(event *pubsub.Event) { logger.Info("Received update streaming event ", event.TargetGuild) - gs := bot.State.Guild(true, event.TargetGuildInt) + gs := bot.State.GetGuild(event.TargetGuildInt) if gs == nil { return } - gs.UserCacheDel(CacheKeyConfig) - + cachedConfig.Delete(event.TargetGuildInt) CheckGuildFull(gs, true) } -func CheckGuildFull(gs *dstate.GuildState, fetchMembers bool) { +func CheckGuildFull(gs *dstate.GuildSet, fetchMembers bool) { config, err := GetConfig(gs.ID) if err != nil { @@ -57,47 +56,45 @@ func CheckGuildFull(gs *dstate.GuildState, fetchMembers bool) { return } - gs.RLock() - var wg sync.WaitGroup - slowCheck := make([]*dstate.MemberState, 0, len(gs.Members)) + slowCheck := make([]*dstate.MemberState, 0) err = common.RedisPool.Do(radix.WithConn(KeyCurrentlyStreaming(gs.ID), func(conn radix.Conn) error { - for _, ms := range gs.Members { - if !ms.MemberSet || !ms.PresenceSet { + bot.State.IterateMembers(gs.ID, func(chunk []*dstate.MemberState) bool { + for _, ms := range chunk { + if ms.Member == nil || ms.Presence == nil { - if ms.PresenceSet && fetchMembers { - // If were fetching members, then fetch the missing members - // TODO: Maybe use the gateway request for this? - slowCheck = append(slowCheck, ms) - wg.Add(1) - go func(gID, uID int64) { - bot.GetMember(gID, uID) - wg.Done() + if ms.Presence != nil && fetchMembers { + // If were fetching members, then fetch the missing members + // TODO: Maybe use the gateway request for this? + slowCheck = append(slowCheck, ms) + wg.Add(1) + go func(gID, uID int64) { + bot.GetMember(gID, uID) + wg.Done() - }(gs.ID, ms.ID) - } + }(gs.ID, ms.User.ID) + } - continue - } + continue + } - gs.RUnlock() - err = CheckPresence(conn, config, ms, gs) - gs.RLock() + err = CheckPresence(conn, config, ms, gs) - if err != nil { - logger.WithError(err).Error("Error checking presence") - continue + if err != nil { + logger.WithError(err).Error("Error checking presence") + continue + } } - } + + return true + }) return nil })) - gs.RUnlock() - if fetchMembers { wg.Wait() } else { @@ -106,18 +103,14 @@ func CheckGuildFull(gs *dstate.GuildState, fetchMembers bool) { logger.WithField("guild", gs.ID).Info("Starting slowcheck") - gs.RLock() - defer gs.RUnlock() err = common.RedisPool.Do(radix.WithConn(KeyCurrentlyStreaming(gs.ID), func(conn radix.Conn) error { for _, ms := range slowCheck { - if !ms.MemberSet || !ms.PresenceSet { + if ms.Member == nil || ms.Presence == nil { continue } - gs.RUnlock() err = CheckPresence(conn, config, ms, gs) - gs.RLock() if err != nil { logger.WithError(err).Error("Error checking presence") continue @@ -137,7 +130,7 @@ func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) return false, nil } - config, err := BotCachedGetConfig(evt.GS) + config, err := BotCachedGetConfig(evt.GS.ID) if err != nil { return true, errors.WithStackIf(err) } @@ -146,13 +139,13 @@ func HandleGuildMemberUpdate(evt *eventsystem.EventData) (retry bool, err error) return false, nil } - ms := evt.GS.MemberCopy(true, m.User.ID) + ms := bot.State.GetMember(m.GuildID, m.User.ID) if ms == nil { logger.WithField("guild", m.GuildID).Error("Member not found in state") return false, nil } - if !ms.PresenceSet { + if ms.Presence == nil { return // no presence tracked, no poing in continuing } @@ -181,31 +174,30 @@ func HandleGuildCreate(evt *eventsystem.EventData) { if !config.Enabled { return } - gs := bot.State.Guild(true, g.ID) + gs := bot.State.GetGuild(g.ID) if gs == nil { logger.WithField("guild", g.ID).Error("Guild not found in state") return } - gs.RLock() - defer gs.RUnlock() - err = common.RedisPool.Do(radix.WithConn(KeyCurrentlyStreaming(g.ID), func(conn radix.Conn) error { - for _, ms := range gs.Members { + bot.State.IterateMembers(g.ID, func(chunk []*dstate.MemberState) bool { + for _, ms := range chunk { - if !ms.MemberSet || !ms.PresenceSet { - continue - } + if ms.Member == nil || ms.Presence == nil { + continue + } - gs.RUnlock() - err = CheckPresence(conn, config, ms, gs) - gs.RLock() + err = CheckPresence(conn, config, ms, gs) + if err != nil { + logger.WithError(err).Error("Failed checking presence") + } - if err != nil { - logger.WithError(err).Error("Failed checking presence") } - } + + return true + }) return nil })) @@ -220,12 +212,12 @@ func HandlePresenceUpdate(evt *eventsystem.EventData) (retry bool, err error) { return false, nil } - config, err := BotCachedGetConfig(gs) + config, err := BotCachedGetConfig(gs.ID) if err != nil { return true, errors.WithStackIf(err) } - if !config.Enabled || (config.GiveRole == 0 && (config.AnnounceMessage == "" || gs.Channel(true, config.AnnounceChannel) == nil)) { + if !config.Enabled || (config.GiveRole == 0 && (config.AnnounceMessage == "" || gs.GetChannel(config.AnnounceChannel) == nil)) { // Don't bother trying to send anything, its not "fully" enabled return } @@ -238,7 +230,7 @@ func HandlePresenceUpdate(evt *eventsystem.EventData) (retry bool, err error) { return false, nil } -func CheckPresenceSparse(client radix.Client, config *Config, p *discordgo.Presence, gs *dstate.GuildState) error { +func CheckPresenceSparse(client radix.Client, config *Config, p *discordgo.Presence, gs *dstate.GuildSet) error { if !config.Enabled { // RemoveStreaming(client, config, gs.ID, p.User.ID, member) return nil @@ -300,7 +292,7 @@ func retrieveMainActivity(p *discordgo.Presence) *discordgo.Game { return nil } -func CheckPresence(client radix.Client, config *Config, ms *dstate.MemberState, gs *dstate.GuildState) error { +func CheckPresence(client radix.Client, config *Config, ms *dstate.MemberState, gs *dstate.GuildSet) error { if !config.Enabled { // RemoveStreaming(client, config, gs.ID, p.User.ID, member) return nil @@ -308,21 +300,21 @@ func CheckPresence(client radix.Client, config *Config, ms *dstate.MemberState, // Now the real fun starts // Either add or remove the stream - if ms.PresenceStatus != dstate.StatusOffline && ms.PresenceGame != nil && ms.PresenceGame.URL != "" && ms.PresenceGame.Type == 1 { + if ms.Presence != nil && ms.Presence.Status != dstate.StatusOffline && ms.Presence.Game != nil && ms.Presence.Game.URL != "" && ms.Presence.Game.Type == 1 { // Streaming - if !config.MeetsRequirements(ms.Roles, ms.PresenceGame.State, ms.PresenceGame.Details) { - RemoveStreaming(client, config, gs.ID, ms.ID, ms.Roles) + if !config.MeetsRequirements(ms.Member.Roles, ms.Presence.Game.State, ms.Presence.Game.Details) { + RemoveStreaming(client, config, gs.ID, ms.User.ID, ms.Member.Roles) return nil } if config.GiveRole != 0 { - go GiveStreamingRole(gs.Guild.ID, ms.ID, config.GiveRole, ms.Roles) + go GiveStreamingRole(gs.ID, ms.User.ID, config.GiveRole, ms.Member.Roles) } // if true, then we were marked now, and not before var markedNow bool - client.Do(radix.FlatCmd(&markedNow, "SADD", KeyCurrentlyStreaming(gs.ID), ms.ID)) + client.Do(radix.FlatCmd(&markedNow, "SADD", KeyCurrentlyStreaming(gs.ID), ms.User.ID)) if !markedNow { // Already marked return nil @@ -330,12 +322,12 @@ func CheckPresence(client radix.Client, config *Config, ms *dstate.MemberState, // Send the streaming announcement if enabled if config.AnnounceChannel != 0 && config.AnnounceMessage != "" { - SendStreamingAnnouncement(config, gs, ms, ms.PresenceGame.URL, ms.PresenceGame.State, ms.PresenceGame.Details, ms.PresenceGame.Name) + SendStreamingAnnouncement(config, gs, ms, ms.Presence.Game.URL, ms.Presence.Game.State, ms.Presence.Game.Details, ms.Presence.Game.Name) } } else { // Not streaming - RemoveStreaming(client, config, gs.ID, ms.ID, ms.Roles) + RemoveStreaming(client, config, gs.ID, ms.User.ID, ms.Member.Roles) } return nil @@ -395,10 +387,10 @@ func RemoveStreaming(client radix.Client, config *Config, guildID int64, memberI // } } -func SendStreamingAnnouncement(config *Config, guild *dstate.GuildState, ms *dstate.MemberState, url string, gameName string, streamTitle string, streamPlatform string) { +func SendStreamingAnnouncement(config *Config, guild *dstate.GuildSet, ms *dstate.MemberState, url string, gameName string, streamTitle string, streamPlatform string) { // Only send one announcment every 1 hour var resp string - key := fmt.Sprintf("streaming_announcement_sent:%d:%d", guild.ID, ms.ID) + key := fmt.Sprintf("streaming_announcement_sent:%d:%d", guild.ID, ms.User.ID) err := common.RedisPool.Do(radix.Cmd(&resp, "SET", key, "1", "EX", "3600", "NX")) if err != nil { logger.WithError(err).Error("failed setting streaming announcment cooldown") @@ -406,7 +398,7 @@ func SendStreamingAnnouncement(config *Config, guild *dstate.GuildState, ms *dst } if resp != "OK" { - logger.Info("streaming announcment cooldown: ", ms.ID) + logger.Info("streaming announcment cooldown: ", ms.User.ID) return } @@ -511,15 +503,11 @@ func DisableStreamingRole(guildID int64) { featureflags.MarkGuildDirty(guildID) } -type CacheKey int - -const ( - CacheKeyConfig CacheKey = iota -) +var cachedConfig = common.CacheSet.RegisterSlot("streaming_configs", nil, int64(0)) -func BotCachedGetConfig(gs *dstate.GuildState) (*Config, error) { - v, err := gs.UserCacheFetch(CacheKeyConfig, func() (interface{}, error) { - return GetConfig(gs.ID) +func BotCachedGetConfig(guildID int64) (*Config, error) { + v, err := cachedConfig.GetCustomFetch(guildID, func(key interface{}) (interface{}, error) { + return GetConfig(guildID) }) if err != nil { diff --git a/streaming/web.go b/streaming/web.go index 600311b000..9373b85313 100644 --- a/streaming/web.go +++ b/streaming/web.go @@ -129,7 +129,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w roleStr := "none / unknown" indicatorRole := "" - if role := ag.Role(config.GiveRole); role != nil { + if role := ag.GetRole(config.GiveRole); role != nil { roleStr = html.EscapeString(role.Name) indicatorRole = web.Indicator(true) } else { @@ -139,7 +139,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w indicatorMessage := "" channelStr := "none / unknown" - if channel := ag.Channel(config.AnnounceChannel); channel != nil { + if channel := ag.GetChannel(config.AnnounceChannel); channel != nil { indicatorMessage = web.Indicator(true) channelStr = html.EscapeString(channel.Name) } else { diff --git a/tickets/tickets_commands.go b/tickets/tickets_commands.go index de1e3766b2..e5a5740630 100644 --- a/tickets/tickets_commands.go +++ b/tickets/tickets_commands.go @@ -12,9 +12,9 @@ import ( "sync" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/commands" @@ -45,7 +45,7 @@ func (p *Plugin) AddCommands() { Description: "Opens a new ticket", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "subject", Type: dcmd.String}, + {Name: "subject", Type: dcmd.String}, }, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { conf := parsed.Context().Value(CtxKeyConfig).(*models.TicketConfig) @@ -53,7 +53,7 @@ func (p *Plugin) AddCommands() { return "Ticket system is disabled in this server, the server admins can enable it in the control panel.", nil } - if parsed.GuildData.GS.Channel(true, conf.TicketsChannelCategory) == nil { + if parsed.GuildData.GS.GetChannel(conf.TicketsChannelCategory) == nil { return "No category for ticket channels set", nil } @@ -66,10 +66,13 @@ func (p *Plugin) AddCommands() { qm.Where("closed_at IS NULL"), qm.Where("guild_id = ?", parsed.GuildData.GS.ID), qm.Where("author_id = ?", parsed.Author.ID)).AllG(parsed.Context()) + if err != nil { + return "failed checking current tickets...", err + } count := 0 for _, v := range inCurrentTickets { - if parsed.GuildData.GS.Channel(true, v.ChannelID) != nil { + if parsed.GuildData.GS.GetChannel(v.ChannelID) != nil { count++ } } @@ -106,7 +109,9 @@ func (p *Plugin) AddCommands() { // send the first ticket message - tmplCTX := templates.NewContext(parsed.GuildData.GS, dstate.NewChannelState(parsed.GuildData.GS, parsed.GuildData.GS, channel), parsed.GuildData.MS) + cs := dstate.ChannelStateFromDgo(channel) + + tmplCTX := templates.NewContext(parsed.GuildData.GS, &cs, parsed.GuildData.MS) tmplCTX.Name = "ticket open message" tmplCTX.Data["Reason"] = parsed.Args[0].Str() ticketOpenMsg := conf.TicketOpenMSG @@ -137,7 +142,7 @@ func (p *Plugin) AddCommands() { Description: "Adds a user to the ticket in this channel", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "target", Type: &commands.MemberArg{}}, + {Name: "target", Type: &commands.MemberArg{}}, }, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { @@ -145,26 +150,23 @@ func (p *Plugin) AddCommands() { currentTicket := parsed.Context().Value(CtxKeyCurrentTicket).(*Ticket) - parsed.GuildData.GS.RLock() OUTER: for _, v := range parsed.GuildData.CS.PermissionOverwrites { - if v.Type == "member" && v.ID == target.ID { + if v.Type == "member" && v.ID == target.User.ID { if (v.Allow & InTicketPerms) == InTicketPerms { - parsed.GuildData.GS.RUnlock() return "User is already part of the ticket", nil } break OUTER } } - parsed.GuildData.GS.RUnlock() - err := common.BotSession.ChannelPermissionSet(currentTicket.Ticket.ChannelID, target.ID, "member", InTicketPerms, 0) + err := common.BotSession.ChannelPermissionSet(currentTicket.Ticket.ChannelID, target.User.ID, "member", InTicketPerms, 0) if err != nil { return nil, err } - return fmt.Sprintf("Added %s#%04d to the ticket", target.Username, target.Discriminator), nil + return fmt.Sprintf("Added %s#%s to the ticket", target.User.Username, target.User.Discriminator), nil }, } @@ -174,7 +176,7 @@ func (p *Plugin) AddCommands() { Description: "Removes a user from the ticket", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "target", Type: &commands.MemberArg{}}, + {Name: "target", Type: &commands.MemberArg{}}, }, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { @@ -184,10 +186,9 @@ func (p *Plugin) AddCommands() { foundUser := false - parsed.GuildData.GS.RLock() OUTER: for _, v := range parsed.GuildData.CS.PermissionOverwrites { - if v.Type == "member" && v.ID == target.ID { + if v.Type == "member" && v.ID == target.User.ID { if (v.Allow & InTicketPerms) == InTicketPerms { foundUser = true } @@ -195,18 +196,17 @@ func (p *Plugin) AddCommands() { break OUTER } } - parsed.GuildData.GS.RUnlock() if !foundUser { - return fmt.Sprintf("%s#%04d is already not (explicitly) part of this ticket", target.Username, target.Discriminator), nil + return fmt.Sprintf("%s#%s is already not (explicitly) part of this ticket", target.User.Username, target.User.Discriminator), nil } - err := common.BotSession.ChannelPermissionDelete(currentTicket.Ticket.ChannelID, target.ID) + err := common.BotSession.ChannelPermissionDelete(currentTicket.Ticket.ChannelID, target.User.ID) if err != nil { return nil, err } - return fmt.Sprintf("Removed %s#%04d from the ticket", target.Username, target.Discriminator), nil + return fmt.Sprintf("Removed %s#%s from the ticket", target.User.Username, target.User.Discriminator), nil }, } @@ -216,7 +216,7 @@ func (p *Plugin) AddCommands() { Description: "Renames the ticket", RequiredArgs: 1, Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "new-name", Type: dcmd.String}, + {Name: "new-name", Type: dcmd.String}, }, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { currentTicket := parsed.Context().Value(CtxKeyCurrentTicket).(*Ticket) @@ -255,7 +255,7 @@ func (p *Plugin) AddCommands() { Aliases: []string{"end", "delete"}, Description: "Closes the ticket", Arguments: []*dcmd.ArgDef{ - &dcmd.ArgDef{Name: "reason", Type: dcmd.String, Default: "none"}, + {Name: "reason", Type: dcmd.String, Default: "none"}, }, RunFunc: func(parsed *dcmd.Data) (interface{}, error) { conf := parsed.Context().Value(CtxKeyConfig).(*models.TicketConfig) @@ -323,7 +323,6 @@ func (p *Plugin) AddCommands() { modOverwrites := make([]*discordgo.PermissionOverwrite, 0) - parsed.GuildData.GS.RLock() for _, ow := range parsed.GuildData.CS.PermissionOverwrites { if ow.Type == "role" && common.ContainsInt64Slice(conf.ModRoles, ow.ID) { if (ow.Allow & InTicketPerms) == InTicketPerms { @@ -331,10 +330,9 @@ func (p *Plugin) AddCommands() { isAdminsOnlyCurrently = false } - modOverwrites = append(modOverwrites, ow) + modOverwrites = append(modOverwrites, &ow) } } - parsed.GuildData.GS.RUnlock() // update existing overwrites for _, v := range modOverwrites { @@ -442,7 +440,7 @@ func (p *Plugin) AddCommands() { commands.RegisterSlashCommandsContainer(container, false, TicketCommandsRolesRunFuncfunc) } -func TicketCommandsRolesRunFuncfunc(gs *dstate.GuildState) ([]int64, error) { +func TicketCommandsRolesRunFuncfunc(gs *dstate.GuildSet) ([]int64, error) { conf, err := models.FindTicketConfigG(context.Background(), gs.ID) if err != nil { if err != sql.ErrNoRows { @@ -482,7 +480,7 @@ type Ticket struct { Participants []*models.TicketParticipant } -func createLogs(gs *dstate.GuildState, conf *models.TicketConfig, ticket *models.Ticket, adminOnly bool) error { +func createLogs(gs *dstate.GuildSet, conf *models.TicketConfig, ticket *models.Ticket, adminOnly bool) error { if !conf.TicketsUseTXTTranscripts && !conf.DownloadAttachments { return nil // nothing to do here @@ -552,7 +550,7 @@ func createLogs(gs *dstate.GuildState, conf *models.TicketConfig, ticket *models } } - if conf.TicketsUseTXTTranscripts && gs.Channel(true, transcriptChannel(conf, adminOnly)) != nil { + if conf.TicketsUseTXTTranscripts && gs.GetChannel(transcriptChannel(conf, adminOnly)) != nil { formattedTranscript := createTXTTranscript(ticket, msgs) channel := transcriptChannel(conf, adminOnly) @@ -563,7 +561,7 @@ func createLogs(gs *dstate.GuildState, conf *models.TicketConfig, ticket *models } // compress and send the attachments - if conf.DownloadAttachments && gs.Channel(true, transcriptChannel(conf, adminOnly)) != nil { + if conf.DownloadAttachments && gs.GetChannel(transcriptChannel(conf, adminOnly)) != nil { archiveAttachments(conf, ticket, attachments, adminOnly) } @@ -584,7 +582,7 @@ func archiveAttachments(conf *models.TicketConfig, ticket *models.Ticket, groups } fName := fmt.Sprintf("attachments-%d-%s-%s", ticket.LocalID, ticket.Title, ag[0].Filename) - _, err = common.BotSession.ChannelFileSendWithMessage(transcriptChannel(conf, adminOnly), + _, _ = common.BotSession.ChannelFileSendWithMessage(transcriptChannel(conf, adminOnly), fName, fName, resp.Body) continue } @@ -668,7 +666,6 @@ func ticketIsAdminOnly(conf *models.TicketConfig, cs *dstate.ChannelState) bool isAdminsOnlyCurrently := true - cs.Guild.RLock() for _, ow := range cs.PermissionOverwrites { if ow.Type == "role" && common.ContainsInt64Slice(conf.ModRoles, ow.ID) { if (ow.Allow & InTicketPerms) == InTicketPerms { @@ -677,7 +674,6 @@ func ticketIsAdminOnly(conf *models.TicketConfig, cs *dstate.ChannelState) bool } } } - cs.Guild.RUnlock() return isAdminsOnlyCurrently } @@ -690,20 +686,20 @@ func transcriptChannel(conf *models.TicketConfig, adminOnly bool) int64 { return conf.TicketsTranscriptsChannel } -func createTicketChannel(conf *models.TicketConfig, gs *dstate.GuildState, authorID int64, subject string) (int64, *discordgo.Channel, error) { +func createTicketChannel(conf *models.TicketConfig, gs *dstate.GuildSet, authorID int64, subject string) (int64, *discordgo.Channel, error) { // assemble the permission overwrites for the channel were about to create overwrites := []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: authorID, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: gs.ID, Deny: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "member", ID: common.BotUser.ID, Allow: InTicketPerms, @@ -765,13 +761,18 @@ OUTER2: return id, channel, nil } -func applyChannelParentSettings(gs *dstate.GuildState, parentCategoryID int64, overwrites []*discordgo.PermissionOverwrite) []*discordgo.PermissionOverwrite { - cs := gs.ChannelCopy(true, parentCategoryID) +func applyChannelParentSettings(gs *dstate.GuildSet, parentCategoryID int64, overwrites []*discordgo.PermissionOverwrite) []*discordgo.PermissionOverwrite { + cs := gs.GetChannel(parentCategoryID) if cs == nil { return overwrites } - return applyChannelParentSettingsOverwrites(cs.PermissionOverwrites, overwrites) + channel_overwrites := make([]*discordgo.PermissionOverwrite, len(cs.PermissionOverwrites)) + for i := 0; i < len(overwrites); i++ { + channel_overwrites[i] = &cs.PermissionOverwrites[i] + } + + return applyChannelParentSettingsOverwrites(channel_overwrites, overwrites) } func applyChannelParentSettingsOverwrites(parentOverwrites []*discordgo.PermissionOverwrite, newChannelOverwrites []*discordgo.PermissionOverwrite) []*discordgo.PermissionOverwrite { diff --git a/tickets/tickets_test.go b/tickets/tickets_test.go index 936d65a762..bce8f1e04f 100644 --- a/tickets/tickets_test.go +++ b/tickets/tickets_test.go @@ -1,10 +1,11 @@ package tickets -import "testing" +import ( + "fmt" + "testing" -import "github.com/jonas747/discordgo" - -import "fmt" + "github.com/jonas747/discordgo" +) func TestInheritPermissionsFromCategory(t *testing.T) { cases := []struct { @@ -15,14 +16,14 @@ func TestInheritPermissionsFromCategory(t *testing.T) { { // 0, basic ParentOverwrites: []*discordgo.PermissionOverwrite{}, InputOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, }, ExpectedOutput: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, @@ -32,24 +33,24 @@ func TestInheritPermissionsFromCategory(t *testing.T) { { // 1, basic with role ParentOverwrites: []*discordgo.PermissionOverwrite{}, InputOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, }, ExpectedOutput: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, @@ -58,36 +59,36 @@ func TestInheritPermissionsFromCategory(t *testing.T) { }, { // 2, basic parent check ParentOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 3, Deny: discordgo.PermissionReadMessages, }, }, InputOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, }, ExpectedOutput: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 3, Deny: discordgo.PermissionReadMessages, @@ -96,31 +97,31 @@ func TestInheritPermissionsFromCategory(t *testing.T) { }, { // 3, allow/deny flip check ParentOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Deny: discordgo.PermissionReadMessages, }, }, InputOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, }, ExpectedOutput: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, @@ -129,41 +130,41 @@ func TestInheritPermissionsFromCategory(t *testing.T) { }, { // 4, multiples ParentOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Deny: discordgo.PermissionReadMessages, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 3, Allow: discordgo.PermissionReadMessages, }, }, InputOverwrites: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, }, ExpectedOutput: []*discordgo.PermissionOverwrite{ - &discordgo.PermissionOverwrite{ + { Type: "member", ID: 1, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 2, Allow: InTicketPerms, }, - &discordgo.PermissionOverwrite{ + { Type: "role", ID: 3, Allow: discordgo.PermissionReadMessages, diff --git a/timezonecompanion/plugin_bot.go b/timezonecompanion/plugin_bot.go index 7bc30b6e32..5a1dd262bf 100644 --- a/timezonecompanion/plugin_bot.go +++ b/timezonecompanion/plugin_bot.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/jonas747/dcmd/v2" + "github.com/jonas747/dcmd/v3" "github.com/jonas747/discordgo" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" diff --git a/verification/verification_bot.go b/verification/verification_bot.go index d85e3048e1..3adff26403 100644 --- a/verification/verification_bot.go +++ b/verification/verification_bot.go @@ -12,7 +12,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" - "github.com/jonas747/dstate/v2" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/analytics" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/bot/eventsystem" @@ -115,7 +115,7 @@ func (p *Plugin) startVerificationProcess(conf *models.VerificationConfig, guild return } - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) if gs == nil { logger.Error("guild not available") return @@ -132,19 +132,21 @@ func (p *Plugin) startVerificationProcess(conf *models.VerificationConfig, guild return } - channel, err := common.BotSession.UserChannelCreate(ms.ID) + channel, err := common.BotSession.UserChannelCreate(ms.User.ID) if err != nil { logger.WithError(err).Error("failed creating user channel") return } - tmplCTX := templates.NewContext(gs, dstate.NewChannelState(gs, gs, channel), ms) + cs := dstate.ChannelStateFromDgo(channel) + + tmplCTX := templates.NewContext(gs, &cs, ms) tmplCTX.Name = "dm_veification_message" tmplCTX.Data["Link"] = fmt.Sprintf("%s/public/%d/verify/%d/%s", web.BaseURL(), guildID, target.ID, token) err = tmplCTX.ExecuteAndSendWithErrors(msg, channel.ID) if err != nil { - logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.ID).Error("failed sending verification dm message") + logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.User.ID).Error("failed sending verification dm message") } evt := &VerificationEventData{ @@ -153,9 +155,9 @@ func (p *Plugin) startVerificationProcess(conf *models.VerificationConfig, guild } // schedule the kick and warnings - err = p.clearScheduledEvents(context.Background(), gs.ID, ms.ID) //clear old scheduled events + err = p.clearScheduledEvents(context.Background(), gs.ID, ms.User.ID) //clear old scheduled events if err != nil { - logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.ID).Error("failed clearing past scheduled warn/kick events.") + logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.User.ID).Error("failed clearing past scheduled warn/kick events.") } if conf.WarnUnverifiedAfter > 0 && conf.WarnMessage != "" { scheduledevents2.ScheduleEvent("verification_user_warn", guildID, time.Now().Add(time.Minute*time.Duration(conf.WarnUnverifiedAfter)), evt) @@ -201,12 +203,12 @@ func ScheduledEventMW(innerHandler func(ms *dstate.MemberState, guildID int64, c } func (p *Plugin) handleUserVerifiedScheduledEvent(ms *dstate.MemberState, guildID int64, conf *models.VerificationConfig, rawData interface{}) (retry bool, err error) { - err = common.BotSession.GuildMemberRoleAdd(guildID, ms.ID, conf.VerifiedRole) + err = common.BotSession.GuildMemberRoleAdd(guildID, ms.User.ID, conf.VerifiedRole) if err != nil { return scheduledevents2.CheckDiscordErrRetry(err), err } - model, err := models.FindVerifiedUserG(context.Background(), guildID, ms.ID) + model, err := models.FindVerifiedUserG(context.Background(), guildID, ms.User.ID) if err != nil { if err == sql.ErrNoRows { return false, err @@ -214,24 +216,24 @@ func (p *Plugin) handleUserVerifiedScheduledEvent(ms *dstate.MemberState, guildI return scheduledevents2.CheckDiscordErrRetry(err), err } - err = p.clearScheduledEvents(context.Background(), guildID, ms.ID) + err = p.clearScheduledEvents(context.Background(), guildID, ms.User.ID) if err != nil { return true, err } if !confVerificationTrackIPs.GetBool() || model.IP == "" { - p.logAction(guildID, conf.LogChannel, ms.DGoUser(), "User successfully verified", 0x49ed47) + p.logAction(guildID, conf.LogChannel, &ms.User, "User successfully verified", 0x49ed47) return false, nil } // Check for IP conflicts - conflicts, err := p.findIPConflicts(guildID, ms.ID, model.IP) + conflicts, err := p.findIPConflicts(guildID, ms.User.ID, model.IP) if err != nil { return scheduledevents2.CheckDiscordErrRetry(err), err } if len(conflicts) < 1 { - p.logAction(guildID, conf.LogChannel, ms.DGoUser(), "User successfully verified", 0x49ed47) + p.logAction(guildID, conf.LogChannel, &ms.User, "User successfully verified", 0x49ed47) return false, nil } @@ -251,12 +253,12 @@ func (p *Plugin) handleUserVerifiedScheduledEvent(ms *dstate.MemberState, guildI banReason = string(r) + "..." } - err := moderation.BanUser(nil, guildID, nil, nil, common.BotUser, banReason, ms.DGoUser()) + err := moderation.BanUser(nil, guildID, nil, nil, common.BotUser, banReason, &ms.User) if err != nil { return scheduledevents2.CheckDiscordErrRetry(err), err } - p.logAction(guildID, conf.LogChannel, ms.DGoUser(), fmt.Sprintf("User banned for sharing IP with banned user %s#%s (%d)\nReason: %s", + p.logAction(guildID, conf.LogChannel, &ms.User, fmt.Sprintf("User banned for sharing IP with banned user %s#%s (%d)\nReason: %s", ban.User.Username, ban.User.Discriminator, ban.User.ID, ban.Reason), 0xef4640) return false, nil @@ -274,7 +276,7 @@ func (p *Plugin) handleUserVerifiedScheduledEvent(ms *dstate.MemberState, guildI } } - p.logAction(guildID, conf.LogChannel, ms.DGoUser(), builder.String(), 0xff8228) + p.logAction(guildID, conf.LogChannel, &ms.User, builder.String(), 0xff8228) return false, nil } @@ -337,7 +339,7 @@ func (p *Plugin) CheckBanned(guildID int64, users []*discordgo.User) (*discordgo } func (p *Plugin) handleWarnUserVerification(ms *dstate.MemberState, guildID int64, conf *models.VerificationConfig, rawData interface{}) (retry bool, err error) { - gs := bot.State.Guild(true, guildID) + gs := bot.State.GetGuild(guildID) if gs == nil { return false, nil } @@ -361,25 +363,26 @@ func (p *Plugin) handleWarnUserVerification(ms *dstate.MemberState, guildID int6 return scheduledevents2.CheckDiscordErrRetry(err), err } -func (p *Plugin) sendWarning(ms *dstate.MemberState, gs *dstate.GuildState, token string, conf *models.VerificationConfig) error { +func (p *Plugin) sendWarning(ms *dstate.MemberState, gs *dstate.GuildSet, token string, conf *models.VerificationConfig) error { msg := conf.WarnMessage if strings.TrimSpace(msg) == "" { return nil // no message to send } - channel, err := common.BotSession.UserChannelCreate(ms.ID) + channel, err := common.BotSession.UserChannelCreate(ms.User.ID) if err != nil { return err } + cs := dstate.ChannelStateFromDgo(channel) - tmplCTX := templates.NewContext(gs, dstate.NewChannelState(gs, gs, channel), ms) + tmplCTX := templates.NewContext(gs, &cs, ms) tmplCTX.Name = "warn message" - tmplCTX.Data["Link"] = fmt.Sprintf("%s/public/%d/verify/%d/%s", web.BaseURL(), gs.ID, ms.ID, token) + tmplCTX.Data["Link"] = fmt.Sprintf("%s/public/%d/verify/%d/%s", web.BaseURL(), gs.ID, ms.User.ID, token) err = tmplCTX.ExecuteAndSendWithErrors(msg, channel.ID) if err != nil { - logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.ID).Error("failed sending warning message") + logger.WithError(err).WithField("guild", gs.ID).WithField("user", ms.User.ID).Error("failed sending warning message") } return nil @@ -402,9 +405,9 @@ func (p *Plugin) handleKickUser(ms *dstate.MemberState, guildID int64, conf *mod return false, nil } - err = common.BotSession.GuildMemberDelete(guildID, ms.ID) + err = common.BotSession.GuildMemberDelete(guildID, ms.User.ID) if err == nil { - p.logAction(guildID, conf.LogChannel, ms.DGoUser(), "Kicked for not verifying within deadline", 0xef4640) + p.logAction(guildID, conf.LogChannel, &ms.User, "Kicked for not verifying within deadline", 0xef4640) } return scheduledevents2.CheckDiscordErrRetry(err), err diff --git a/verification/verification_web.go b/verification/verification_web.go index b8b7a4e9be..fd7bdc3330 100644 --- a/verification/verification_web.go +++ b/verification/verification_web.go @@ -317,7 +317,7 @@ func (p *Plugin) LoadServerHomeWidget(w http.ResponseWriter, r *http.Request) (w roleStr := "none / unknown" indicatorRole := "" - if role := ag.Role(settings.VerifiedRole); role != nil { + if role := ag.GetRole(settings.VerifiedRole); role != nil { roleStr = html.EscapeString(role.Name) indicatorRole = web.Indicator(true) } else { diff --git a/web/discorddata/discord_data.go b/web/discorddata/discord_data.go index 588189ae83..e4d76e1727 100644 --- a/web/discorddata/discord_data.go +++ b/web/discorddata/discord_data.go @@ -7,6 +7,7 @@ import ( "emperror.dev/errors" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/common" @@ -92,12 +93,13 @@ func keyFullGuild(guildID int64) string { // 3. Discord api // // It will will also make sure channels are included in the event we fall back to the discord API -func GetFullGuild(guildID int64) (*discordgo.Guild, error) { +func GetFullGuild(guildID int64) (*dstate.GuildSet, error) { result, err := applicationCache.Fetch(keyFullGuild(guildID), time.Minute*10, func() (interface{}, error) { - guild, err := botrest.GetGuild(guildID) + gs, err := botrest.GetGuild(guildID) if err != nil { // fall back to discord API - guild, err = common.BotSession.Guild(guildID) + + guild, err := common.BotSession.Guild(guildID) if err != nil { return nil, err } @@ -108,20 +110,22 @@ func GetFullGuild(guildID int64) (*discordgo.Guild, error) { return nil, err } + // does the API guarantee the order? i actually have no idea lmao + sort.Sort(dutil.Channels(channels)) + sort.Sort(dutil.Roles(guild.Roles)) guild.Channels = channels - } - sort.Sort(dutil.Channels(guild.Channels)) - sort.Sort(dutil.Roles(guild.Roles)) + gs = dstate.GuildSetFromGuild(guild) + } - return guild, nil + return gs, nil }) if err != nil { return nil, err } - return result.Value().(*discordgo.Guild), nil + return result.Value().(*dstate.GuildSet), nil } func keyGuildMember(guildID int64, userID int64) string { diff --git a/web/handlers_auth.go b/web/handlers_auth.go index 08b8aa4e4f..58cbfd59ef 100644 --- a/web/handlers_auth.go +++ b/web/handlers_auth.go @@ -140,7 +140,7 @@ func CheckCSRFToken(token string) (bool, error) { return num > 0, nil } -var ErrNotLoggedIn = errors.New("Not logged in") +var ErrNotLoggedIn = errors.New("not logged in") // AuthTokenFromB64 Retrives an oauth2 token from the base64 string // Returns an error if expired @@ -173,7 +173,7 @@ func discordAuthTokenFromYag(yagToken string) (t *oauth2.Token, err error) { } var ( - ErrDuplicateToken = errors.New("Somehow a duplicate token was found") + ErrDuplicateToken = errors.New("somehow a duplicate token was found") ) // CreateCookieSession creates a session cookie where the value is the access token itself, diff --git a/web/handlers_general.go b/web/handlers_general.go index 142f67a2b4..f9a84920ac 100644 --- a/web/handlers_general.go +++ b/web/handlers_general.go @@ -16,6 +16,7 @@ import ( "time" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot/botrest" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/cplogs" @@ -370,7 +371,7 @@ func HandleReconnectShard(w http.ResponseWriter, r *http.Request) (TemplateData, } func HandleChanenlPermissions(w http.ResponseWriter, r *http.Request) interface{} { - g := r.Context().Value(common.ContextKeyCurrentGuild).(*discordgo.Guild) + g := r.Context().Value(common.ContextKeyCurrentGuild).(*dstate.GuildSet) c, _ := strconv.ParseInt(pat.Param(r, "channel"), 10, 64) perms, err := botrest.GetChannelPermissions(g.ID, c) if err != nil { diff --git a/web/middleware.go b/web/middleware.go index f6a369dde9..ac0b9a3d0a 100644 --- a/web/middleware.go +++ b/web/middleware.go @@ -15,6 +15,7 @@ import ( "emperror.dev/errors" "github.com/gorilla/schema" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/config" @@ -242,7 +243,7 @@ func UserInfoMiddleware(inner http.Handler) http.Handler { return http.HandlerFunc(mw) } -func getGuild(ctx context.Context, guildID int64) (*discordgo.Guild, error) { +func getGuild(ctx context.Context, guildID int64) (*dstate.GuildSet, error) { guild, err := discorddata.GetFullGuild(guildID) if err != nil { CtxLogger(ctx).WithError(err).Warn("failed getting guild from discord fallback, nothing more we can do...") @@ -292,7 +293,7 @@ func LoadCoreConfigMiddleware(inner http.Handler) http.Handler { return } - g := v.(*discordgo.Guild) + g := v.(*dstate.GuildSet) coreConf := common.GetCoreServerConfCached(g.ID) @@ -362,12 +363,12 @@ func RequireBotMemberMW(inner http.Handler) http.Handler { return } - guildCast := guild.(*discordgo.Guild) + guildCast := guild.(*dstate.GuildSet) if len(guildCast.Roles) < 1 { // Not full guild return } - var highest *discordgo.Role + var highest discordgo.Role combinedPerms := 0 for _, role := range guildCast.Roles { found := false @@ -391,15 +392,14 @@ func RequireBotMemberMW(inner http.Handler) http.Handler { combinedPerms |= discordgo.PermissionAll } - if highest == nil || dutil.IsRoleAbove(role, highest) { + if highest.ID == 0 || dutil.IsRoleAbove(&role, &highest) { highest = role } - } - ctx = context.WithValue(ctx, common.ContextKeyHighestBotRole, highest) + ctx = context.WithValue(ctx, common.ContextKeyHighestBotRole, &highest) ctx = context.WithValue(ctx, common.ContextKeyBotPermissions, combinedPerms) - ctx = SetContextTemplateData(ctx, map[string]interface{}{"HighestRole": highest, "BotPermissions": combinedPerms}) + ctx = SetContextTemplateData(ctx, map[string]interface{}{"HighestRole": &highest, "BotPermissions": combinedPerms}) r = r.WithContext(ctx) }) } @@ -770,7 +770,7 @@ func SetGuildMemberMiddleware(inner http.Handler) http.Handler { CtxLogger(r.Context()).WithError(err).Warn("failed retrieving member info from discord api") } else if m != nil { // calculate permissions - perms := discordgo.MemberPermissions(guild, nil, m) + perms := dstate.CalculatePermissions(&guild.GuildState, guild.Roles, nil, m.User.ID, m.Roles) ctx = context.WithValue(r.Context(), common.ContextKeyUserMember, m) ctx = context.WithValue(ctx, common.ContextKeyMemberPermissions, perms) diff --git a/web/template.go b/web/template.go index 003324717e..f55ff1cc4d 100644 --- a/web/template.go +++ b/web/template.go @@ -9,6 +9,7 @@ import ( "time" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/dutil" "github.com/jonas747/yagpdb/common/templates" ) @@ -57,7 +58,7 @@ func hasPerm(botPerms int, checkPerm string) (bool, error) { // 1. current selected roleid // 2. default empty display name // 3. default unknown display name -func tmplRoleDropdown(roles []*discordgo.Role, highestBotRole *discordgo.Role, args ...interface{}) template.HTML { +func tmplRoleDropdown(roles []discordgo.Role, highestBotRole *discordgo.Role, args ...interface{}) template.HTML { hasCurrentSelected := len(args) > 0 var currentSelected int64 if hasCurrentSelected { @@ -108,7 +109,7 @@ func tmplRoleDropdown(roles []*discordgo.Role, highestBotRole *discordgo.Role, a optName := template.HTMLEscapeString(role.Name) if highestBotRole != nil { - if dutil.IsRoleAbove(role, highestBotRole) || role.ID == highestBotRole.ID { + if dutil.IsRoleAbove(&role, highestBotRole) || role.ID == highestBotRole.ID { output += " disabled" optName += " (role is above bot)" } @@ -124,7 +125,7 @@ func tmplRoleDropdown(roles []*discordgo.Role, highestBotRole *discordgo.Role, a } // Same as tmplRoleDropdown but supports multiple selections -func tmplRoleDropdownMutli(roles []*discordgo.Role, highestBotRole *discordgo.Role, selections []int64) template.HTML { +func tmplRoleDropdownMutli(roles []discordgo.Role, highestBotRole *discordgo.Role, selections []int64) template.HTML { var builder strings.Builder @@ -167,7 +168,7 @@ OUTER: optName := template.HTMLEscapeString(role.Name) if highestBotRole != nil { - if dutil.IsRoleAbove(role, highestBotRole) || highestBotRole.ID == role.ID { + if dutil.IsRoleAbove(&role, highestBotRole) || highestBotRole.ID == role.ID { if !optIsSelected { builder.WriteString(" disabled") } @@ -183,9 +184,9 @@ OUTER: func tmplChannelOpts(channelTypes []discordgo.ChannelType, optionPrefix string) interface{} { optsBuilder := tmplChannelOptsMulti(channelTypes, optionPrefix) - return func(channels []*discordgo.Channel, selection interface{}, allowEmpty bool, emptyName string) template.HTML { + return func(channels []dstate.ChannelState, selection interface{}, allowEmpty bool, emptyName string) template.HTML { - const unknownName = "Deleted channel" + // const unknownName = "Deleted channel" var builder strings.Builder @@ -215,8 +216,8 @@ func tmplChannelOpts(channelTypes []discordgo.ChannelType, optionPrefix string) } } -func tmplChannelOptsMulti(channelTypes []discordgo.ChannelType, optionPrefix string) func(channels []*discordgo.Channel, selections []int64) template.HTML { - return func(channels []*discordgo.Channel, selections []int64) template.HTML { +func tmplChannelOptsMulti(channelTypes []discordgo.ChannelType, optionPrefix string) func(channels []dstate.ChannelState, selections []int64) template.HTML { + return func(channels []dstate.ChannelState, selections []int64) template.HTML { var builder strings.Builder diff --git a/web/util.go b/web/util.go index b0b9740a67..a4a7bb4de9 100644 --- a/web/util.go +++ b/web/util.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/bot" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/cplogs" @@ -137,8 +138,8 @@ func SucessAlert(args ...interface{}) *Alert { } } -func ContextGuild(ctx context.Context) *discordgo.Guild { - return ctx.Value(common.ContextKeyCurrentGuild).(*discordgo.Guild) +func ContextGuild(ctx context.Context) *dstate.GuildSet { + return ctx.Value(common.ContextKeyCurrentGuild).(*dstate.GuildSet) } func ContextIsAdmin(ctx context.Context) bool { @@ -151,10 +152,10 @@ func ContextIsAdmin(ctx context.Context) bool { } // Returns base context data for control panel plugins -func GetBaseCPContextData(ctx context.Context) (*discordgo.Guild, TemplateData) { - var guild *discordgo.Guild +func GetBaseCPContextData(ctx context.Context) (*dstate.GuildSet, TemplateData) { + var guild *dstate.GuildSet if v := ctx.Value(common.ContextKeyCurrentGuild); v != nil { - guild = v.(*discordgo.Guild) + guild = v.(*dstate.GuildSet) } templateData := ctx.Value(common.ContextKeyTemplateData).(TemplateData) @@ -189,7 +190,7 @@ func IsAdminRequest(ctx context.Context, r *http.Request) (read bool, write bool if v := ctx.Value(common.ContextKeyCurrentGuild); v != nil { // accessing a server page - g := v.(*discordgo.Guild) + g := v.(*dstate.GuildSet) gWithConnected := &common.GuildWithConnected{ UserGuild: &discordgo.UserGuild{ @@ -208,7 +209,7 @@ func IsAdminRequest(ctx context.Context, r *http.Request) (read bool, write bool userID = member.User.ID roles = member.Roles - gWithConnected.Permissions = ContextMemberPerms(ctx) + gWithConnected.Permissions = int(ContextMemberPerms(ctx)) gWithConnected.Owner = userID == g.OwnerID } @@ -247,7 +248,7 @@ func NewLogEntryFromContext(ctx context.Context, action string, params ...*cplog return nil } - g := ctx.Value(common.ContextKeyCurrentGuild).(*discordgo.Guild) + g := ctx.Value(common.ContextKeyCurrentGuild).(*dstate.GuildSet) return cplogs.NewEntry(g.ID, user.ID, user.Username, action, params...) } @@ -258,7 +259,7 @@ func StaticRoleProvider(roles []int64) func(guildID, userID int64) []int64 { } } -func HasPermissionCTX(ctx context.Context, aperms int) bool { +func HasPermissionCTX(ctx context.Context, aperms int64) bool { perms := ContextMemberPerms(ctx) // Require manageserver, ownership of guild or ownership of bot if perms&discordgo.PermissionAdministrator == discordgo.PermissionAdministrator || @@ -290,8 +291,6 @@ func WriteErrorResponse(w http.ResponseWriter, r *http.Request, err string, stat } http.Redirect(w, r, "/?error="+url.QueryEscape(err), http.StatusTemporaryRedirect) - return - } func IsRequestPartial(ctx context.Context) bool { @@ -315,13 +314,13 @@ func ContextMember(ctx context.Context) *discordgo.Member { return i.(*discordgo.Member) } -func ContextMemberPerms(ctx context.Context) int { +func ContextMemberPerms(ctx context.Context) int64 { i := ctx.Value(common.ContextKeyMemberPermissions) if i == nil { return 0 } - return i.(int) + return i.(int64) } func ParamOrEmpty(r *http.Request, key string) string { diff --git a/web/validation.go b/web/validation.go index 2207794aec..6f6815fbaa 100644 --- a/web/validation.go +++ b/web/validation.go @@ -36,6 +36,7 @@ import ( "unicode/utf8" "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" "github.com/jonas747/yagpdb/common" "github.com/jonas747/yagpdb/common/templates" "github.com/lib/pq" @@ -83,12 +84,12 @@ func (p *ValidationTag) Len() int { } var ( - ErrChannelNotFound = errors.New("Channel not found") - ErrRoleNotFound = errors.New("Role not found") + ErrChannelNotFound = errors.New("channel not found") + ErrRoleNotFound = errors.New("role not found") ) // Probably needs some cleaning up -func ValidateForm(guild *discordgo.Guild, tmpl TemplateData, form interface{}) bool { +func ValidateForm(guild *dstate.GuildSet, tmpl TemplateData, form interface{}) bool { ok := true @@ -127,7 +128,7 @@ func ValidateForm(guild *discordgo.Guild, tmpl TemplateData, form interface{}) b keep, err = ValidateIntField(cv.Int64, validationTag, guild, false) if err == nil && !keep { vField.Set(reflect.ValueOf(newNullInt)) - } + } case float64: min, max := readMinMax(validationTag) err = ValidateFloatField(cv, min, max) @@ -226,7 +227,7 @@ func readMinMax(valid *ValidationTag) (float64, float64) { return min, max } -func ValidateIntSliceField(is []int64, tags *ValidationTag, guild *discordgo.Guild) (filtered []int64, err error) { +func ValidateIntSliceField(is []int64, tags *ValidationTag, guild *dstate.GuildSet) (filtered []int64, err error) { filtered = make([]int64, 0, len(is)) for _, integer := range is { keep, err := ValidateIntField(integer, tags, guild, true) @@ -242,7 +243,7 @@ func ValidateIntSliceField(is []int64, tags *ValidationTag, guild *discordgo.Gui return filtered, nil } -func ValidateIntField(i int64, tags *ValidationTag, guild *discordgo.Guild, forceAllowEmpty bool) (keep bool, err error) { +func ValidateIntField(i int64, tags *ValidationTag, guild *dstate.GuildSet, forceAllowEmpty bool) (keep bool, err error) { kind, _ := tags.Str(0) if kind != "role" && kind != "channel" { @@ -286,7 +287,7 @@ func ValidateIntField(i int64, tags *ValidationTag, guild *discordgo.Guild, forc func ValidateIntMinMaxField(i int64, min, max int64) error { if min != max && (i < min || i > max) { - return fmt.Errorf("Out of range (%d - %d)", min, max) + return fmt.Errorf("out of range (%d - %d)", min, max) } return nil @@ -295,7 +296,7 @@ func ValidateIntMinMaxField(i int64, min, max int64) error { func ValidateFloatField(f float64, min, max float64) error { if min != max && (f < min || f > max) { - return fmt.Errorf("Out of range (%f - %f)", min, max) + return fmt.Errorf("out of range (%f - %f)", min, max) } return nil @@ -303,14 +304,14 @@ func ValidateFloatField(f float64, min, max float64) error { func ValidateRegexField(s string, max int) error { if utf8.RuneCountInString(s) > max { - return fmt.Errorf("Too long (max %d)", max) + return fmt.Errorf("too long (max %d)", max) } _, err := regexp.Compile(s) return err } -func ValidateStringField(s string, tags *ValidationTag, guild *discordgo.Guild) (str string, err error) { +func ValidateStringField(s string, tags *ValidationTag, guild *dstate.GuildSet) (str string, err error) { maxLen := 2000 str = s @@ -387,11 +388,11 @@ func ValidateStringField(s string, tags *ValidationTag, guild *discordgo.Guild) func ValidateNormalStringField(s string, min, max int) error { rCount := utf8.RuneCountInString(s) if rCount > max { - return fmt.Errorf("Too long (max %d)", max) + return fmt.Errorf("too long (max %d)", max) } if rCount < min { - return fmt.Errorf("Too short (min %d)", min) + return fmt.Errorf("too short (min %d)", min) } return nil @@ -399,19 +400,19 @@ func ValidateNormalStringField(s string, min, max int) error { func ValidateTemplateField(s string, max int) error { if utf8.RuneCountInString(s) > max { - return fmt.Errorf("Too long (max %d)", max) + return fmt.Errorf("too long (max %d)", max) } _, err := templates.NewContext(nil, nil, nil).Parse(s) return err } -func ValidateChannelField(s int64, channels []*discordgo.Channel, allowEmpty bool) error { +func ValidateChannelField(s int64, channels []dstate.ChannelState, allowEmpty bool) error { if s == 0 { if allowEmpty { return nil } else { - return errors.New("No channel specified") + return errors.New("no channel specified") } } @@ -424,12 +425,12 @@ func ValidateChannelField(s int64, channels []*discordgo.Channel, allowEmpty boo return ErrChannelNotFound } -func ValidateRoleField(s int64, roles []*discordgo.Role, allowEmpty bool) error { +func ValidateRoleField(s int64, roles []discordgo.Role, allowEmpty bool) error { if s == 0 { if allowEmpty { return nil } else { - return errors.New("No role specified (or role is above bot)") + return errors.New("no role specified (or role is above bot)") } } diff --git a/web/validation_test.go b/web/validation_test.go index 6e7f7410f9..935653c1fa 100644 --- a/web/validation_test.go +++ b/web/validation_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/jonas747/discordgo" + "github.com/jonas747/dstate/v3" ) type StringTestStruct struct { @@ -108,9 +108,9 @@ var ( func TestValidationChannel(t *testing.T) { - g := &discordgo.Guild{ - Channels: []*discordgo.Channel{ - &discordgo.Channel{ + g := &dstate.GuildSet{ + Channels: []dstate.ChannelState{ + { ID: 1, }, }, diff --git a/web/web_test.go b/web/web_test.go index 89f621cc4d..38eba135fc 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -48,7 +48,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ///////////////////////// // default settings, non member access - &TestCase{ + { Name: "default settings non member access (ro)", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, false), @@ -58,7 +58,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "default settings non member access", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, false), @@ -70,7 +70,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // default settings normal member access - &TestCase{ + { Name: "default settings normal normal member access (ro)", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, false), @@ -80,7 +80,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "default settings normal user access", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, false), @@ -92,7 +92,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // default settings admin user access - &TestCase{ + { Name: "default settings admin user access (ro)", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, true), @@ -102,7 +102,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "default settings admin user access", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, false, true), @@ -114,7 +114,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // default settings owner user access - &TestCase{ + { Name: "default settings owner user access (ro)", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, true, false), @@ -124,7 +124,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "default settings owner user access", Conf: &models.CoreConfig{}, GWC: createUserGuild(true, true, false), @@ -140,7 +140,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { //////////////////////////////////// // all users ro - normal user access - &TestCase{ + { Name: "all users ro-normal user access (ro)", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -152,7 +152,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "all users ro-normal user access", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -165,7 +165,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, // all users ro - member access - &TestCase{ + { Name: "all users ro-member access (ro)", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -177,7 +177,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "all users ro-member access", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -190,7 +190,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, // all users ro - admin access - &TestCase{ + { Name: "all users ro-admin access (ro)", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -202,7 +202,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "all users ro-admin access", Conf: &models.CoreConfig{ AllowNonMembersReadOnly: true, @@ -220,7 +220,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { //////////////////////////////////// // all members ro - normal user access - &TestCase{ + { Name: "all members ro-normal user access (ro)", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -232,7 +232,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "all members ro-normal user access", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -245,7 +245,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, // all members ro - member access - &TestCase{ + { Name: "all members ro-member access (ro)", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -257,7 +257,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "all members ro-member access", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -270,7 +270,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, // all members ro - admin access - &TestCase{ + { Name: "all members ro-admin access (ro)", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -282,7 +282,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "all members ro-admin access", Conf: &models.CoreConfig{ AllowAllMembersReadOnly: true, @@ -300,7 +300,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { //////////////////////////////////// // ro roles - normal user access - &TestCase{ + { Name: "ro roles-normal user access (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -312,7 +312,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "ro roles-normal user access", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -326,7 +326,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // ro roles - member no roles - &TestCase{ + { Name: "ro roles-member no roles (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -338,7 +338,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "ro roles-member no roles", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -352,7 +352,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // ro roles - member access one role - &TestCase{ + { Name: "ro roles-member access one role (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -364,7 +364,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "ro roles-member access one role", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -378,7 +378,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // ro roles - member access other role - &TestCase{ + { Name: "ro roles-member access other role (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -390,7 +390,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "ro roles-member access other role", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -404,7 +404,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // ro roles - member access both roles - &TestCase{ + { Name: "ro roles - member access both roles (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -416,7 +416,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "ro roles - member access both roles", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -430,7 +430,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // ro roles - admin access - &TestCase{ + { Name: "ro roles-admin access (ro)", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -442,7 +442,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "ro roles-admin access", Conf: &models.CoreConfig{ AllowedReadOnlyRoles: []int64{5, 6}, @@ -460,7 +460,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { //////////////////////////////////// // write roles - normal user access - &TestCase{ + { Name: "write roles-normal user access (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -472,7 +472,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "write roles-normal user access", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -486,7 +486,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // write roles - member no roles - &TestCase{ + { Name: "write roles-member no roles (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -498,7 +498,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: false, }, - &TestCase{ + { Name: "write roles-member no roles", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -512,7 +512,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // write roles - member access one role - &TestCase{ + { Name: "write roles-member access one role (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -524,7 +524,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "write roles-member access one role", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -538,7 +538,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // write roles - member access other role - &TestCase{ + { Name: "write roles-member access other role (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -550,7 +550,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "write roles-member access other role", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -564,7 +564,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // write roles - member access both roles - &TestCase{ + { Name: "write roles - member access both roles (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -576,7 +576,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "write roles - member access both roles", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -590,7 +590,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { }, // write roles - admin access - &TestCase{ + { Name: "write roles-admin access (ro)", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6}, @@ -602,7 +602,7 @@ func TestHasAccesstoGuildSettings(t *testing.T) { ShouldHaveAcces: true, }, - &TestCase{ + { Name: "write roles-admin access", Conf: &models.CoreConfig{ AllowedWriteRoles: []int64{5, 6},