mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 15:47:26 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4702b34eb5 | ||
|
|
4d28fabc9d | ||
|
|
474a6d358b | ||
|
|
5e6b27a860 | ||
|
|
008b84902a | ||
|
|
52d887faff | ||
|
|
097d8282c8 | ||
|
|
8919a88ea0 | ||
|
|
2dcf965e41 | ||
|
|
c776571860 | ||
|
|
78dd74e5c1 | ||
|
|
b3d15cdfd4 | ||
|
|
92883eb3ff | ||
|
|
53773326df | ||
|
|
2281cdbf8d | ||
|
|
ff20b12532 | ||
|
|
8e62f86239 |
@@ -312,3 +312,6 @@ Resources/MapImages
|
||||
|
||||
# Changelog package lock
|
||||
package-lock.json
|
||||
|
||||
# Discord Bot Config
|
||||
link-config.json
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -48,6 +49,18 @@ namespace Content.Client.Info
|
||||
changelogButton.OnPressed += args => UserInterfaceManager.GetUIController<ChangelogUIController>().ToggleWindow();
|
||||
buttons.AddChild(changelogButton);
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
var linkAccount = UserInterfaceManager.GetUIController<LinkAccountUIController>();
|
||||
var linkAccountButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("ui-link-discord-account"),
|
||||
};
|
||||
linkAccountButton.OnPressed += _ => linkAccount.ToggleWindow();
|
||||
buttons.AddChild(linkAccountButton);
|
||||
|
||||
AddInfoButton("ui-patreon", CCVars.InfoLinksPatreon);
|
||||
// END DeltaV
|
||||
|
||||
void AddInfoButton(string loc, CVarDef<string> cVar)
|
||||
{
|
||||
var button = new Button { Text = Loc.GetString(loc) };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Chat.Managers;
|
||||
@@ -62,6 +63,7 @@ namespace Content.Client.IoC
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
collection.Register<TitleWindowManager>();
|
||||
collection.Register<ClientsidePlaytimeTrackingManager>();
|
||||
collection.Register<LinkAccountManager>(); // DeltaV - Discord Linking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Audio;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.LateJoin;
|
||||
@@ -29,6 +30,7 @@ namespace Content.Client.Lobby
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly ClientsidePlaytimeTrackingManager _playtimeTracking = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Linking
|
||||
|
||||
private ClientGameTicker _gameTicker = default!;
|
||||
private ContentAudioSystem _contentAudioSystem = default!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Humanoid;
|
||||
using Content.Client.Inventory;
|
||||
@@ -38,6 +39,7 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly JobRequirementsManager _requirements = default!;
|
||||
[Dependency] private readonly MarkingManager _markings = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Account Linking
|
||||
[UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
|
||||
[UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
|
||||
[UISystemDependency] private readonly StationSpawningSystem _spawn = default!;
|
||||
@@ -75,6 +77,8 @@ public sealed class LobbyUIController : UIController, IOnStateEntered<LobbyState
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleLoadoutTimers, _ => RefreshProfileEditor());
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.GameRoleWhitelist, _ => RefreshProfileEditor());
|
||||
|
||||
_linkAccount.Updated += RefreshProfileEditor; // DeltaV - Discord Account Linking
|
||||
}
|
||||
|
||||
private LobbyCharacterPreviewPanel? GetLobbyPreview()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client._DV.AccountLinking; // DeltaV
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared._EE.CCVars; // EE - chat stack
|
||||
@@ -17,6 +18,7 @@ public sealed partial class MiscTab : Control
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!; // DeltaV - Discord Account Linking
|
||||
|
||||
public MiscTab()
|
||||
{
|
||||
@@ -48,7 +50,7 @@ public sealed partial class MiscTab : Control
|
||||
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
ShowOocPatronColor.Visible = _linkAccount.Tier != null; // DeltaV - Discord Account Linking
|
||||
|
||||
Control.AddOptionDropDown(CVars.InterfaceTheme, DropDownHudTheme, themeEntries);
|
||||
Control.AddOptionDropDown(CCVars.UILayout, DropDownHudLayout, layoutEntries);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
|
||||
private readonly List<SharedPatron> _allPatrons = [];
|
||||
|
||||
public SharedPatronTier? Tier { get; private set; }
|
||||
public bool Linked { get; private set; }
|
||||
|
||||
public event Action<Guid>? CodeReceived;
|
||||
public event Action? Updated;
|
||||
|
||||
private void OnCode(LinkAccountCodeMsg message)
|
||||
{
|
||||
CodeReceived?.Invoke(message.Code);
|
||||
}
|
||||
|
||||
private void OnStatus(LinkAccountStatusMsg ev)
|
||||
{
|
||||
Tier = ev.Patron?.Tier;
|
||||
Linked = ev.Patron?.Linked ?? false;
|
||||
Updated?.Invoke();
|
||||
}
|
||||
|
||||
private void OnPatronList(PatronListMsg ev)
|
||||
{
|
||||
_allPatrons.Clear();
|
||||
_allPatrons.AddRange(ev.Patrons);
|
||||
}
|
||||
|
||||
public IReadOnlyList<SharedPatron> GetPatrons()
|
||||
{
|
||||
return _allPatrons;
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<LinkAccountCodeMsg>(OnCode);
|
||||
_net.RegisterNetMessage<LinkAccountRequestMsg>();
|
||||
_net.RegisterNetMessage<LinkAccountStatusMsg>(OnStatus);
|
||||
_net.RegisterNetMessage<PatronListMsg>(OnPatronList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountSystem : EntitySystem;
|
||||
@@ -0,0 +1,107 @@
|
||||
using Content.Client.Lobby.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IClipboardManager _clipboard = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IUriOpener _uriOpener = default!;
|
||||
|
||||
private LinkAccountWindow? _window;
|
||||
private TimeSpan _disableUntil;
|
||||
|
||||
private Guid _code;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_linkAccount.CodeReceived += OnCode;
|
||||
_linkAccount.Updated += OnUpdated;
|
||||
}
|
||||
|
||||
private void OnCode(Guid code)
|
||||
{
|
||||
_code = code;
|
||||
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
_window.CopyButton.Disabled = false;
|
||||
}
|
||||
|
||||
private void OnUpdated()
|
||||
{
|
||||
if (UIManager.ActiveScreen is not LobbyGui gui)
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public void ToggleWindow()
|
||||
{
|
||||
if (_window == null)
|
||||
{
|
||||
_window = new LinkAccountWindow();
|
||||
_window.OnClose += () => _window = null;
|
||||
_window.Label.SetMarkupPermissive($"{Loc.GetString("ui-link-discord-account-text")}");
|
||||
if (_linkAccount.Linked)
|
||||
_window.Label.SetMarkupPermissive($"{Loc.GetString("ui-link-discord-account-already-linked")}\n\n{Loc.GetString("ui-link-discord-account-text")}");
|
||||
|
||||
_window.CopyButton.OnPressed += _ =>
|
||||
{
|
||||
_clipboard.SetText(_code.ToString());
|
||||
_window.CopyButton.Text = Loc.GetString("ui-link-discord-account-copied");
|
||||
_window.CopyButton.Disabled = true;
|
||||
_disableUntil = _timing.RealTime.Add(TimeSpan.FromSeconds(3));
|
||||
};
|
||||
|
||||
var messageLink = _config.GetCVar(DCCVars.DiscordAccountLinkingMessageLink);
|
||||
if (string.IsNullOrEmpty(messageLink))
|
||||
{
|
||||
_window.LinkButton.Visible = false;
|
||||
_window.CopyButton.RemoveStyleClass("OpenRight");
|
||||
}
|
||||
else
|
||||
{
|
||||
_window.LinkButton.Visible = true;
|
||||
_window.LinkButton.OnPressed += _ => _uriOpener.OpenUri(messageLink);
|
||||
_window.CopyButton.AddStyleClass("OpenRight");
|
||||
}
|
||||
|
||||
_window.OpenCentered();
|
||||
|
||||
if (_code == default)
|
||||
_window.CopyButton.Disabled = true;
|
||||
|
||||
_net.ClientSendMessage(new LinkAccountRequestMsg());
|
||||
return;
|
||||
}
|
||||
|
||||
_window.Close();
|
||||
_window = null;
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
if (_window == null)
|
||||
return;
|
||||
|
||||
var time = _timing.RealTime;
|
||||
if (_disableUntil != default && time > _disableUntil)
|
||||
{
|
||||
_disableUntil = default;
|
||||
_window.CopyButton.Text = Loc.GetString("ui-link-discord-account-copy");
|
||||
_window.CopyButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<linkAccount:LinkAccountWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:linkAccount="clr-namespace:Content.Client._DV.AccountLinking"
|
||||
Title="{Loc 'ui-link-discord-account'}">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="Label" Access="Public" Margin="0 0 0 5" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center"
|
||||
HorizontalExpand="True">
|
||||
<Button Name="CopyButton" Access="Public" StyleClasses="OpenRight"
|
||||
Text="{Loc 'ui-link-discord-account-copy'}" HorizontalExpand="True" />
|
||||
<Button Name="LinkButton" Access="Public" StyleClasses="OpenLeft"
|
||||
Text="{Loc 'ui-link-discord-account-open-channel'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</linkAccount:LinkAccountWindow>
|
||||
@@ -0,0 +1,15 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._DV.AccountLinking;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LinkAccountWindow : DefaultWindow
|
||||
{
|
||||
public LinkAccountWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Reflection;
|
||||
using Content.DiscordBot.Modules;
|
||||
using Content.Server.Database;
|
||||
using Discord;
|
||||
using Discord.Commands;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public sealed class CommandHandler(DiscordSocketClient client, InteractionService interaction, ServerDbContext db, Config config)
|
||||
{
|
||||
private ImmutableDictionary<ulong, PatronTier>? _patronTiers;
|
||||
private ImmutableArray<PatronTier> _tierPriority;
|
||||
private Task? _refreshPatronsTask;
|
||||
|
||||
public int Running = 1;
|
||||
|
||||
public async Task InstallCommandsAsync()
|
||||
{
|
||||
var patronTiers = await db.PatronTiers.ToListAsync();
|
||||
_tierPriority = [..patronTiers.OrderBy(t => t.Priority)];
|
||||
_patronTiers = patronTiers.ToImmutableDictionary(t => t.DiscordRole, t => t);
|
||||
|
||||
client.Ready += ReadyAsync;
|
||||
client.SlashCommandExecuted += HandleCommandAsync;
|
||||
client.ButtonExecuted += HandleButtonAsync;
|
||||
client.ModalSubmitted += HandleModalAsync;
|
||||
client.GuildMemberUpdated += HandleGuildMemberUpdated;
|
||||
|
||||
// If you get a log that says '[ERROR]' here, you probably don't have the corrent intents
|
||||
await client.LoginAsync(TokenType.Bot, config.Token);
|
||||
await client.StartAsync();
|
||||
|
||||
interaction.AddModalInfo<LinkAccountModal>();
|
||||
|
||||
_refreshPatronsTask = Task.Run(async () => await RefreshPatrons());
|
||||
}
|
||||
|
||||
private async Task ReadyAsync()
|
||||
{
|
||||
await interaction.AddModulesAsync(Assembly.GetEntryAssembly(), null);
|
||||
await interaction.RegisterCommandsToGuildAsync(config.Guild);
|
||||
}
|
||||
|
||||
private async Task HandleGuildMemberUpdated(Cacheable<SocketGuildUser, ulong> old, SocketGuildUser user)
|
||||
{
|
||||
if (_patronTiers == null)
|
||||
return;
|
||||
|
||||
var rolesChanged = !old.HasValue || old.Value.Roles.Count != user.Roles.Count || !old.Value.Roles.SequenceEqual(user.Roles);
|
||||
if (!rolesChanged)
|
||||
return;
|
||||
|
||||
var wasPatron = old.HasValue || old.Value.Roles.Any(r => _patronTiers.ContainsKey(r.Id));
|
||||
var isPatron = user.Roles.Any(r => _patronTiers.ContainsKey(r.Id));
|
||||
if (wasPatron && !isPatron)
|
||||
{
|
||||
var linked = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.ThenInclude(p => p!.Tier)
|
||||
.Where(l => l.Player.Patron != null)
|
||||
.FirstOrDefaultAsync(p => p.DiscordId == user.Id);
|
||||
|
||||
if (linked?.Player.Patron is { } patron)
|
||||
{
|
||||
db.Patrons.Remove(patron);
|
||||
await db.SaveChangesAsync();
|
||||
await Logger.Info($"Removed patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {patron.Tier.Name}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPatron)
|
||||
{
|
||||
foreach (var tier in _tierPriority)
|
||||
{
|
||||
if (user.Roles.Any(r => r.Id == tier.DiscordRole))
|
||||
{
|
||||
var linked = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.FirstOrDefaultAsync(p => p.DiscordId == user.Id);
|
||||
|
||||
if (linked?.Player is not { } player)
|
||||
return;
|
||||
|
||||
player.Patron ??= db.Patrons.Add(new Patron { PlayerId = player.UserId }).Entity;
|
||||
player.Patron.TierId = tier.Id;
|
||||
await db.SaveChangesAsync();
|
||||
await Logger.Info($"Updated patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {tier.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCommandAsync(SocketSlashCommand message)
|
||||
{
|
||||
|
||||
if (message.GuildId != config.Guild)
|
||||
return;
|
||||
|
||||
// Create a WebSocket-based command context based on the message
|
||||
var context = new SocketInteractionContext(client, message);
|
||||
|
||||
// Execute the command with the command context we just
|
||||
// created, along with the service provider for precondition checks.
|
||||
await interaction.ExecuteCommandAsync(context, null);
|
||||
}
|
||||
|
||||
private async Task HandleButtonAsync(SocketMessageComponent component)
|
||||
{
|
||||
switch (component.Data.CustomId)
|
||||
{
|
||||
case "link-ss14-account":
|
||||
await component.RespondWithModalAsync<LinkAccountModal>("link-ss14-account");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleModalAsync(SocketModal modal)
|
||||
{
|
||||
switch (modal.Data.CustomId)
|
||||
{
|
||||
case "link-ss14-account":
|
||||
if (modal.GuildId is not { } guildId)
|
||||
break;
|
||||
|
||||
var codeStr = modal.Data.Components.First(c => c.CustomId == "account_code").Value.Trim();
|
||||
if (string.IsNullOrWhiteSpace(codeStr))
|
||||
break;
|
||||
|
||||
await modal.DeferAsync(true);
|
||||
if (!Guid.TryParse(codeStr, out var code))
|
||||
{
|
||||
await modal.FollowupAsync($"{codeStr} isn't a valid code! Get one in-game from the lobby at the top left of the screen.", ephemeral: true);
|
||||
}
|
||||
|
||||
var author = modal.User;
|
||||
var authorId = author.Id;
|
||||
var discord = await db.DiscordAccounts
|
||||
.Include(d => d.LinkedAccount)
|
||||
.ThenInclude(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.FirstOrDefaultAsync(a => a.Id == authorId);
|
||||
var codes = await db.DiscordLinkingCodes
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(player => player.Patron)
|
||||
.FirstOrDefaultAsync(p => p.Code == code);
|
||||
|
||||
if (codes == null)
|
||||
{
|
||||
await modal.FollowupAsync($"No player found with code {codeStr}, join the game server and get another code before trying again, or ask for help in another channel.", ephemeral: true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (codes.CreationTime < DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)))
|
||||
{
|
||||
await modal.FollowupAsync($"Code {codeStr} were generated too long ago, join the game server and get another code before trying again.", ephemeral: true);
|
||||
}
|
||||
|
||||
if (discord?.LinkedAccount is { } linked)
|
||||
{
|
||||
if (linked.Player.Patron is { } patron)
|
||||
db.Patrons.Remove(patron);
|
||||
|
||||
linked.Player.Patron = null;
|
||||
db.DiscordLinkedAccounts.Remove(linked);
|
||||
}
|
||||
|
||||
discord ??= db.DiscordAccounts.Add(new DiscordAccount { Id = authorId }).Entity;
|
||||
discord.LinkedAccount = db.DiscordLinkedAccounts.Add(new DiscordLinkedAccount { Discord = discord }).Entity;
|
||||
discord.LinkedAccount.Player = codes.Player;
|
||||
|
||||
var roles = client.GetGuild(guildId).GetUser(authorId).Roles.Select(r => r.Id).ToArray();
|
||||
var tiers = await db.PatronTiers
|
||||
.Where(t => roles.Contains(t.DiscordRole))
|
||||
.ToListAsync();
|
||||
if (tiers.Count == 0)
|
||||
{
|
||||
discord.LinkedAccount.Player.Patron = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
tiers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
var tier = tiers[0];
|
||||
discord.LinkedAccount.Player.Patron = db.Patrons.Add(new Patron { Tier = tier }).Entity;
|
||||
discord.LinkedAccount.Player.Patron.Tier = tier;
|
||||
}
|
||||
|
||||
db.DiscordLinkedAccountLogs.Add(new DiscordLinkedAccountLogs
|
||||
{
|
||||
Discord = discord,
|
||||
Player = discord.LinkedAccount.Player,
|
||||
});
|
||||
|
||||
db.ChangeTracker.DetectChanges();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var msg = $"Linked SS14 account with name {codes.Player.LastSeenUserName}";
|
||||
if (codes.Player.Patron != null)
|
||||
msg += $" and tier {codes.Player.Patron.Tier.Name}";
|
||||
|
||||
await modal.FollowupAsync(msg, ephemeral: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshPatrons()
|
||||
{
|
||||
while (Interlocked.CompareExchange(ref Running, 1, 1) == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var patrons = await db.DiscordLinkedAccounts
|
||||
.Include(l => l.Player)
|
||||
.ThenInclude(p => p.Patron)
|
||||
.ThenInclude(p => p!.Tier)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var linked in patrons)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await client.Rest.GetGuildUserAsync(config.Guild, linked.DiscordId);
|
||||
if (user == null)
|
||||
{
|
||||
if (linked.Player.Patron != null)
|
||||
{
|
||||
linked.Player.Patron = null;
|
||||
await Logger.Info($"Removed patron {linked.DiscordId}:{linked.Player.LastSeenUserName}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var isPatron = false;
|
||||
foreach (var tier in _tierPriority)
|
||||
{
|
||||
if (user.RoleIds.Contains(tier.DiscordRole))
|
||||
{
|
||||
isPatron = true;
|
||||
if (linked.Player.Patron?.Tier.DiscordRole == tier.DiscordRole)
|
||||
break;
|
||||
|
||||
linked.Player.Patron ??= db.Patrons.Add(new Patron { PlayerId = linked.PlayerId })
|
||||
.Entity;
|
||||
linked.Player.Patron.TierId = tier.Id;
|
||||
await Logger.Info($"Updated patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName} with tier {tier.Name}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPatron && linked.Player.Patron != null)
|
||||
{
|
||||
linked.Player.Patron = null;
|
||||
await Logger.Info($"Removed patron {user.Username}:{linked.DiscordId}:{linked.Player.LastSeenUserName}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await Logger.Error($"Error updating patron with discord id {linked.DiscordId} and player id {linked.PlayerId}", e);
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
await Task.Delay(60000); // 60 seconds
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await Logger.Error("Error refreshing patrons", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public sealed class Config
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
public string DatabaseString { get; set; } = string.Empty;
|
||||
|
||||
public string DatabaseContext { get; set; } = "postgres";
|
||||
|
||||
/// <summary>
|
||||
/// Discord "guild" ID (which is just the server ID). Default is DeltaV's server ID.
|
||||
/// </summary>
|
||||
public ulong Guild { get; set; } = 968983104247185448UL;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<OutputPath>..\bin\Content.DiscordBot\</OutputPath>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
<RootNamespace>Content.DiscordBot</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.15.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Content.Server.Database\Content.Server.Database.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,21 @@
|
||||
using Discord;
|
||||
|
||||
namespace Content.DiscordBot;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public static Task Log(LogMessage msg)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[{msg.Severity.ToString().ToUpper()}] {msg.Message}");
|
||||
}
|
||||
|
||||
public static Task Info(string msg)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[INFO] {msg}");
|
||||
}
|
||||
|
||||
public static Task Error(string msg, Exception e)
|
||||
{
|
||||
return Console.Out.WriteLineAsync($"[ERROR] {msg}\n{e}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
|
||||
namespace Content.DiscordBot.Modules;
|
||||
|
||||
public sealed class AccountLinkingModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
[SlashCommand("discordlink", "Creates the message with the linking button.")]
|
||||
[RequireTeam]
|
||||
public Task CreateAsync()
|
||||
{
|
||||
var component = new ComponentBuilder()
|
||||
.WithButton("Link your SS14 account here!", "link-ss14-account")
|
||||
.Build();
|
||||
|
||||
return RespondAsync(String.Empty, components: component);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Discord.Interactions;
|
||||
|
||||
namespace Content.DiscordBot.Modules;
|
||||
|
||||
public class LinkAccountModal : IModal
|
||||
{
|
||||
public string Title => "Link SS14 Account";
|
||||
|
||||
[InputLabel("SS14 Linking Code (Top Left in the Lobby)")]
|
||||
[RequiredInput]
|
||||
[ModalTextInput("account_code")]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Text.Json;
|
||||
using Content.DiscordBot;
|
||||
using Content.Server.Database;
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var client = new DiscordSocketClient(new DiscordSocketConfig { GatewayIntents = GatewayIntents.GuildMembers });
|
||||
client.Log += Logger.Log;
|
||||
|
||||
Console.Out.WriteLine($"[INIT] Working Directory: {Directory.GetCurrentDirectory()}");
|
||||
|
||||
var config = new Config();
|
||||
if (File.Exists("link-config.json"))
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] link-config.json found. Deserializing.");
|
||||
config = await JsonSerializer.DeserializeAsync<Config>(File.OpenRead("link-config.json")) ?? new Config();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
if (Environment.GetEnvironmentVariable("DISCORD_TOKEN") is { } envToken)
|
||||
config.Token = envToken;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("DATABASE_STRING") is { } dbString)
|
||||
config.DatabaseString = dbString;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("DATABASE_CONTEXT") is { } context)
|
||||
config.DatabaseContext = context;
|
||||
|
||||
if (Environment.GetEnvironmentVariable("GUILD") is { } guildId)
|
||||
{
|
||||
try
|
||||
{
|
||||
config.Guild = ulong.Parse("guildId");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Out.WriteLine($"[INIT] Unable to parse guild ID. Defaulting to {config.Guild}. Exception: {ex.GetType()} {ex.Message}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.Token))
|
||||
throw new ArgumentException("[INIT] No Discord Bot token found.");
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(config.DatabaseString))
|
||||
throw new ArgumentException("[INIT] No database connection string found.");
|
||||
|
||||
// Override for local sqlite development
|
||||
var usePostgres = true;
|
||||
if (!string.IsNullOrWhiteSpace(config.DatabaseContext) && config.DatabaseContext.Equals("sqlite"))
|
||||
usePostgres = false;
|
||||
|
||||
Console.Out.WriteLine($"[INIT] Guild ID: {config.Guild}");
|
||||
|
||||
ServerDbContext db;
|
||||
if (usePostgres)
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] Connecting to postgres...");
|
||||
var builder = new DbContextOptionsBuilder<PostgresServerDbContext>();
|
||||
builder.UseNpgsql(config.DatabaseString);
|
||||
db = new PostgresServerDbContext(builder.Options);
|
||||
// await db.Database.MigrateAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Out.WriteLine("[INIT] Connecting to sqlite...");
|
||||
var builder = new DbContextOptionsBuilder<SqliteServerDbContext>();
|
||||
builder.UseSqlite(new SqliteConnection(config.DatabaseString));
|
||||
db = new SqliteServerDbContext(builder.Options);
|
||||
}
|
||||
|
||||
Console.Out.WriteLine("[INIT] Database connection successful.");
|
||||
|
||||
var interaction = new InteractionService(client);
|
||||
var handler = new CommandHandler(client, interaction, db, config);
|
||||
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => Interlocked.Decrement(ref handler.Running);
|
||||
|
||||
Console.Out.WriteLine("[INIT] Starting Discord Bot.");
|
||||
await handler.InstallCommandsAsync();
|
||||
|
||||
// Block this task until the program is closed.
|
||||
await Task.Delay(-1);
|
||||
@@ -0,0 +1,32 @@
|
||||
# Content.DiscordBot
|
||||
|
||||
|
||||
### Configuration (Debug)
|
||||
* `DISCORD_TOKEN`: Discord application token provided @ `https://discord.com/developers/applications/<APP_ID>/bot`
|
||||
* `DATABASE_STRING`: Connection string to the database
|
||||
* For local development, you'll want it to be `Data Source=./bin/Content.Server/data/preferences.db` if you're using sqlite.
|
||||
* `DATABASE_CONTEXT`: Default is `postgres`. If testing locally with sqlite, use `sqlite`.
|
||||
* `GUILD`: The Discord Server ID. DeltaV's discord is `968983104247185448`.
|
||||
* You can find this by turning Developer mode on in Discord, right-clicking on a server, and copying the server ID.
|
||||
|
||||
### Configuration (Production)
|
||||
Requires a `link-config.json` file in the working directory.
|
||||
```json
|
||||
{
|
||||
"Token": "DISCORD_BOT_TOKEN",
|
||||
"DatabaseString": "DB_CONNECTION_STRING"
|
||||
}
|
||||
```
|
||||
|
||||
Here's a quick bash command to create it in your current working directory. Windows users will have to fend for themselves.
|
||||
```bash
|
||||
cat << EOF > link-config.json
|
||||
{
|
||||
"Token": "DISCORD_BOT_TOKEN",
|
||||
"DatabaseString": "DB_CONNECTION_STRING"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
You can also override `Guild` and `DatabaseContext` if you wanted to by including those JSON properties.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Content.Server.Database;
|
||||
|
||||
[Table("discord_accounts")]
|
||||
public sealed class DiscordAccount
|
||||
{
|
||||
[Key]
|
||||
public ulong Id { get; set; }
|
||||
|
||||
public DiscordLinkedAccount LinkedAccount { get; set; } = default!;
|
||||
public List<DiscordLinkedAccountLogs> LinkedAccountLogs { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("discord_linked_accounts")]
|
||||
public sealed class DiscordLinkedAccount
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public ulong DiscordId { get; set; }
|
||||
|
||||
public DiscordAccount Discord { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("patreon_tiers")]
|
||||
public sealed class PatronTier
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool ShowOnCredits { get; set; }
|
||||
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
public ulong DiscordRole { get; set; }
|
||||
|
||||
public int Priority { get; set; }
|
||||
|
||||
public List<Patron> Patrons { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("patreon_patrons")]
|
||||
[Index(nameof(TierId))]
|
||||
public sealed class Patron
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public int TierId { get; set; }
|
||||
|
||||
public PatronTier Tier { get; set; } = default!;
|
||||
}
|
||||
|
||||
[Table("discord_linking_codes")]
|
||||
[Index(nameof(Code))]
|
||||
public sealed class DiscordLinkingCodes
|
||||
{
|
||||
[Key]
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public Guid Code { get; set; }
|
||||
|
||||
public DateTime CreationTime { get; set; }
|
||||
}
|
||||
|
||||
[Table("discord_linked_accounts_logs")]
|
||||
[Index(nameof(PlayerId))]
|
||||
[Index(nameof(DiscordId))]
|
||||
[Index(nameof(At))]
|
||||
public sealed class DiscordLinkedAccountLogs
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
public Guid PlayerId { get; set; }
|
||||
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
public ulong DiscordId { get; set; }
|
||||
|
||||
public DiscordAccount Discord { get; set; } = default!;
|
||||
|
||||
public DateTime At { get; set; }
|
||||
}
|
||||
+2433
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DiscordLinking : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "cur_time",
|
||||
table: "admin_log");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
discord_accounts_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_accounts", x => x.discord_accounts_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linking_codes",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
code = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
creation_time = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linking_codes", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linking_codes_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_tiers",
|
||||
columns: table => new
|
||||
{
|
||||
patreon_tiers_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
show_on_credits = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
discord_role = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
priority = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_tiers", x => x.patreon_tiers_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts_logs",
|
||||
columns: table => new
|
||||
{
|
||||
discord_linked_accounts_logs_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
at = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts_logs", x => x.discord_linked_accounts_logs_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_patrons",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
tier_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_patrons", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_patreon_tiers_tier_id",
|
||||
column: x => x.tier_id,
|
||||
principalTable: "patreon_tiers",
|
||||
principalColumn: "patreon_tiers_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_discord_id",
|
||||
table: "discord_linked_accounts",
|
||||
column: "discord_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_at",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_discord_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "discord_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_player_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "player_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linking_codes_code",
|
||||
table: "discord_linking_codes",
|
||||
column: "code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_patrons_tier_id",
|
||||
table: "patreon_patrons",
|
||||
column: "tier_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_tiers_discord_role",
|
||||
table: "patreon_tiers",
|
||||
column: "discord_role",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linking_codes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_patrons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_tiers");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "cur_time",
|
||||
table: "admin_log",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
-2
@@ -1,5 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Content.Server.Database;
|
||||
@@ -20,7 +21,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.1")
|
||||
.HasAnnotation("ProductVersion", "10.0.6")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -739,6 +740,151 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.ToTable("dv_seen_tips", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<int>("TierId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("tier_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_patreon_patrons");
|
||||
|
||||
b.HasIndex("TierId")
|
||||
.HasDatabaseName("IX_patreon_patrons_tier_id");
|
||||
|
||||
b.ToTable("patreon_patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("patreon_tiers_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<decimal>("DiscordRole")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_role");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<bool>("ShowOnCredits")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("show_on_credits");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_patreon_tiers");
|
||||
|
||||
b.HasIndex("DiscordRole")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("patreon_tiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Property<decimal>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_accounts_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_accounts");
|
||||
|
||||
b.ToTable("discord_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<decimal>("DiscordId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linked_accounts");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("discord_linked_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("discord_linked_accounts_logs_id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("At")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("at");
|
||||
|
||||
b.Property<decimal>("DiscordId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_linked_accounts_logs");
|
||||
|
||||
b.HasIndex("At")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_at");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_discord_id");
|
||||
|
||||
b.HasIndex("PlayerId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_player_id");
|
||||
|
||||
b.ToTable("discord_linked_accounts_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<Guid>("Code")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linking_codes");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.HasDatabaseName("IX_discord_linking_codes_code");
|
||||
|
||||
b.ToTable("discord_linking_codes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -901,7 +1047,7 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("admin_ooc_color");
|
||||
|
||||
b.PrimitiveCollection<string[]>("ConstructionFavorites")
|
||||
b.PrimitiveCollection<List<string>>("ConstructionFavorites")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]")
|
||||
.HasColumnName("construction_favorites");
|
||||
@@ -1840,6 +1986,85 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasConstraintName("FK_dv_seen_tips_player_player_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("Patron")
|
||||
.HasForeignKey("Content.Server.Database.Patron", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_player_player_id1");
|
||||
|
||||
b.HasOne("Content.Server.Database.PatronTier", "Tier")
|
||||
.WithMany("Patrons")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_patreon_tiers_tier_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkingCodes")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkingCodes", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linking_codes_player_player_id1");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
@@ -2169,6 +2394,19 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("BanHits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Navigation("Patrons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Navigation("LinkedAccount")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
@@ -2206,6 +2444,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
|
||||
b.Navigation("LinkedAccount");
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
|
||||
b.Navigation("LinkingCodes");
|
||||
|
||||
b.Navigation("Patron");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
+2428
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class DiscordLinking : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "cur_time",
|
||||
table: "admin_log");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
discord_accounts_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_accounts", x => x.discord_accounts_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linking_codes",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
code = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
creation_time = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linking_codes", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linking_codes_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_tiers",
|
||||
columns: table => new
|
||||
{
|
||||
patreon_tiers_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
show_on_credits = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
discord_role = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
priority = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_tiers", x => x.patreon_tiers_id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "discord_linked_accounts_logs",
|
||||
columns: table => new
|
||||
{
|
||||
discord_linked_accounts_logs_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
discord_id = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
at = table.Column<DateTime>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_discord_linked_accounts_logs", x => x.discord_linked_accounts_logs_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_discord_accounts_discord_id",
|
||||
column: x => x.discord_id,
|
||||
principalTable: "discord_accounts",
|
||||
principalColumn: "discord_accounts_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_discord_linked_accounts_logs_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "patreon_patrons",
|
||||
columns: table => new
|
||||
{
|
||||
player_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
tier_id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_patreon_patrons", x => x.player_id);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_patreon_tiers_tier_id",
|
||||
column: x => x.tier_id,
|
||||
principalTable: "patreon_tiers",
|
||||
principalColumn: "patreon_tiers_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_patreon_patrons_player_player_id1",
|
||||
column: x => x.player_id,
|
||||
principalTable: "player",
|
||||
principalColumn: "user_id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_discord_id",
|
||||
table: "discord_linked_accounts",
|
||||
column: "discord_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_at",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_discord_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "discord_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linked_accounts_logs_player_id",
|
||||
table: "discord_linked_accounts_logs",
|
||||
column: "player_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_discord_linking_codes_code",
|
||||
table: "discord_linking_codes",
|
||||
column: "code");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_patrons_tier_id",
|
||||
table: "patreon_patrons",
|
||||
column: "tier_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_patreon_tiers_discord_role",
|
||||
table: "patreon_tiers",
|
||||
column: "discord_role",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linked_accounts_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_linking_codes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_patrons");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "discord_accounts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "patreon_tiers");
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "cur_time",
|
||||
table: "admin_log",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.6");
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Admin", b =>
|
||||
{
|
||||
@@ -698,6 +698,147 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.ToTable("dv_seen_tips", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<int>("TierId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("tier_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_patreon_patrons");
|
||||
|
||||
b.HasIndex("TierId")
|
||||
.HasDatabaseName("IX_patreon_patrons_tier_id");
|
||||
|
||||
b.ToTable("patreon_patrons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("patreon_tiers_id");
|
||||
|
||||
b.Property<ulong>("DiscordRole")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_role");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<bool>("ShowOnCredits")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("show_on_credits");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_patreon_tiers");
|
||||
|
||||
b.HasIndex("DiscordRole")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("patreon_tiers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Property<ulong>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_accounts_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_accounts");
|
||||
|
||||
b.ToTable("discord_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linked_accounts");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("discord_linked_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_linked_accounts_logs_id");
|
||||
|
||||
b.Property<DateTime>("At")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("at");
|
||||
|
||||
b.Property<ulong>("DiscordId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("discord_id");
|
||||
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("PK_discord_linked_accounts_logs");
|
||||
|
||||
b.HasIndex("At")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_at");
|
||||
|
||||
b.HasIndex("DiscordId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_discord_id");
|
||||
|
||||
b.HasIndex("PlayerId")
|
||||
.HasDatabaseName("IX_discord_linked_accounts_logs_player_id");
|
||||
|
||||
b.ToTable("discord_linked_accounts_logs", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.Property<Guid>("PlayerId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("player_id");
|
||||
|
||||
b.Property<Guid>("Code")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("code");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("creation_time");
|
||||
|
||||
b.HasKey("PlayerId")
|
||||
.HasName("PK_discord_linking_codes");
|
||||
|
||||
b.HasIndex("Code")
|
||||
.HasDatabaseName("IX_discord_linking_codes_code");
|
||||
|
||||
b.ToTable("discord_linking_codes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.IPIntelCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -1759,6 +1900,85 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasConstraintName("FK_dv_seen_tips_player_player_id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Patron", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("Patron")
|
||||
.HasForeignKey("Content.Server.Database.Patron", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_player_player_id1");
|
||||
|
||||
b.HasOne("Content.Server.Database.PatronTier", "Tier")
|
||||
.WithMany("Patrons")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_patreon_patrons_patreon_tiers_tier_id");
|
||||
|
||||
b.Navigation("Player");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccount", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkedAccount")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkedAccount", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkedAccountLogs", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.DiscordAccount", "Discord")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("DiscordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_discord_accounts_discord_id");
|
||||
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithMany("LinkedAccountLogs")
|
||||
.HasForeignKey("PlayerId")
|
||||
.HasPrincipalKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linked_accounts_logs_player_player_id1");
|
||||
|
||||
b.Navigation("Discord");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordLinkingCodes", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Player", "Player")
|
||||
.WithOne("LinkingCodes")
|
||||
.HasForeignKey("Content.Server.Database.DiscordLinkingCodes", "PlayerId")
|
||||
.HasPrincipalKey("Content.Server.Database.Player", "UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("FK_discord_linking_codes_player_player_id1");
|
||||
|
||||
b.Navigation("Player");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Job", b =>
|
||||
{
|
||||
b.HasOne("Content.Server.Database.Profile", "Profile")
|
||||
@@ -2088,6 +2308,19 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("BanHits");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.PatronTier", b =>
|
||||
{
|
||||
b.Navigation("Patrons");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.DiscordAccount", b =>
|
||||
{
|
||||
b.Navigation("LinkedAccount")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||
{
|
||||
b.Navigation("AdminLogs");
|
||||
@@ -2125,6 +2358,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.Navigation("AdminWatchlistsReceived");
|
||||
|
||||
b.Navigation("JobWhitelists");
|
||||
|
||||
b.Navigation("LinkedAccount");
|
||||
|
||||
b.Navigation("LinkedAccountLogs");
|
||||
|
||||
b.Navigation("LinkingCodes");
|
||||
|
||||
b.Navigation("Patron");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||
|
||||
@@ -48,6 +48,15 @@ namespace Content.Server.Database
|
||||
public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
|
||||
public DbSet<DVModel.SeenTip> DVSeenTips { get; set; } = null!; // DeltaV - Tips
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
public DbSet<DiscordAccount> DiscordAccounts { get; set; } = default!;
|
||||
public DbSet<DiscordLinkedAccount> DiscordLinkedAccounts { get; set; } = default!;
|
||||
public DbSet<PatronTier> PatronTiers { get; set; } = default!;
|
||||
public DbSet<Patron> Patrons { get; set; } = default!;
|
||||
public DbSet<DiscordLinkingCodes> DiscordLinkingCodes { get; set; } = default!;
|
||||
public DbSet<DiscordLinkedAccountLogs> DiscordLinkedAccountLogs { get; set; } = default!;
|
||||
// END DeltaV
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Preference>()
|
||||
@@ -395,6 +404,61 @@ namespace Content.Server.Database
|
||||
.OwnsOne(p => p.HWId)
|
||||
.Property(p => p.Type)
|
||||
.HasDefaultValue(HwidType.Legacy);
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
modelBuilder.Entity<DiscordLinkedAccount>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithOne(p => p.LinkedAccount)
|
||||
.HasForeignKey<DiscordLinkedAccount>(l => l.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccount>()
|
||||
.HasOne(l => l.Discord)
|
||||
.WithOne(d => d.LinkedAccount)
|
||||
.HasForeignKey<DiscordLinkedAccount>(l => l.DiscordId)
|
||||
.HasPrincipalKey<DiscordAccount>(d => d.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Patron>()
|
||||
.HasOne(p => p.Player)
|
||||
.WithOne(p => p.Patron)
|
||||
.HasForeignKey<Patron>(p => p.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Patron>()
|
||||
.HasOne(p => p.Tier)
|
||||
.WithMany(t => t.Patrons)
|
||||
.HasForeignKey(p => p.TierId)
|
||||
.HasPrincipalKey(p => p.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<PatronTier>()
|
||||
.HasIndex(t => t.DiscordRole)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder.Entity<DiscordLinkingCodes>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithOne(p => p.LinkingCodes)
|
||||
.HasForeignKey<DiscordLinkingCodes>(l => l.PlayerId)
|
||||
.HasPrincipalKey<Player>(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccountLogs>()
|
||||
.HasOne(l => l.Player)
|
||||
.WithMany(p => p.LinkedAccountLogs)
|
||||
.HasForeignKey(l => l.PlayerId)
|
||||
.HasPrincipalKey(p => p.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<DiscordLinkedAccountLogs>()
|
||||
.HasOne(l => l.Discord)
|
||||
.WithMany(p => p.LinkedAccountLogs)
|
||||
.HasForeignKey(l => l.DiscordId)
|
||||
.HasPrincipalKey(p => p.Id)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
// END DeltaV
|
||||
}
|
||||
|
||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||
@@ -621,6 +685,13 @@ namespace Content.Server.Database
|
||||
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
|
||||
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
|
||||
public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
public DiscordLinkedAccount? LinkedAccount { get; set; }
|
||||
public Patron? Patron { get; set; }
|
||||
public DiscordLinkingCodes? LinkingCodes { get; set; }
|
||||
public List<DiscordLinkedAccountLogs> LinkedAccountLogs { get; set; } = default!;
|
||||
// END DeltaV
|
||||
}
|
||||
|
||||
[Table("whitelist")]
|
||||
|
||||
@@ -89,10 +89,10 @@ internal sealed partial class ChatManager : IChatManager
|
||||
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message"));
|
||||
}
|
||||
|
||||
public void DeleteMessagesBy(NetUserId uid)
|
||||
{
|
||||
if (!_players.TryGetValue(uid, out var user))
|
||||
return;
|
||||
public void DeleteMessagesBy(NetUserId uid)
|
||||
{
|
||||
if (!_players.TryGetValue(uid, out var user))
|
||||
return;
|
||||
|
||||
var msg = new MsgDeleteChatMessagesBy { Key = user.Key, Entities = user.Entities };
|
||||
_netManager.ServerSendToAll(msg);
|
||||
|
||||
@@ -16,7 +16,7 @@ using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
//using Content.Shared.Traits; // DV - Traits
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -1924,8 +1924,60 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
.Where(s => s.PlayerUserId == player)
|
||||
.ExecuteDeleteAsync();
|
||||
}
|
||||
#endregion
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
#region DeltaV - Account Linking
|
||||
|
||||
public async Task<Guid?> GetLinkingCode(Guid player)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var linking = await db.DbContext.DiscordLinkingCodes.FirstOrDefaultAsync(l => l.PlayerId == player);
|
||||
return linking?.Code;
|
||||
}
|
||||
|
||||
public async Task SetLinkingCode(Guid player, Guid code)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var linking = await db.DbContext.DiscordLinkingCodes.FirstOrDefaultAsync(l => l.PlayerId == player);
|
||||
if (linking == null)
|
||||
{
|
||||
linking = new DiscordLinkingCodes { PlayerId = player };
|
||||
db.DbContext.DiscordLinkingCodes.Add(linking);
|
||||
}
|
||||
|
||||
linking.Code = code;
|
||||
linking.CreationTime = DateTime.UtcNow;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
return await db.DbContext.DiscordLinkedAccounts.AnyAsync(l => l.PlayerId == player, cancel);
|
||||
|
||||
}
|
||||
|
||||
public async Task<Patron?> GetPatron(Guid player, CancellationToken cancel)
|
||||
{
|
||||
await using var db = await GetDb(cancel);
|
||||
var patron = await db.DbContext.Patrons
|
||||
.Include(p => p.Tier)
|
||||
.FirstOrDefaultAsync(p => p.PlayerId == player, cancellationToken: cancel);
|
||||
return patron;
|
||||
}
|
||||
|
||||
public async Task<List<Patron>> GetAllPatrons()
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
return await db.DbContext.Patrons
|
||||
.Include(p => p.Player)
|
||||
.Include(p => p.Tier)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
public abstract Task SendNotification(DatabaseNotification notification);
|
||||
|
||||
|
||||
@@ -354,6 +354,21 @@ namespace Content.Server.Database
|
||||
|
||||
#endregion
|
||||
|
||||
// BEGIN DeltaV - Discord / Patreon Account Linking
|
||||
#region DeltaV - Discord Linking
|
||||
|
||||
Task<Guid?> GetLinkingCode(Guid player);
|
||||
|
||||
Task SetLinkingCode(Guid player, Guid code);
|
||||
|
||||
Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel);
|
||||
|
||||
Task<Patron?> GetPatron(Guid player, CancellationToken cancel);
|
||||
|
||||
Task<List<Patron>> GetAllPatrons();
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
#region DB Notifications
|
||||
|
||||
void SubscribeToNotifications(Action<DatabaseNotification> handler);
|
||||
@@ -1127,6 +1142,41 @@ namespace Content.Server.Database
|
||||
}
|
||||
}
|
||||
|
||||
// BEGIN DeltaV - Discord Linking
|
||||
#region DeltaV - Discord Linking
|
||||
public Task<Guid?> GetLinkingCode(Guid player)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetLinkingCode(player));
|
||||
}
|
||||
|
||||
public Task SetLinkingCode(Guid player, Guid code)
|
||||
{
|
||||
DbWriteOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.SetLinkingCode(player, code));
|
||||
}
|
||||
|
||||
public Task<bool> HasLinkedAccount(Guid player, CancellationToken cancel)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.HasLinkedAccount(player, cancel));
|
||||
}
|
||||
|
||||
public Task<Patron?> GetPatron(Guid player, CancellationToken cancel)
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetPatron(player, cancel));
|
||||
}
|
||||
|
||||
public Task<List<Patron>> GetAllPatrons()
|
||||
{
|
||||
DbReadOpsMetric.Inc();
|
||||
return RunDbCommand(() => _db.GetAllPatrons());
|
||||
}
|
||||
#endregion
|
||||
// END DeltaV
|
||||
|
||||
|
||||
// Wrapper functions to run DB commands from the thread pool.
|
||||
// This will avoid SynchronizationContext capturing and avoid running CPU work on the main thread.
|
||||
// For SQLite, this will also enable read parallelization (within limits).
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server._DV.AccountLinking; // DeltaV
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Managers;
|
||||
@@ -81,5 +82,6 @@ internal static class ServerContentIoC
|
||||
deps.Register<CVarControlManager>();
|
||||
deps.Register<DiscordLink>();
|
||||
deps.Register<DiscordChatLink>();
|
||||
deps.Register<LinkAccountManager>(); // DeltaV - Discord Account Linking
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared._DV.AccountLinking;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountManager : IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _db = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly UserDbDataManager _userDb = default!;
|
||||
|
||||
private readonly Dictionary<NetUserId, TimeSpan> _lastRequest = new();
|
||||
private readonly TimeSpan _minimumWait = TimeSpan.FromSeconds(0.5);
|
||||
private readonly Dictionary<NetUserId, SharedPatronFull> _connected = new();
|
||||
private readonly List<SharedPatron> _allPatrons = [];
|
||||
|
||||
private async Task LoadData(ICommonSession player, CancellationToken cancel)
|
||||
{
|
||||
var patron = await _db.GetPatron(player.UserId, cancel);
|
||||
var linked = await _db.HasLinkedAccount(player.UserId, cancel);
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
|
||||
var tier = patron?.Tier;
|
||||
var sharedTier = tier == null
|
||||
? null
|
||||
: new SharedPatronTier(
|
||||
tier.ShowOnCredits,
|
||||
tier.Name
|
||||
);
|
||||
|
||||
_connected[player.UserId] = new SharedPatronFull(sharedTier, linked);
|
||||
}
|
||||
|
||||
private void FinishLoad(ICommonSession player)
|
||||
{
|
||||
SendPatronStatus(player);
|
||||
}
|
||||
|
||||
private void ClientDisconnected(ICommonSession player)
|
||||
{
|
||||
_connected.Remove(player.UserId);
|
||||
}
|
||||
|
||||
private void SendPatronStatus(ICommonSession player)
|
||||
{
|
||||
var connected = _connected.GetValueOrDefault(player.UserId);
|
||||
var msg = new LinkAccountStatusMsg { Patron = connected, };
|
||||
_net.ServerSendMessage(msg, player.Channel);
|
||||
SendPatrons(player);
|
||||
}
|
||||
|
||||
private void OnRequest(LinkAccountRequestMsg message)
|
||||
{
|
||||
var user = message.MsgChannel.UserId;
|
||||
var time = _timing.RealTime;
|
||||
if (_lastRequest.TryGetValue(user, out var last) &&
|
||||
last + _minimumWait > time)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastRequest[user] = time;
|
||||
|
||||
var code = Guid.NewGuid();
|
||||
_db.SetLinkingCode(user, code);
|
||||
|
||||
var response = new LinkAccountCodeMsg { Code = code };
|
||||
_net.ServerSendMessage(response, message.MsgChannel);
|
||||
}
|
||||
|
||||
public async Task RefreshAllPatrons()
|
||||
{
|
||||
var patrons = await _db.GetAllPatrons();
|
||||
|
||||
_allPatrons.Clear();
|
||||
foreach (var patron in patrons)
|
||||
{
|
||||
_allPatrons.Add(new SharedPatron(patron.Player.LastSeenUserName, patron.Tier.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public void SendPatronsToAll()
|
||||
{
|
||||
var msg = new PatronListMsg { Patrons = _allPatrons };
|
||||
_net.ServerSendToAll(msg);
|
||||
}
|
||||
|
||||
public void SendPatrons(ICommonSession player)
|
||||
{
|
||||
var msg = new PatronListMsg { Patrons = _allPatrons };
|
||||
_net.ServerSendMessage(msg, player.Channel);
|
||||
}
|
||||
|
||||
public SharedPatronFull? GetPatron(ICommonSession player)
|
||||
{
|
||||
return GetPatron(player.UserId);
|
||||
}
|
||||
|
||||
public SharedPatronFull? GetPatron(NetUserId userId)
|
||||
{
|
||||
return _connected.GetValueOrDefault(userId);
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
{
|
||||
_net.RegisterNetMessage<LinkAccountRequestMsg>(OnRequest);
|
||||
_net.RegisterNetMessage<LinkAccountCodeMsg>();
|
||||
_net.RegisterNetMessage<LinkAccountStatusMsg>();
|
||||
_net.RegisterNetMessage<PatronListMsg>();
|
||||
_userDb.AddOnLoadPlayer(LoadData);
|
||||
_userDb.AddOnFinishLoad(FinishLoad);
|
||||
_userDb.AddOnPlayerDisconnect(ClientDisconnected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.GameTicking;
|
||||
|
||||
namespace Content.Server._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountSystem : EntitySystem
|
||||
{
|
||||
|
||||
[Dependency] private readonly LinkAccountManager _linkAccount = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameRunLevelChanged);
|
||||
|
||||
ReloadPatrons();
|
||||
}
|
||||
|
||||
private void OnGameRunLevelChanged(GameRunLevelChangedEvent ev)
|
||||
{
|
||||
switch (ev.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
ReloadPatrons();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ReloadPatrons()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _linkAccount.RefreshAllPatrons();
|
||||
_linkAccount.SendPatronsToAll();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Error reloading Patrons list:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Traits;
|
||||
//using Content.Shared.Traits; // DeltaV - Traits Rework
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountCodeMsg : NetMessage
|
||||
{
|
||||
public Guid Code;
|
||||
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
Code = buffer.ReadGuid();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.Write(Code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountRequestMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.IO;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class LinkAccountStatusMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public SharedPatronFull? Patron;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var isPatron = buffer.ReadBoolean();
|
||||
if (!isPatron)
|
||||
return;
|
||||
|
||||
buffer.ReadPadBits();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
using var stream = new MemoryStream(length);
|
||||
buffer.ReadAlignedMemory(stream, length);
|
||||
Patron = serializer.Deserialize<SharedPatronFull>(stream);
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
if (Patron == null)
|
||||
{
|
||||
buffer.Write(false);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.Write(true);
|
||||
buffer.WritePadBits();
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
serializer.Serialize(stream, Patron);
|
||||
buffer.WriteVariableInt32((int) stream.Length);
|
||||
stream.TryGetBuffer(out var segment);
|
||||
buffer.Write(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
public sealed class PatronListMsg : NetMessage
|
||||
{
|
||||
public override MsgGroups MsgGroup => MsgGroups.Core;
|
||||
|
||||
public List<SharedPatron> Patrons = default!;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
var count = buffer.ReadVariableInt32();
|
||||
Patrons = new List<SharedPatron>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var name = buffer.ReadString();
|
||||
var tier = buffer.ReadString();
|
||||
Patrons.Add(new SharedPatron(name, tier));
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
{
|
||||
buffer.WriteVariableInt32(Patrons.Count);
|
||||
foreach (var patron in Patrons)
|
||||
{
|
||||
buffer.Write(patron.Name);
|
||||
buffer.Write(patron.Tier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class SharedPatron(string name, string tier)
|
||||
{
|
||||
public readonly string Name = name;
|
||||
public readonly string Tier = tier;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronFull(
|
||||
SharedPatronTier? Tier,
|
||||
bool Linked
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared._DV.AccountLinking;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed record SharedPatronTier(
|
||||
bool ShowOnCredits,
|
||||
string Tier
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Shared._DV.CCVars;
|
||||
|
||||
public sealed partial class DCCVars
|
||||
{
|
||||
/// <summary>
|
||||
/// Redirects you to the channel in discord to link your account.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DiscordAccountLinkingMessageLink =
|
||||
CVarDef.Create("discord.discord_account_linking_message_link", "", CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
}
|
||||
@@ -59,4 +59,7 @@ storage_limit = 3
|
||||
|
||||
[audio] # DeltaV
|
||||
attenuation = 4 # inversedistanceclamped 1 << 2, see audioparams.cs
|
||||
z_offset = -2
|
||||
z_offset = -2
|
||||
|
||||
[discord]
|
||||
discord_account_linking_message_link = "https://discord.com/channels/968983104247185448/1513638637018021978"
|
||||
@@ -112,3 +112,6 @@ max_announcement_length = 512
|
||||
[audio]
|
||||
attenuation = 4 # inversedistanceclamped 1 << 2, see audioparams.cs
|
||||
z_offset = -2
|
||||
|
||||
[discord]
|
||||
discord_account_linking_message_link = "https://discord.com/channels/968983104247185448/1513638637018021978"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ui-discord = Discord
|
||||
|
||||
ui-link-discord-account = Link Discord Account
|
||||
ui-link-discord-account-text = Click the button below to copy your code.
|
||||
Then go to the #account-linking channel to link your accounts.
|
||||
ui-link-discord-account-copy = Copy code
|
||||
ui-link-discord-account-copied = Copied!
|
||||
ui-link-discord-account-already-linked = [bold]Your account is already linked![/bold]
|
||||
ui-link-discord-account-open-channel = Open channel
|
||||
@@ -0,0 +1 @@
|
||||
ui-patreon = Patreon
|
||||
@@ -25,6 +25,7 @@
|
||||
</Folder>
|
||||
<Project Path="Content.Benchmarks/Content.Benchmarks.csproj" />
|
||||
<Project Path="Content.Client/Content.Client.csproj" />
|
||||
<Project Path="Content.DiscordBot/Content.DiscordBot.csproj" />
|
||||
<Project Path="Content.IntegrationTests/Content.IntegrationTests.csproj" />
|
||||
<Project Path="Content.MapRenderer/Content.MapRenderer.csproj" />
|
||||
<Project Path="Content.Packaging/Content.Packaging.csproj" />
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Sorry Windows devs, we're making a basic Debian package for the server, so if you can't run bash, you can't package this.
|
||||
# Also, TODO: Discord Linking
|
||||
Reference in New Issue
Block a user