Skip to content

Commit

Permalink
Add unmark/unwhitelist ability
Browse files Browse the repository at this point in the history
  • Loading branch information
leighmacdonald committed Mar 11, 2023
1 parent 4ddd5e9 commit 01e3eda
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 50 deletions.
78 changes: 58 additions & 20 deletions internal/detector/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,18 @@ func (bd *BD) LaunchGameAndWait() {
}
}

func (bd *BD) OnUnMark(sid64 steamid.SID64) error {
bd.gameStateUpdate <- updateGameStateEvent{
kind: updateMark,
source: bd.settings.GetSteamId(),
data: updateMarkEvent{
target: sid64,
delete: true,
},
}
return nil
}

func (bd *BD) OnMark(sid64 steamid.SID64, attrs []string) error {
bd.gameStateUpdate <- updateGameStateEvent{
kind: updateMark,
Expand All @@ -338,12 +350,13 @@ func (bd *BD) OnMark(sid64 steamid.SID64, attrs []string) error {
return nil
}

func (bd *BD) OnWhitelist(sid64 steamid.SID64) error {
func (bd *BD) OnWhitelist(sid64 steamid.SID64, enabled bool) error {
bd.gameStateUpdate <- updateGameStateEvent{
kind: updateWhitelist,
source: bd.settings.GetSteamId(),
data: updateWhitelistEvent{
target: sid64,
target: sid64,
enabled: enabled,
},
}
return nil
Expand Down Expand Up @@ -393,10 +406,12 @@ type updateGameStateEvent struct {
type updateMarkEvent struct {
target steamid.SID64
attrs []string
delete bool
}

type updateWhitelistEvent struct {
target steamid.SID64
target steamid.SID64
enabled bool
}

type messageEvent struct {
Expand Down Expand Up @@ -814,13 +829,14 @@ func (bd *BD) onUpdateStatus(ctx context.Context, store store.DataStore, steamID
func (bd *BD) onUpdateWhitelist(event updateWhitelistEvent) error {
player := bd.GetPlayer(event.target)
if player == nil {
return errors.New("Unknown player, cannot whitelist")
return errors.New("Unknown player, cannot update whitelist")
}
bd.playersMu.Lock()
player.Whitelisted = true
player.Whitelisted = event.enabled
player.Touch()
bd.playersMu.Unlock()
bd.logger.Info("Whitelisted player successfully", zap.Int64("steam_id", player.SteamId.Int64()))
bd.logger.Info("Update player whitelist status successfully",
zap.Int64("steam_id", player.SteamId.Int64()), zap.Bool("enabled", event.enabled))
return nil
}

Expand All @@ -836,14 +852,27 @@ func (bd *BD) onUpdateMark(status updateMarkEvent) error {
if name == "" {
name = player.NamePrevious
}
if errMark := bd.rules.Mark(rules.MarkOpts{
SteamID: status.target,
Attributes: status.attrs,
Name: name,
}); errMark != nil {
return errors.Wrap(errMark, "Failed to add mark")
if status.delete {
bd.rules.Unmark(status.target)
bd.playersMu.Lock()
for idx := range bd.players {
if bd.players[idx].SteamId == status.target {
bd.players[idx].Match = nil
break
}
}
bd.playersMu.Unlock()
bd.gui.UpdatePlayerState(bd.players)
} else {
if errMark := bd.rules.Mark(rules.MarkOpts{
SteamID: status.target,
Attributes: status.attrs,
Name: name,
}); errMark != nil {
return errors.Wrap(errMark, "Failed to add mark")
}
}
of, errOf := os.OpenFile(bd.settings.LocalPlayerListPath(), os.O_RDWR|os.O_CREATE, 0666)
of, errOf := os.OpenFile(bd.settings.LocalPlayerListPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
if errOf != nil {
return errors.Wrap(errOf, "Failed to open player list for updating")
}
Expand Down Expand Up @@ -913,23 +942,32 @@ func (bd *BD) checkPlayerStates(ctx context.Context, validTeam model.Team) {
}

func (bd *BD) triggerMatch(ctx context.Context, ps *model.Player, match *rules.MatchResult) {
msg := "Matched player"
if ps.Whitelisted {
msg = "Matched whitelisted player"
ps.RLock()
announceGeneralLast := ps.AnnouncedGeneralLast
announcePartyLast := ps.AnnouncedPartyLast
ps.RUnlock()
if time.Since(announceGeneralLast) >= model.DurationAnnounceMatchTimeout {
msg := "Matched player"
if ps.Whitelisted {
msg = "Matched whitelisted player"
}
bd.logger.Info(msg, zap.String("match_type", match.MatcherType),
zap.Int64("steam_id", ps.SteamId.Int64()), zap.String("name", ps.Name), zap.String("origin", match.Origin))
bd.playersMu.Lock()
ps.AnnouncedGeneralLast = time.Now()
bd.playersMu.Unlock()
}
bd.logger.Info(msg, zap.String("match_type", match.MatcherType),
zap.Int64("steam_id", ps.SteamId.Int64()), zap.String("name", ps.Name), zap.String("origin", match.Origin))
if ps.Whitelisted {
return
}
if bd.settings.GetPartyWarningsEnabled() && time.Since(ps.AnnouncedLast) >= model.DurationAnnounceMatchTimeout {
if bd.settings.GetPartyWarningsEnabled() && time.Since(announcePartyLast) >= model.DurationAnnounceMatchTimeout {
// Don't spam friends, but eventually remind them if they manage to forget long enough
if errLog := bd.SendChat(ctx, model.ChatDestParty, "(%d) [%s] [%s] %s ", ps.UserId, match.Origin, strings.Join(match.Attributes, ","), ps.Name); errLog != nil {
bd.logger.Error("Failed to send party log message", zap.Error(errLog))
return
}
bd.playersMu.Lock()
ps.AnnouncedLast = time.Now()
ps.AnnouncedPartyLast = time.Now()
bd.playersMu.Unlock()
}
if bd.settings.GetKickerEnabled() {
Expand Down
2 changes: 1 addition & 1 deletion internal/detector/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func newLogReader(logger *zap.Logger, path string, outChan chan string, echo boo
Follow: true,
ReOpen: true,
MustExist: false,
Poll: runtime.GOOS == "windows",
Poll: runtime.GOOS == "windows", // TODO Is this still required years later?
Logger: tailLogger,
}
//goland:noinspection ALL
Expand Down
4 changes: 3 additions & 1 deletion internal/model/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ type Player struct {
KickAttemptCount int

// Tracks the duration between announces to chat
AnnouncedLast time.Time
AnnouncedPartyLast time.Time

AnnouncedGeneralLast time.Time

// Dangling will be true when the user is new and doesn't have a physical entry in the database yet.
Dangling bool
Expand Down
56 changes: 45 additions & 11 deletions internal/ui/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ func generateAttributeMenu(window fyne.Window, sid64 steamid.SID64, attrList bin
showUserError(markFunc(clsSteamId, []string{clsAttribute}), window)
}
}
markAsMenuLabel := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "menu_markas_label", Other: "Mark As..."}})
markAsMenuLabel := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{ID: "menu_markas_label", Other: "Mark As..."}})
markAsMenu := fyne.NewMenu(markAsMenuLabel)
knownAttributes, _ := attrList.Get()
sort.Slice(knownAttributes, func(i, j int) bool {
Expand All @@ -58,7 +59,8 @@ func generateAttributeMenu(window fyne.Window, sid64 steamid.SID64, attrList bin
entry := widget.NewEntry()
entry.Validator = func(s string) error {
if s == "" {
msg := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "error_attribute_empty", Other: "Attribute cannot be empty"}})
msg := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{ID: "error_attribute_empty", Other: "Attribute cannot be empty"}})
return errors.New(msg)
}
for _, knownAttr := range knownAttributes {
Expand Down Expand Up @@ -151,6 +153,29 @@ func generateSteamIdMenu(window fyne.Window, steamId steamid.SID64) *fyne.Menu {
return m
}

func generateWhitelistMenu(parent fyne.Window, ui *Ui, steamID steamid.SID64) *fyne.Menu {
title := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "menu_title_whitelist", Other: "Whitelist"}})
labelAdd := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "menu_whitelist_add", Other: "Enable"}})
labelRemove := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "menu_whitelist_remove", Other: "Disable"}})
m := fyne.NewMenu(title,
&fyne.MenuItem{
Icon: theme.ContentAddIcon(),
Label: labelAdd,
Action: func() {
showUserError(ui.bd.OnWhitelist(steamID, true), parent)
},
},
&fyne.MenuItem{
Icon: theme.ContentRemoveIcon(),
Label: labelRemove,
Action: func() {
showUserError(ui.bd.OnWhitelist(steamID, false), parent)
},
},
)
return m
}

