Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Менеджер для привязки игрового профиля к дискорду #2

Merged
merged 4 commits into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Content.Server/Entry/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Content.Server.Preferences.Managers;
using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.SS220.Discord;
using Content.Server.SS220.PrimeWhitelist;
using Content.Server.Voting.Managers;
using Content.Shared.Administration;
Expand Down Expand Up @@ -117,6 +118,7 @@ public override void Init()
IoCManager.Resolve<TTSManager>().Initialize(); // Corvax-TTS
IoCManager.Resolve<ServerInfoManager>().Initialize();
IoCManager.Resolve<Primelist>().Initialize();
IoCManager.Resolve<DiscordPlayerManager>().Initialize();

_voteManager.Initialize();
_updateManager.Initialize();
Expand Down
2 changes: 2 additions & 0 deletions Content.Server/IoC/ServerContentIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Content.Server.Preferences.Managers;
using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.SS220.Discord;
using Content.Server.SS220.PrimeWhitelist;
using Content.Server.Voting.Managers;
using Content.Shared.Administration;
Expand Down Expand Up @@ -68,6 +69,7 @@ public static void Register()
IoCManager.Register<DiscordAuthManager>(); // Corvax-DiscordAuth
IoCManager.Register<ServerInfoManager>();
IoCManager.Register<Primelist>();
IoCManager.Register<DiscordPlayerManager>();
}
}
}
48 changes: 48 additions & 0 deletions Content.Server/SS220/Discord/Commands/DiscordCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Text;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;

namespace Content.Server.SS220.Discord.Commands;

[AnyCommand]
public sealed class DiscordCommand : IConsoleCommand
{
/// <inheritdoc />
public string Command => "discordlink";

/// <inheritdoc />
public string Description => "Generate key for Link Discord";

/// <inheritdoc />
public string Help => "Usage: discordlink";

[Dependency] private readonly DiscordPlayerManager _discordPlayerManager = default!;

/// <inheritdoc />
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not IPlayerSession player)
return;
try
{
var key = await _discordPlayerManager.CheckAndGenerateKey(player.Data.UserName);
var sb = new StringBuilder();

if (!string.IsNullOrEmpty(key))
{
sb.Append($"Проследуйте в дискорд и используйте команду ... {key}");
}
else
{
sb.Append($"Вы уже привязали свой дискорд к игре");
}

shell.WriteLine(sb.ToString());
}
catch (Exception e)
{
shell.WriteLine("Произошла ошибка. Свяжитесь с администрацией");
Redict marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
195 changes: 195 additions & 0 deletions Content.Server/SS220/Discord/DiscordPlayerManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using System.Security.Cryptography;
SantaGitHub marked this conversation as resolved.
Show resolved Hide resolved
using System.Threading.Tasks;
using Content.Shared.CCVar;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using MySqlConnector;
using Robust.Shared.Configuration;

namespace Content.Server.SS220.Discord;

public sealed class DiscordPlayerManager
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
private DiscordDbImpl _dbImpl = default!;
private ISawmill _sawmill = default!;

public void Initialize()
{
_sawmill = Logger.GetSawmill("DiscordPlayerManager");
var host = _cfg.GetCVar(CCVars.PrimelistDatabaseIp);
var port = _cfg.GetCVar(CCVars.PrimelistDatabasePort);
var db = _cfg.GetCVar(CCVars.PrimelistDatabaseName);
var user = _cfg.GetCVar(CCVars.PrimelistDatabaseUsername);
var pass = _cfg.GetCVar(CCVars.PrimelistDatabasePassword);

var builder = new DbContextOptionsBuilder<DiscordDbImpl.DiscordDbContext>();
//TODO: поменять когда переедем на Posgresql
var connectionString = new MySqlConnectionStringBuilder()
{
Server = host,
Port = Convert.ToUInt32(port),
Database = db,
UserID = user,
Password = pass,
}.ConnectionString;

_sawmill.Debug($"Using MySQL \"{host}:{port}/{db}\"");
builder.UseMySql(connectionString, new MariaDbServerVersion(new Version(10, 11 , 2)));
_dbImpl = new DiscordDbImpl(builder.Options);
}

/// <summary>
/// Проверка, генерация ключа для дискорда.
/// Если валидация пройдена, то вернется пустая строка
/// Если валидации не было, то вернется сгенерированный ключ
/// </summary>
/// <param name="ckey"></param>
/// <returns></returns>
public async Task<string> CheckAndGenerateKey(string ckey)
{
var (validate, discordPlayer) = await _dbImpl.IsValidateDiscord(ckey);
if (!validate)
{
if (discordPlayer != null)
return discordPlayer.HashKey;

discordPlayer = new DiscordDbImpl.DiscordPlayer()
{
CKey = ckey,
HashKey = CreateSecureRandomString()
};
await _dbImpl.InsertDiscord(discordPlayer);
return discordPlayer.HashKey;

}

return string.Empty;
}

private static string CreateSecureRandomString(int count = 32) =>
Convert.ToBase64String(RandomNumberGenerator.GetBytes(count));
}

