This commit is contained in:
zznty
2022-05-24 15:49:43 +07:00
commit c20e468b99
32 changed files with 1654 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
using System.Collections.ObjectModel;
using Torch;
using Torch.Views;
namespace LightPerms.Discord;
public class Config : ViewModel
{
[Display(Name = "Token", Description = "Discord bot token")]
public string Token { get; set; } = "bot-token";
[Display(Name = "Guild Id", Description = "Id of the guild to work with")]
public ulong GuildId { get; set; }
[Display(Name = "Role Configs")]
public ObservableCollection<DiscordRoleConfig> RoleConfigs { get; set; } = new();
}
public class DiscordRoleConfig : ViewModel
{
[Display(Name = "Role Id", Description = "Id of the discord role to work with")]
public ulong RoleId { get; set; }
[Display(Name = "Group Name", Description = "Name of the group to work with (read guide on LightPerms plugin page to create a new group)")]
public string GroupName { get; set; } = "role-group";
public override string ToString() => GroupName;
}

View File

@@ -0,0 +1,143 @@
using Discord;
using Discord.WebSocket;
using NLog;
using NLog.Fluent;
using PetaPoco;
using Torch.API;
using Torch.Managers;
namespace LightPerms.Discord;
public class DiscordManager : Manager
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private readonly Config _config;
[Dependency]
private readonly IPermissionsManager _permissionsManager = null!;
private readonly DiscordSocketClient _client = new(new()
{
GatewayIntents = GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.GuildMembers
});
private IDatabase _db = null!;
public Dictionary<string, ulong> WaitingUsers { get; } = new();
public DiscordManager(ITorchBase torchInstance, Config config) : base(torchInstance)
{
_config = config;
_client.SlashCommandExecuted += ClientOnSlashCommandExecutedAsync;
_client.GuildMemberUpdated += ClientOnGuildMemberUpdatedAsync;
_client.Ready += ClientOnReady;
_client.Log += message =>
{
NLog.Fluent.Log.Level(message.Severity switch
{
LogSeverity.Critical => LogLevel.Fatal,
LogSeverity.Error => LogLevel.Error,
LogSeverity.Warning => LogLevel.Warn,
LogSeverity.Info => LogLevel.Info,
LogSeverity.Debug => LogLevel.Debug,
LogSeverity.Verbose => LogLevel.Trace,
_ => throw new ArgumentOutOfRangeException()
}).LoggerName(message.Source).Exception(message.Exception).Message(message.Message).Write();
return Task.CompletedTask;
};
}
private async Task ClientOnReady()
{
var guild = _client.GetGuild(_config.GuildId);
if (_config.RoleConfigs.Any(b => guild.Roles.All(c => c.Id != b.RoleId)))
throw new InvalidOperationException("Some roles are not exists in the guild");
await AddSlashCommandsAsync(guild);
await guild.DownloadUsersAsync();
await Task.WhenAll(guild.Users.Select(b => ClientOnGuildMemberUpdatedAsync(default, b)));
}
public override void Attach()
{
base.Attach();
_db = _permissionsManager.Db;
_db.Execute(LinkedUser.CreateTableSql);
Task.Run(AttachAsync);
}
private async Task AttachAsync()
{
await _client.LoginAsync(TokenType.Bot, _config.Token);
await _client.StartAsync();
}
private async Task ClientOnGuildMemberUpdatedAsync(Cacheable<SocketGuildUser, ulong> cacheable, SocketGuildUser user)
{
if (await _db.SingleOrDefaultAsync<LinkedUser>(Sql.Builder.Where("discord_id = @0", user.Id.ToString())) is not { } linkedUser ||
await _db.SingleOrDefaultAsync<GroupMember>(Sql.Builder.Where("client_id = @0", linkedUser.ClientId)) is not { } groupMember)
return;
bool HasRole(ulong id) => user.Roles.Any(b => b.Id == id);
foreach (var (role, group) in _config.RoleConfigs.Select(b => (b, _permissionsManager.GetGroup(b.GroupName)!)))
{
if (!HasRole(role.RoleId) && groupMember.GroupUid == group.Uid)
{
_permissionsManager.AssignGroup(linkedUser.ClientIdNumber, "player");
Log.Info($"Removed group {role.GroupName} from {linkedUser.ClientId}");
}
else if (HasRole(role.RoleId) && groupMember.GroupUid != group.Uid)
{
_permissionsManager.AssignGroup(linkedUser.ClientIdNumber, group.Name);
groupMember.GroupUid = group.Uid;
Log.Info($"Assigned group {role.GroupName} to {linkedUser.ClientId}");
}
}
}
private async Task ClientOnSlashCommandExecutedAsync(SocketSlashCommand arg)
{
if (arg.CommandName != "lp-link")
return;
if (GetClientIdByDiscordId(arg.User.Id) is { })
{
await arg.RespondAsync("You are already linked with lp.");
return;
}
var username = Format.UsernameAndDiscriminator(arg.User, false);
if (!WaitingUsers.ContainsKey(username))
{
await arg.RespondAsync("Type `!lp link` in-game first.");
return;
}
var clientId = WaitingUsers[username];
WaitingUsers.Remove(username);
await _db.InsertAsync(new LinkedUser
{
DiscordId = arg.User.Id.ToString(),
ClientId = clientId.ToString()
});
await arg.RespondAsync("Linked successfully.");
}
private Task AddSlashCommandsAsync(SocketGuild guild)
{
return guild.CreateApplicationCommandAsync(new SlashCommandBuilder()
.WithName("lp-link")
.WithDescription("Light perms link with game")
.Build());
}
public ulong? GetClientIdByDiscordId(ulong discordId)
{
if (_db.SingleOrDefault<LinkedUser>(Sql.Builder.Where("discord_id = @0", discordId.ToString())) is not { } user)
return null;
return ulong.Parse(user.ClientId);
}
}