func generateKickMenu(ctx context.Context, parent fyne.Window, userId int64, kickFunc model.KickFunc) *fyne.Menu {
fn := func(reason model.KickReason) func() {
return func() {
Expand All @@ -173,11 +198,12 @@ func generateKickMenu(ctx context.Context, parent fyne.Window, userId int64, kic
func generateUserMenu(ctx context.Context, window fyne.Window, ui *Ui, steamId steamid.SID64, userId int64, knownAttributes binding.StringList) *fyne.Menu {
kickTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_call_vote", Other: "Call Vote..."}})
markTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_mark", Other: "Mark As..."}})
unMarkTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_unmark", Other: "Unmark"}})
externalTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_external", Other: "Open External..."}})
steamIdTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_steam_id", Other: "Copy SteamID..."}})
chatHistoryTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_chat_hist", Other: "View Chat History"}})
nameHistoryTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_name_hist", Other: "View Name History"}})
whitelistTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_whitelist", Other: "Whitelist Player"}})
whitelistTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_whitelist", Other: "Whitelist"}})
notesTitle := tr.Localizer.MustLocalize(&i18n.LocalizeConfig{DefaultMessage: &i18n.Message{ID: "user_menu_notes", Other: "Edit Notes"}})
var items []*fyne.MenuItem
if userId > 0 {
Expand All @@ -186,11 +212,23 @@ func generateUserMenu(ctx context.Context, window fyne.Window, ui *Ui, steamId s
ChildMenu: generateKickMenu(ctx, window, userId, ui.bd.CallVote),
Label: kickTitle})
}
unMarkFn := func(steamID steamid.SID64) func() {
clsSteamId := steamID
return func() {
showUserError(ui.bd.OnUnMark(clsSteamId), window)
}
}
items = append(items, []*fyne.MenuItem{
{
Icon: theme.ZoomFitIcon(),
ChildMenu: generateAttributeMenu(window, steamId, knownAttributes, ui.bd.OnMark),
Label: markTitle},
Label: markTitle,
},
{
Icon: theme.DeleteIcon(),
Label: unMarkTitle,
Action: unMarkFn(steamId),
},
{
Icon: theme.SearchIcon(),
ChildMenu: generateExternalLinksMenu(ui.logger, steamId, ui.settings.GetLinks(), ui.application.OpenURL),
Expand All @@ -212,13 +250,9 @@ func generateUserMenu(ctx context.Context, window fyne.Window, ui *Ui, steamId s
},
Label: nameHistoryTitle},
{
Icon: theme.VisibilityOffIcon(),
Action: func() {
if err := ui.bd.OnWhitelist(steamId); err != nil {
showUserError(err, window)
}
},
Label: whitelistTitle},
Icon: theme.VisibilityOffIcon(),
ChildMenu: generateWhitelistMenu(window, ui, steamId),
Label: whitelistTitle},
{
Icon: theme.DocumentCreateIcon(),
Action: func() {
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var (
builtBy string = "src"
)

func MustCreateLogger(logFile string) *zap.Logger {
func mustCreateLogger(logFile string) *zap.Logger {
loggingConfig := zap.NewProductionConfig()
//loggingConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
if logFile != "" {
Expand Down Expand Up @@ -57,7 +57,7 @@ func main() {
if settings.DebugLogEnabled {
logFilePath = settings.LogFilePath()
}
logger := MustCreateLogger(logFilePath)
logger := mustCreateLogger(logFilePath)
defer func() {
if errSync := logger.Sync(); errSync != nil {
fmt.Printf("Failed to sync log: %v\n", errSync)
Expand Down
83 changes: 68 additions & 15 deletions pkg/rules/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,82 @@ func (e *Engine) FindNewestEntries(max int, validAttrs []string) steamid.Collect
return valid
}

func (e *Engine) Unmark(steamID steamid.SID64) bool {
e.Lock()
defer e.Unlock()
if len(e.playerLists) == 0 {
return false
}
found := false
var players []playerDefinition
for _, knownPlayer := range e.playerLists[0].Players {
strId := steamID.String()
if knownPlayer.SteamID == strId {
found = true
continue
}
players = append(players, knownPlayer)
}
e.playerLists[0].Players = players
// Remove the matcher from memory
var validMatchers []SteamIDMatcher
for _, matcher := range e.matchersSteam {
if match := matcher.Match(steamID); match == nil {
validMatchers = append(validMatchers, matcher)
}
}
e.matchersSteam = validMatchers
return found
}

func (e *Engine) Mark(opts MarkOpts) error {
if len(opts.Attributes) == 0 {
return errors.New("Invalid attribute count")
}
e.Lock()
for _, knownPlayer := range e.playerLists[0].Players {
strId := opts.SteamID.String()
if knownPlayer.SteamID == strId {
e.Unlock()
return errDuplicateSteamID
updatedAttributes := false
for idx, knownPlayer := range e.playerLists[0].Players {
knownSid64, errSid64 := steamid.StringToSID64(knownPlayer.SteamID)
if errSid64 != nil {
continue
}
if knownSid64 == opts.SteamID {
var newAttr []string
for _, updatedAttr := range opts.Attributes {
isNew := true
for _, existingAttr := range knownPlayer.Attributes {
if strings.EqualFold(updatedAttr, existingAttr) {
isNew = false
break
}
}
if isNew {
newAttr = append(newAttr, updatedAttr)
}
}
if len(newAttr) == 0 {
e.Unlock()
return errDuplicateSteamID
}
e.playerLists[0].Players[idx].Attributes = append(e.playerLists[0].Players[idx].Attributes, newAttr...)
updatedAttributes = true
}
}
e.playerLists[0].Players = append(e.playerLists[0].Players, playerDefinition{
Attributes: opts.Attributes,
LastSeen: playerLastSeen{
Time: int(time.Now().Unix()),
PlayerName: opts.Name,
},
SteamID: opts.SteamID.String(),
Proof: opts.Proof,
})
if !updatedAttributes {
e.playerLists[0].Players = append(e.playerLists[0].Players, playerDefinition{
Attributes: opts.Attributes,
LastSeen: playerLastSeen{
Time: int(time.Now().Unix()),
PlayerName: opts.Name,
},
SteamID: opts.SteamID.String(),
Proof: opts.Proof,
})
}
e.Unlock()
e.registerSteamIDMatcher(newSteamIDMatcher(LocalRuleName, opts.SteamID, opts.Attributes))
if !updatedAttributes {
e.registerSteamIDMatcher(newSteamIDMatcher(LocalRuleName, opts.SteamID, opts.Attributes))
}
return nil
}

Expand Down

0 comments on commit 01e3eda

Please sign in to comment.