using axXez.TS3.Protocol; using System.Collections.Generic; using System.Linq; using System.Text; namespace axXez.TS3.Bot.Modules { [Module(Namespace = "axXez", DisplayName = "Auto AFK Switch")] internal class AutoAFK : Module { public string AfkChannel { get; set; } = null; public int AfkSwitchTimeFullMuted { get; set; } = 14400; public int AfkSwitchTimeMuted { get; set; } = 5400; public int AfkSwitchTimeUnmuted { get; set; } = 5400; public List ExcludedChannels { get; set; } = new(); public List ExcludedServerGroups { get; set; } = new(); private int _afkChannelId = -1; private HashSet _excludedChannelIds = new(); private HashSet _excludedGroupIds = new(); protected override void OnStart() { _afkChannelId = AfkChannel != null && Server.FindChannel(AfkChannel, out var ch) ? ch.ID : -1; foreach (var entry in ExcludedChannels) if (Server.FindChannel(entry, out var c)) _excludedChannelIds.Add(c.ID); foreach (var entry in ExcludedServerGroups) if (Server.FindServerGroup(entry, out var g)) _excludedGroupIds.Add(g.ID); } public override void OnClientUpdated(Client client, TsEntityChangeInfo changes) { if (_afkChannelId == -1 || client.UID == Server.QueryClient.UID || client.ChannelID == _afkChannelId || _excludedChannelIds.Contains(client.ChannelID)) return; if (_excludedGroupIds.Count > 0 && client.ServerGroups.Split(',').Select(int.Parse).Any(_excludedGroupIds.Contains)) return; if (changes.Has(nameof(Client.IdleTime))) { bool fullMuted = client.OutputMuted || !client.OutputEnabled; bool micMuted = client.InputMuted || !client.InputEnabled; if ((client.IdleTime.TotalSeconds >= AfkSwitchTimeFullMuted && fullMuted) || (client.IdleTime.TotalSeconds >= AfkSwitchTimeMuted && !fullMuted && micMuted) || (client.IdleTime.TotalSeconds >= AfkSwitchTimeUnmuted && !fullMuted && !micMuted)) { Query.MoveClient(client.ID, _afkChannelId); if (!fullMuted && !micMuted) { //Query.PokeClient(client.ID, $"Du wurdest aufgrund von {client.IdleTime:hh\\:mm} Inaktivitätszeit in den AFK Channel verschoben. Bitte mute dich in Zukunft."); << zu lang ... 101 Zeichen MAX Query.PokeClient(client.ID, "Du wurdest aufgrund von Inaktivität in den AFK Channel verschoben. Bitte mute dich in Zukunft."); } Log($"moveClient: {client.Name} (fullMuted: {fullMuted}, micMuted: {micMuted}, idle: {client.IdleTime.TotalSeconds})"); } } } [Command("afk status", Description = "Shows AFK switch configuration")] public CommandResponse Status(CommandContext ctx) { var sb = new StringBuilder(); sb.AppendLine("[B]Auto AFK Switch[/B]"); if (AfkChannel == null) sb.AppendLine(" Channel: [COLOR=gray]not set[/COLOR]"); else if (_afkChannelId == -1) sb.AppendLine($" Channel: [B]{AfkChannel}[/B] [COLOR=red](not found)[/COLOR]"); else sb.AppendLine($" Channel: [B]{AfkChannel}[/B]"); sb.AppendLine($" Idle time (full muted): [B]{AfkSwitchTimeFullMuted}s[/B]"); sb.AppendLine($" Idle time (mic muted): [B]{AfkSwitchTimeMuted}s[/B]"); sb.AppendLine($" Idle time (unmuted): [B]{AfkSwitchTimeUnmuted}s[/B]"); if (ExcludedChannels.Count > 0) sb.AppendLine(" Excluded channels: " + string.Join(", ", ExcludedChannels.Select(e => $"[B]{e}[/B]"))); if (ExcludedServerGroups.Count > 0) sb.AppendLine(" Excluded groups: " + string.Join(", ", ExcludedServerGroups.Select(e => $"[B]{e}[/B]"))); return ctx.Ok(sb.ToString().TrimEnd()); } [Command("afk channel", Description = "Sets the AFK channel", Role = CommandRole.Admin)] public CommandResponse SetChannel(CommandContext ctx, [CommandParam("channel")] string channelName) { if (!Server.FindChannel(channelName, out var ch)) return ctx.Error($"Channel [B]{channelName}[/B] not found."); AfkChannel = ch.Name; _afkChannelId = ch.ID; Save(); return ctx.Ok($"AFK channel set to [B]{ch.Name}[/B]."); } [Command("afk time fullmuted", Description = "Sets idle threshold for fully muted clients in seconds", Role = CommandRole.Admin)] public CommandResponse SetTimeFullMuted(CommandContext ctx, int seconds) { AfkSwitchTimeFullMuted = seconds; Save(); return ctx.Ok($"Full muted idle threshold set to [B]{seconds}s[/B]."); } [Command("afk time muted", Description = "Sets idle threshold for mic-muted clients in seconds", Role = CommandRole.Admin)] public CommandResponse SetTimeMuted(CommandContext ctx, int seconds) { AfkSwitchTimeMuted = seconds; Save(); return ctx.Ok($"Mic muted idle threshold set to [B]{seconds}s[/B]."); } [Command("afk time unmuted", Description = "Sets idle threshold for unmuted clients in seconds", Role = CommandRole.Admin)] public CommandResponse SetTimeUnmuted(CommandContext ctx, int seconds) { AfkSwitchTimeUnmuted = seconds; Save(); return ctx.Ok($"Unmuted idle threshold set to [B]{seconds}s[/B]."); } [Command("afk exclude channel add", Description = "Excludes a channel from AFK switching", Role = CommandRole.Admin)] public CommandResponse ExcludeChannelAdd(CommandContext ctx, [CommandParam("channel")] string channelName) { if (!Server.FindChannel(channelName, out var ch)) return ctx.Error($"Channel [B]{channelName}[/B] not found."); if (_excludedChannelIds.Contains(ch.ID)) return ctx.Error($"Channel [B]{ch.Name}[/B] is already excluded."); ExcludedChannels.Add(ch.Name); _excludedChannelIds.Add(ch.ID); Save(); return ctx.Ok($"Channel [B]{ch.Name}[/B] added to exclusions."); } [Command("afk exclude channel remove", Description = "Removes a channel from AFK exclusions", Role = CommandRole.Admin)] public CommandResponse ExcludeChannelRemove(CommandContext ctx, [CommandParam("channel")] string channelName) { if (!Server.FindChannel(channelName, out var ch)) return ctx.Error($"Channel [B]{channelName}[/B] not found."); if (!_excludedChannelIds.Contains(ch.ID)) return ctx.Error($"Channel [B]{ch.Name}[/B] is not in the exclusion list."); ExcludedChannels.RemoveAll(e => Server.FindChannel(e, out var c) && c.ID == ch.ID); _excludedChannelIds.Remove(ch.ID); Save(); return ctx.Ok($"Channel [B]{ch.Name}[/B] removed from exclusions."); } [Command("afk exclude group add", Description = "Excludes a server group from AFK switching", Role = CommandRole.Admin)] public CommandResponse ExcludeGroupAdd(CommandContext ctx, [CommandParam("group")] string groupName) { if (!Server.FindServerGroup(groupName, out var g)) return ctx.Error($"Server group [B]{groupName}[/B] not found."); if (_excludedGroupIds.Contains(g.ID)) return ctx.Error($"Group [B]{g.Name}[/B] is already excluded."); ExcludedServerGroups.Add(g.Name); _excludedGroupIds.Add(g.ID); Save(); return ctx.Ok($"Server group [B]{g.Name}[/B] added to exclusions."); } [Command("afk exclude group remove", Description = "Removes a server group from AFK exclusions", Role = CommandRole.Admin)] public CommandResponse ExcludeGroupRemove(CommandContext ctx, [CommandParam("group")] string groupName) { if (!Server.FindServerGroup(groupName, out var g)) return ctx.Error($"Server group [B]{groupName}[/B] not found."); if (!_excludedGroupIds.Contains(g.ID)) return ctx.Error($"Group [B]{g.Name}[/B] is not in the exclusion list."); ExcludedServerGroups.RemoveAll(e => Server.FindServerGroup(e, out var sg) && sg.ID == g.ID); _excludedGroupIds.Remove(g.ID); Save(); return ctx.Ok($"Server group [B]{g.Name}[/B] removed from exclusions."); } } }