Skip to content

Commit 847cfd9

Browse files
committed
Added DynamicChannels support. BeefBot can now ensure there is always an empty channel for a given list of channels. Aka it will create a new channel with the same properties when they are all occupied and delete channels if there is more than one empty with that name.
1 parent 5c10897 commit 847cfd9

File tree

3 files changed

+147
-5
lines changed

3 files changed

+147
-5
lines changed

Beef/Application.cs

+97-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class Application : ProfileInfoProvider, MmrListener {
1717
private BeefUserConfigManager _userManager;
1818
private PresentationManager _presentationManager;
1919
private String[] _leaderRoles;
20+
private String[] _dynamicChannels; // Names of channels that should be cloned to maintain 1 empty at all times
21+
private bool _dynamicVoiceChannelsEnabled = false;
2022
private String _exePath;
2123
private MmrReader.MmrReader _mmrReader;
2224
private DispatcherSynchronizationContext _mainContext;
@@ -36,6 +38,7 @@ public Application(BeefConfig config, String exePath) {
3638
_botPrefix = config.BotPrefix;
3739
_beefCommand = config.BeefCommand;
3840
_leaderRoles = config.LeaderRoles;
41+
_dynamicChannels = config.DynamicChannels;
3942

4043
_presentationManager = new PresentationManager(
4144
_config.GoogleApiPresentationId,
@@ -87,6 +90,83 @@ private Boolean IsLeader(SocketUser socketUser) {
8790
return false;
8891
}
8992

93+
private void EnableDynamicChannels() {
94+
if (!_dynamicVoiceChannelsEnabled) {
95+
_discordClient.UserVoiceStateUpdated += OnUserVoiceStateUpdated;
96+
_dynamicVoiceChannelsEnabled = true;
97+
}
98+
}
99+
100+
private void DisableDynamicChannels() {
101+
if (_dynamicVoiceChannelsEnabled) {
102+
_discordClient.UserVoiceStateUpdated -= OnUserVoiceStateUpdated;
103+
_dynamicVoiceChannelsEnabled = false;
104+
}
105+
}
106+
107+
private async Task OnUserVoiceStateUpdated(SocketUser user, SocketVoiceState state1, SocketVoiceState state2) {
108+
await CheckDynamicChannels();
109+
}
110+
111+
private async Task CheckDynamicChannels() {
112+
if (_dynamicChannels == null)
113+
return; // Dynamic channels are not configured.
114+
115+
List<Task> tasks = new List<Task>();
116+
foreach (SocketGuild guild in _discordClient.Guilds) {
117+
String[] channelNamesToCopy = _dynamicChannels;
118+
List<SocketVoiceChannel>[] emptyChannels = new List<SocketVoiceChannel>[channelNamesToCopy.Length];
119+
List<SocketVoiceChannel>[] occupiedChannels = new List<SocketVoiceChannel>[channelNamesToCopy.Length];
120+
121+
for (int channelIndex = 0; channelIndex < channelNamesToCopy.Length; channelIndex++) {
122+
occupiedChannels[channelIndex] = new List<SocketVoiceChannel>();
123+
emptyChannels[channelIndex] = new List<SocketVoiceChannel>();
124+
}
125+
126+
foreach (SocketVoiceChannel channel in guild.VoiceChannels) {
127+
int channelIndex = 0;
128+
foreach (String channelNameToCopy in channelNamesToCopy) {
129+
if (channelNameToCopy.Equals(channel.Name)) {
130+
if (channel.Users.Count == 0) {
131+
emptyChannels[channelIndex].Add(channel);
132+
} else {
133+
occupiedChannels[channelIndex].Add(channel);
134+
}
135+
}
136+
137+
channelIndex++;
138+
}
139+
}
140+
141+
for (int channelIndex = 0; channelIndex < emptyChannels.Length; channelIndex++) {
142+
List<SocketVoiceChannel> emptyChannelList = emptyChannels[channelIndex];
143+
144+
if (emptyChannelList.Count > 1) {
145+
// Remove channels until there's 1 empty left
146+
for (int emptyIndex = 0; emptyIndex < emptyChannelList.Count - 1; emptyIndex++) {
147+
SocketVoiceChannel channel = emptyChannelList[emptyIndex];
148+
tasks.Add(channel.DeleteAsync());
149+
}
150+
} else if (emptyChannelList.Count == 0) {
151+
List<SocketVoiceChannel> occupiedChannelList = occupiedChannels[channelIndex];
152+
// Add a channel with the same name
153+
if (occupiedChannelList.Count > 0) {
154+
SocketVoiceChannel firstOccupiedChannel = occupiedChannelList[0];
155+
tasks.Add(guild.CreateVoiceChannelAsync(firstOccupiedChannel.Name, channelProperties => {
156+
channelProperties.UserLimit = firstOccupiedChannel.UserLimit;
157+
channelProperties.CategoryId = firstOccupiedChannel.CategoryId;
158+
channelProperties.Position = firstOccupiedChannel.Position;
159+
}));
160+
}
161+
}
162+
}
163+
}
164+
165+
// Await the various tasks we started.
166+
foreach (Task task in tasks)
167+
await task;
168+
}
169+
90170
private void HandleCommand(SocketMessage userInput) {
91171
// This is for Cloud...
92172
if (userInput.Content.StartsWith("I love you Beef bot!", StringComparison.CurrentCultureIgnoreCase)) {
@@ -121,6 +201,15 @@ private void HandleCommand(SocketMessage userInput) {
121201
String beefLink = sc2Beef + " **Settle the Beef** " + sc2Beef + "\n";
122202
beefLink += _config.BeefLadderLink;
123203
MessageChannel(channel, beefLink).GetAwaiter().GetResult();
204+
} else if (arguments[1] == "enableDynamicChannels") {
205+
MessageChannel(channel, "Enabling dynamic channels.").GetAwaiter().GetResult();
206+
EnableDynamicChannels();
207+
CheckDynamicChannels();
208+
code = ErrorCode.Success;
209+
} else if (arguments[1] == "disableDynamicChannels") {
210+
MessageChannel(channel, "Disabling dynamic channels.").GetAwaiter().GetResult();
211+
DisableDynamicChannels();
212+
code = ErrorCode.Success;
124213
} else if (arguments[1] == "register") {
125214
if (!IsLeader(author)) {
126215
MessageChannel(channel, "You don't have permission to do that.").GetAwaiter().GetResult();
@@ -504,6 +593,8 @@ private void HandleCommand(SocketMessage userInput) {
504593
help += "\t **%beef% switch <PlayerOrRank> <OtherPlayerOrRank>** - Switches the two players on the ladder leaving everyone else in place.\n";
505594
help += "\t **%beef% refresh** - Requests a refresh to the MMRs for each player. Note that this can take a minute.\n";
506595
help += "\t **%beef% undo** - Undoes the last change to the ladder (renames, wins, etc..).\n";
596+
help += "\t **%beef% enableDynamicChannels** - Enables dynamic channels (default). If there are dynamic channels set, the bot will ensure there is always exactly 1 empty of each configured dynamic channel.\n";
597+
help += "\t **%beef% disableDynamicChannels** - Disables dynamic channels. If there are dynamic channels set, the bot will no longer ensure there is always exactly 1 empty of each configured dynamic channel.\n";
507598
help += "\t **%beef% version** - Prints the version of BeefBot\n";
508599
help = help.Replace("%beef%", _botPrefix + _beefCommand);
509600

@@ -531,10 +622,14 @@ private Task LogAsync(LogMessage log) {
531622
/// Called when the bot is ready.
532623
/// </summary>
533624
/// <returns>An async task.</returns>
534-
private Task ReadyAsync() {
625+
private async Task ReadyAsync() {
535626
Console.WriteLine($"{_discordClient.CurrentUser} is connected!");
536627

537-
return Task.CompletedTask;
628+
// Enable dynamic voice channels if requested.
629+
if (_dynamicChannels != null) {
630+
EnableDynamicChannels();
631+
await CheckDynamicChannels();
632+
}
538633
}
539634

540635
/// <summary>

Beef/BeefConfig.cs

+40-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Beef {
77
/// </summary>
88
public class BeefConfig {
99
// This is the current version and should always be incremented when changing the config file format.
10-
public static int BeefConfigVersion = 2;
10+
public static int BeefConfigVersion = 3;
1111

1212
// Version
1313
public int Version { get; set; } = BeefConfigVersion; // This identifies the version of the config file.
@@ -17,6 +17,7 @@ public class BeefConfig {
1717
public String BotPrefix { get; set; } = ".";
1818
public String BeefCommand { get; set; } = "beef";
1919
public String[] LeaderRoles { get; set; } = new String[] { "ExampleRole1", "ExampleRole2" };
20+
public String[] DynamicChannels { get; set; } = new String[] { "Teams" }; // Names of channels that there should always be exactly 1 empty of
2021

2122
// Presentation Stuff
2223
public String GoogleApiCredentialFile { get; set; } = "credentials.json";
@@ -43,6 +44,44 @@ public static BeefConfig CreateDefault() {
4344
// able to update old versions in a smarter way.
4445
#region OldConfigVersions
4546

47+
/// <summary>
48+
/// Keeps track of settings for the config file. This class needs to exactly match the Config.json.example file so that it can be
49+
/// deserialized into it. So if you rename or change parameters here, you must upgrade the config file format as well.
50+
/// </summary>
51+
public class BeefConfigV2 {
52+
// This is the current version and should always be incremented when changing the config file format.
53+
public static int BeefConfigVersion = 2;
54+
55+
// Version
56+
public int Version { get; set; } = BeefConfigVersion; // This identifies the version of the config file.
57+
58+
// Discord stuff
59+
public String DiscordBotToken { get; set; } = "";
60+
public String BotPrefix { get; set; } = ".";
61+
public String BeefCommand { get; set; } = "beef";
62+
public String[] LeaderRoles { get; set; } = new String[] { "ExampleRole1", "ExampleRole2" };
63+
64+
// Presentation Stuff
65+
public String GoogleApiCredentialFile { get; set; } = "credentials.json";
66+
public String GoogleApiApplicationName { get; set; } = "";
67+
public String GoogleApiPresentationId { get; set; } = ""; // This is the Google doc IDs of the presentation.
68+
public String BeefLadderLink { get; set; } = ""; // This is the ladders that can be viewed by users.
69+
70+
public ReaderConfig MmrReaderConfig { get; set; } = ReaderConfig.CreateDefault();
71+
72+
/// <summary>
73+
/// Creates a ReaderConfig with default settings.
74+
/// </summary>
75+
/// <returns>Returns the created config.</returns>
76+
public static BeefConfig CreateDefault() {
77+
// Fill out the default settings and version
78+
BeefConfig config = new BeefConfig();
79+
80+
// The credentials are left blank and need to be filled out after
81+
return config;
82+
}
83+
}
84+
4685
/// <summary>
4786
/// Keeps track of settings for the config file. This class needs to exactly match the Config.json.example file so that it can be
4887
/// deserialized into it. So if you rename or change parameters here, you must upgrade the config file format as well.

Beef/config.json.example

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
{
2-
"Version": 0,
2+
"Version": 3,
33
"DiscordBotToken": "<YourDiscordBotTokenHere>"
44
"BotPrefix": ".",
55
"LeaderRoles": [ "ExampleRole1", "ExampleRole2" ],
6+
"DynamicChannels": [ "2v2 Teams", "3v3 Teams", "4v4 Teams" ],
67
"GoogleApiPresentationId": "AAAAAAAAAAAAA_BBBBBBBBBBBBBBBBBBBBBBBBBB",
78
"GoogleApiCredentialFile": "credentials.json",
89
"GoogleApiApplicationName": "<YourDiscordAppNameHere>",
9-
"BeefLadderLink": "<BeefLadderLink Here>"
10+
"BeefLadderLink": "<BeefLadderLinkHere>",
11+
"MmrReaderConfig": {
12+
"Version": 1,
13+
"MsPerRead": 600000,
14+
"DataDirectory": ".",
15+
"ClientId": "<BattleNetDevClientId>",
16+
"ClientSecret": "<BattleNetDevClientSecret>"
17+
}
1018
}

0 commit comments

Comments
 (0)