internal sealed class DiscordDbImpl
{
private readonly DbContextOptions<DiscordDbContext> _options;
private readonly ISawmill _sawmill;

public DiscordDbImpl(DbContextOptions<DiscordDbContext> options)
{
_options = options;
_sawmill = Logger.GetSawmill("DiscordDbImpl");
}

/// <summary>
/// Уже прошел проверку или нет
/// </summary>
/// <param name="ckey"></param>
public async Task<(bool, DiscordPlayer?)> IsValidateDiscord(string ckey)
{
try
{
await using var db = await GetDb();

var discordPlayer = await db.PgDbContext.DiscordPlayers.SingleOrDefaultAsync(p => p.CKey == ckey);

if (discordPlayer == null)
return (false, null);

if (string.IsNullOrEmpty(discordPlayer.DiscordId))
{
return (true, null);
}

return (false, discordPlayer);
}
catch (Exception exception)
{
_sawmill.Log(LogLevel.Error, exception,"Error insert DiscordPlayer");
throw;
}
}

public async Task InsertDiscord(DiscordPlayer discordPlayer)
{
try
{
await using var db = await GetDb();
db.PgDbContext.DiscordPlayers.Add(discordPlayer);
await db.PgDbContext.SaveChangesAsync();
}
catch (Exception exception)
{
_sawmill.Log(LogLevel.Error, exception,"Error insert DiscordPlayer");
throw;
}

}


private async Task<DbGuard> GetDb()
{
return new DbGuard(new DiscordDbContext(_options));
}

public sealed class DiscordDbContext : DbContext
{
public DiscordDbContext(DbContextOptions<DiscordDbContext> options) : base(options)
{
}

public DbSet<DiscordPlayer> DiscordPlayers { get; set; } = null!;

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<DiscordPlayer>(entity =>
{
entity.HasIndex(p => p.Id).IsUnique();
entity.HasIndex(p => new { p.CKey, p.DiscordId });
entity.Property(p => p.Id).ValueGeneratedOnAdd();
});

}

protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.ConfigureWarnings(x =>
{
x.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning);
#if DEBUG
// for tests
x.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning);
#endif
});

#if DEBUG
options.EnableSensitiveDataLogging();
#endif
}
}

private sealed class DbGuard : IAsyncDisposable
{
public DbGuard(DiscordDbContext dbC)
{
PgDbContext = dbC;
}

public DiscordDbContext PgDbContext { get; }

public ValueTask DisposeAsync()
{
return PgDbContext.DisposeAsync();
}
}

public record DiscordPlayer
{
public Guid Id { get; set; }
public string HashKey { get; set; } = null!;
public string CKey { get; set; } = null!;
public string? DiscordId { get; set; }
public string? DiscordName { get; set; }
}
}