View File

@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<PropertyChanged />
</Weavers>

View File

@@ -0,0 +1,93 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>10</LangVersion>
<UseWpf>true</UseWpf>
<TorchDir>$(SolutionDir)TorchBinaries\</TorchDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c">
<HintPath>$(TorchDir)NLog.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game, Version=0.1.1.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics, Version=0.1.1.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="Torch">
<HintPath>$(TorchDir)Torch.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Torch.API">
<HintPath>$(TorchDir)Torch.API.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Torch.Server">
<HintPath>$(TorchDir)Torch.Server.exe</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Input, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.Input.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Network, Version=1.0.53.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(TorchDir)DedicatedServer64\VRage.Network.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Discord.Net.Commands" Version="3.6.1" />
<PackageReference Include="Discord.Net.WebSocket" Version="3.6.1" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Update="manifest.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LightPerms\LightPerms.csproj" Private="false" PrivateAssets="all" />
</ItemGroup>
<Target Name="CopyToTorch" AfterTargets="Build">
<ZipDirectory DestinationFile="$(TorchDir)Plugins\$(AssemblyName).zip" SourceDirectory="$(TargetDir)" Overwrite="true" />
</Target>
</Project>

View File

@@ -0,0 +1,20 @@
using PetaPoco;
namespace LightPerms.Discord;
[TableName("linked_users")]
[PrimaryKey(nameof(Uid), AutoIncrement = true)]
public class LinkedUser
{
[Column]
public long Uid { get; set;}
[Column]
public string DiscordId { get; set; } = "0";
[Column]
public string ClientId { get; set; } = "0";
internal const string CreateTableSql = "create table if not exists linked_users (uid INTEGER PRIMARY KEY AUTOINCREMENT, discord_id TEXT NOT NULL, client_id TEXT NOT NULL);";
[Ignore]
public ulong ClientIdNumber => ulong.Parse(ClientId);
}

View File

@@ -0,0 +1,26 @@
using System.Text.RegularExpressions;
using Torch.API.Managers;
using Torch.Commands;
using Torch.Commands.Permissions;
using VRage.Game.ModAPI;
namespace LightPerms.Discord;
[Category("lp")]
public class LpCommands : CommandModule
{
private static readonly Regex DiscordTagRegex = new(@"^.{3,32}#[0-9]{4}$");
[Command("link", "Link you with your discord account. (type `/lp-link` on server discord after)")]
[Permission(MyPromoteLevel.None)]
public void Link(string discordUsername)
{
if (!DiscordTagRegex.IsMatch(discordUsername))
{
Context.Respond("Please enter a valid discord username! (like abcde#1234)");
return;
}
Context.Torch.CurrentSession.Managers.GetManager<DiscordManager>().WaitingUsers[discordUsername] = Context.Player.SteamUserId;
Context.Respond("Type `/lp-link` on server discord");
}
}

View File

@@ -0,0 +1,29 @@
using System.IO;
using System.Windows.Controls;
using Torch;
using Torch.API;
using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.API.Session;
using Torch.Views;
namespace LightPerms.Discord;
public class Plugin : TorchPluginBase, IWpfPlugin
{
private Persistent<Config> _config = null!;
public override void Init(ITorchBase torch)
{
base.Init(torch);
_config = Persistent<Config>.Load(Path.Combine(StoragePath, "LightPerms.Discord.cfg"));
Torch.Managers.GetManager<ITorchSessionManager>().AddFactory(s => new DiscordManager(s.Torch, _config.Data));
}
public UserControl GetControl() => new PropertyGrid
{
Margin = new(3),
DataContext = _config.Data
};
}

View File

@@ -0,0 +1,5 @@
Add-on for [LightPerms](https://torchapi.com/plugins/view/?guid=5c3f35b3-ac9d-486f-8559-f931536c6700) plugin to automatically assign groups based on discord role
# Setup a bot
if you already have a bot installed with **SEDB** or **Torch Alert** you use token from it else you have to follow the [guide](https://goo.gl/5Do8LJ)

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<PluginManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>LightPerms.Discord</Name>
<Guid>d53cf5e6-27ea-491b-9579-8506d93f184b</Guid>
<Version>v1.0.1</Version>
<Dependencies>
<PluginDependency>
<Plugin>5c3f35b3-ac9d-486f-8559-f931536c6700</Plugin>
<MinVersion>v1.0.1</MinVersion>
</PluginDependency>
</Dependencies>
</PluginManifest>

View File

@@ -0,0 +1,20 @@
:: This script creates a symlink to the game binaries to account for different installation directories on different systems.
@echo off
set /p path="Please enter the folder location of your Torch.Server.exe: "
cd %~dp0
rmdir TorchBinaries > nul 2>&1
mklink /J ..\TorchBinaries "%path%"
if errorlevel 1 goto Error
echo Done!
echo You can now open the plugin without issue.
goto EndFinal
:Error
echo An error occured creating the symlink.
goto EndFinal
:EndFinal
pause