Added VoiceRoleSync

Core functionality is working. Documentation and other
improvements will be done soon before creating a new
release.
This commit is contained in:
Noikoio 2018-08-17 00:45:26 -07:00
parent b06d34c5a9
commit 3f9a133382
3 changed files with 127 additions and 4 deletions

View file

@ -0,0 +1,120 @@
using Discord.WebSocket;
using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace Noikoio.RegexBot.Module.VoiceRoleSync
{
/// <summary>
/// Synchronizes a user's state in a voice channel with a role.
/// In other words: applies a role to a user entering a voice channel. Removes the role when exiting.
/// </summary>
class VoiceRoleSync : BotModule
{
// Wishlist: specify multiple definitions - multiple channels associated with multiple roles.
public VoiceRoleSync(DiscordSocketClient client) : base(client)
{
client.UserVoiceStateUpdated += Client_UserVoiceStateUpdated;
}
private async Task Client_UserVoiceStateUpdated(SocketUser argUser, SocketVoiceState before, SocketVoiceState after)
{
// Gather data.
if (!(argUser is SocketGuildUser user)) return; // not a guild user
var settings = GetState<GuildSettings>(user.Guild.Id);
var deafened = after.IsDeafened || after.IsSelfDeafened;
var (settingBefore, settingAfter) = settings.GetChannelSettings(before.VoiceChannel, after.VoiceChannel);
// Determine action(s) to take
if (before.VoiceChannel?.Id != after.VoiceChannel?.Id)
{
// Joined / Left / Moved voice channels.
if (settingBefore?.Id != settingAfter?.Id)
{
// Replace roles only if the roles to be applied are different.
if (settingBefore != null && user.Roles.Contains(settingBefore)) await user.RemoveRoleAsync(settingBefore);
if (settingAfter != null && !user.Roles.Contains(settingAfter)) await user.AddRoleAsync(settingAfter);
}
}
else
{
// In same voice channel. Deafen state may have changed.
if (after.IsDeafened || after.IsSelfDeafened)
{
if (settingAfter != null && user.Roles.Contains(settingAfter)) await user.RemoveRoleAsync(settingAfter);
}
else
{
if (settingAfter != null && !user.Roles.Contains(settingAfter)) await user.AddRoleAsync(settingAfter);
}
}
}
public override Task<object> CreateInstanceState(JToken configSection)
{
if (configSection == null) return Task.FromResult<object>(null);
if (configSection.Type != JTokenType.Object)
{
throw new RuleImportException("Expected a JSON object.");
}
return Task.FromResult<object>(new GuildSettings((JObject)configSection));
}
/// <summary>
/// Dictionary wrapper. Key = voice channel ID, Value = role.
/// </summary>
private class GuildSettings
{
private ReadOnlyDictionary<ulong, ulong> _values { get; }
public GuildSettings(JObject config)
{
// Configuration format is expected to be an object that contains other objects.
// The objects themselves should have their name be the voice channel,
// and the value be the role to be applied.
// TODO Make it accept names; currently only accepts ulongs
var values = new Dictionary<ulong, ulong>();
foreach (var item in config.Properties())
{
if (!ulong.TryParse(item.Name, out var voice))
{
throw new RuleImportException($"{item.Name} is not a voice channel ID.");
}
var valstr = item.Value.Value<string>();
if (!ulong.TryParse(valstr, out var role))
{
throw new RuleImportException($"{valstr} is not a role ID.");
}
values[voice] = role;
}
_values = new ReadOnlyDictionary<ulong, ulong>(values);
}
/// <summary>
/// Gets designated roles for the given two channels (before, after).
/// Returns null in either for no result/specified role.
/// </summary>
public (SocketRole, SocketRole) GetChannelSettings(SocketVoiceChannel before, SocketVoiceChannel after)
=> (GetIndividualResult(before), GetIndividualResult(after));
private SocketRole GetIndividualResult(SocketVoiceChannel ch)
{
if (ch == null) return null;
if (_values.TryGetValue(ch.Id, out var roleId))
{
return ch.Guild.GetRole(roleId);
}
return null;
}
}
}
}

View file

@ -59,6 +59,7 @@ namespace Noikoio.RegexBot
new Module.ModCommands.ModCommands(_client), new Module.ModCommands.ModCommands(_client),
new Module.AutoRespond.AutoRespond(_client), new Module.AutoRespond.AutoRespond(_client),
new Module.EntryAutoRole.EntryAutoRole(_client), new Module.EntryAutoRole.EntryAutoRole(_client),
new Module.VoiceRoleSync.VoiceRoleSync(_client),
// EntityCache loads before anything using it // EntityCache loads before anything using it
new EntityCache.ECModule(_client), new EntityCache.ECModule(_client),

View file

@ -4,10 +4,12 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<RootNamespace>Noikoio.RegexBot</RootNamespace> <RootNamespace>Noikoio.RegexBot</RootNamespace>
<AssemblyVersion>2.6.0.0</AssemblyVersion> <AssemblyVersion>2.6.2</AssemblyVersion>
<Description>Highly configurable Discord moderation bot</Description> <Description>Highly configurable Discord moderation bot</Description>
<Authors>Noikoio</Authors> <Authors>Noikoio</Authors>
<Company /> <Company />
<FileVersion>2.6.2</FileVersion>
<Version>2.6.2</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@ -16,9 +18,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Discord.Net" Version="1.0.2" /> <PackageReference Include="Discord.Net" Version="1.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Npgsql" Version="3.2.7" /> <PackageReference Include="Npgsql" Version="4.0.2" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>