adds ahelp relay (#5837)
This commit is contained in:
@@ -1,19 +1,15 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Content.Client.Administration.UI;
|
using Content.Client.Administration.UI;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Components;
|
|
||||||
using Robust.Shared.Network;
|
|
||||||
using Robust.Shared.Players;
|
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
namespace Content.Client.Administration
|
namespace Content.Client.Administration
|
||||||
{
|
{
|
||||||
@@ -43,25 +39,17 @@ namespace Content.Client.Administration
|
|||||||
|
|
||||||
public BwoinkWindow EnsureWindow(NetUserId channelId)
|
public BwoinkWindow EnsureWindow(NetUserId channelId)
|
||||||
{
|
{
|
||||||
if (_activeWindowMap.TryGetValue(channelId, out var existingWindow))
|
if (!_activeWindowMap.TryGetValue(channelId, out var existingWindow))
|
||||||
{
|
{
|
||||||
|
_activeWindowMap[channelId] = existingWindow = new BwoinkWindow(channelId,
|
||||||
|
_playerManager.SessionsDict.TryGetValue(channelId, out var otherSession)
|
||||||
|
? otherSession.Name
|
||||||
|
: channelId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
existingWindow.Open();
|
existingWindow.Open();
|
||||||
return existingWindow;
|
return existingWindow;
|
||||||
}
|
}
|
||||||
string title;
|
|
||||||
if (_playerManager.SessionsDict.TryGetValue(channelId, out var otherSession))
|
|
||||||
{
|
|
||||||
title = otherSession.Name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
title = channelId.ToString();
|
|
||||||
}
|
|
||||||
var window = new BwoinkWindow(channelId, title);
|
|
||||||
_activeWindowMap[channelId] = window;
|
|
||||||
window.Open();
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnsureWindowForLocalPlayer()
|
public void EnsureWindowForLocalPlayer()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Client.UserInterface;
|
|
||||||
using Content.Client.Administration;
|
|
||||||
using Content.Shared;
|
|
||||||
using Robust.Client.Credits;
|
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.Network;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using YamlDotNet.RepresentationModel;
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI
|
namespace Content.Client.Administration.UI
|
||||||
{
|
{
|
||||||
@@ -33,14 +22,13 @@ namespace Content.Client.Administration.UI
|
|||||||
|
|
||||||
private readonly NetUserId _channelId;
|
private readonly NetUserId _channelId;
|
||||||
|
|
||||||
public BwoinkWindow(NetUserId channelId, string title)
|
public BwoinkWindow(NetUserId userId, string channelName)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_channelId = userId;
|
||||||
_channelId = channelId;
|
Title = (_playerManager.LocalPlayer?.UserId == _channelId) ? "Admin Message" : channelName;
|
||||||
Title = (_playerManager.LocalPlayer?.UserId == _channelId) ? "Admin Message" : title;
|
|
||||||
|
|
||||||
SenderLineEdit.OnTextEntered += Input_OnTextEntered;
|
SenderLineEdit.OnTextEntered += Input_OnTextEntered;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Client.Administration;
|
using Content.Client.Administration;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Robust.Client.Console;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.Network;
|
|
||||||
using Robust.Shared.Containers;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
namespace Content.Client.Commands
|
namespace Content.Client.Commands
|
||||||
{
|
{
|
||||||
@@ -38,7 +34,6 @@ namespace Content.Client.Commands
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
shell.WriteLine("Bad GUID!");
|
shell.WriteLine("Bad GUID!");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.CCVar;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Server.Administration
|
namespace Content.Server.Administration
|
||||||
@@ -16,6 +25,37 @@ namespace Content.Server.Administration
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
|
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
|
||||||
|
|
||||||
|
private ISawmill? _sawmill;
|
||||||
|
private readonly HttpClient _httpClient = new();
|
||||||
|
private string _webhookUrl = string.Empty;
|
||||||
|
private string _serverName = string.Empty;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
_config.OnValueChanged(CCVars.DiscordAHelpWebhook, OnWebhookChanged, true);
|
||||||
|
_config.OnValueChanged(CVars.GameHostName, OnServerNameChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerNameChanged(string obj)
|
||||||
|
{
|
||||||
|
_serverName = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_config.UnsubValueChanged(CCVars.DiscordAHelpWebhook, OnWebhookChanged);
|
||||||
|
_config.UnsubValueChanged(CVars.GameHostName, OnServerNameChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWebhookChanged(string obj)
|
||||||
|
{
|
||||||
|
_webhookUrl = obj;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
|
protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySessionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
@@ -24,9 +64,9 @@ namespace Content.Server.Administration
|
|||||||
|
|
||||||
// TODO: Sanitize text?
|
// TODO: Sanitize text?
|
||||||
// Confirm that this person is actually allowed to send a message here.
|
// Confirm that this person is actually allowed to send a message here.
|
||||||
var senderPersonalChannel = senderSession.UserId == message.ChannelId;
|
var personalChannel = senderSession.UserId == message.ChannelId;
|
||||||
var senderAdmin = _adminManager.GetAdminData(senderSession);
|
var senderAdmin = _adminManager.GetAdminData(senderSession);
|
||||||
var authorized = senderPersonalChannel || senderAdmin != null;
|
var authorized = personalChannel || senderAdmin != null;
|
||||||
if (!authorized)
|
if (!authorized)
|
||||||
{
|
{
|
||||||
// Unauthorized bwoink (log?)
|
// Unauthorized bwoink (log?)
|
||||||
@@ -60,15 +100,65 @@ namespace Content.Server.Administration
|
|||||||
foreach (var channel in targets)
|
foreach (var channel in targets)
|
||||||
RaiseNetworkEvent(msg, channel);
|
RaiseNetworkEvent(msg, channel);
|
||||||
|
|
||||||
|
var sendsWebhook = _webhookUrl != string.Empty;
|
||||||
|
if (sendsWebhook)
|
||||||
|
{
|
||||||
|
async void SendWebhook()
|
||||||
|
{
|
||||||
|
_sawmill ??= IoCManager.Resolve<ILogManager>().GetSawmill("AHELP");
|
||||||
|
|
||||||
|
var lookup = await _playerLocator.LookupIdAsync(message.ChannelId);
|
||||||
|
|
||||||
|
if (lookup == null)
|
||||||
|
{
|
||||||
|
_sawmill.Log(LogLevel.Error, $"Unable to find player for netuserid {msg.ChannelId} when sending discord webhook.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = new WebhookPayload()
|
||||||
|
{
|
||||||
|
username = _serverName,
|
||||||
|
content = $"`[{lookup.Username}]` {senderSession.Name}: \"{message.Text}\""
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = await _httpClient.PostAsync(_webhookUrl,
|
||||||
|
new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json"));
|
||||||
|
|
||||||
|
if (!request.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var content = await request.Content.ReadAsStringAsync();
|
||||||
|
_sawmill.Log(LogLevel.Error, $"Discord returned bad status code: {request.StatusCode}\nResponse: {content}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendWebhook();
|
||||||
|
}
|
||||||
|
|
||||||
if (targets.Count == 1)
|
if (targets.Count == 1)
|
||||||
{
|
{
|
||||||
var systemText = senderPersonalChannel ?
|
var systemText = sendsWebhook ?
|
||||||
Loc.GetString("bwoink-system-starmute-message-no-other-users-primary") :
|
Loc.GetString("bwoink-system-starmute-message-no-other-users-webhook") :
|
||||||
Loc.GetString("bwoink-system-starmute-message-no-other-users-secondary");
|
Loc.GetString("bwoink-system-starmute-message-no-other-users");
|
||||||
var starMuteMsg = new BwoinkTextMessage(message.ChannelId, SystemUserId, systemText);
|
var starMuteMsg = new BwoinkTextMessage(message.ChannelId, SystemUserId, systemText);
|
||||||
RaiseNetworkEvent(starMuteMsg, senderSession.ConnectedClient);
|
RaiseNetworkEvent(starMuteMsg, senderSession.ConnectedClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct WebhookPayload
|
||||||
|
{
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public string username { get; set; } = "";
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public string content { get; set; } = "";
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public Dictionary<string, string[]> allowed_mentions { get; set; } =
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
{ "parse", Array.Empty<string>() }
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared;
|
using Robust.Shared;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
@@ -16,7 +17,7 @@ using Robust.Shared.Network;
|
|||||||
|
|
||||||
namespace Content.Server.Administration
|
namespace Content.Server.Administration
|
||||||
{
|
{
|
||||||
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId);
|
public sealed record LocatedPlayerData(NetUserId UserId, IPAddress? LastAddress, ImmutableArray<byte>? LastHWId, string Username);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Utilities for finding user IDs that extend to more than the server database.
|
/// Utilities for finding user IDs that extend to more than the server database.
|
||||||
@@ -38,6 +39,12 @@ namespace Content.Server.Administration
|
|||||||
/// If passed a player name, returns <see cref="LookupIdByNameAsync"/>.
|
/// If passed a player name, returns <see cref="LookupIdByNameAsync"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default);
|
Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Look up a user by <see cref="NetUserId"/> globally.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Null if the player does not exist.</returns>
|
||||||
|
Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PlayerLocator : IPlayerLocator
|
internal sealed class PlayerLocator : IPlayerLocator
|
||||||
@@ -54,13 +61,13 @@ namespace Content.Server.Administration
|
|||||||
var userId = session.UserId;
|
var userId = session.UserId;
|
||||||
var address = session.ConnectedClient.RemoteEndPoint.Address;
|
var address = session.ConnectedClient.RemoteEndPoint.Address;
|
||||||
var hwId = session.ConnectedClient.UserData.HWId;
|
var hwId = session.ConnectedClient.UserData.HWId;
|
||||||
return new LocatedPlayerData(userId, address, hwId);
|
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check database for past players.
|
// Check database for past players.
|
||||||
var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
|
var record = await _db.GetPlayerRecordByUserName(playerName, cancel);
|
||||||
if (record != null)
|
if (record != null)
|
||||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId);
|
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||||
|
|
||||||
// If all else fails, ask the auth server.
|
// If all else fails, ask the auth server.
|
||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
@@ -85,7 +92,7 @@ namespace Content.Server.Administration
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null);
|
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
|
public async Task<LocatedPlayerData?> LookupIdAsync(NetUserId userId, CancellationToken cancel = default)
|
||||||
@@ -95,19 +102,19 @@ namespace Content.Server.Administration
|
|||||||
{
|
{
|
||||||
var address = session.ConnectedClient.RemoteEndPoint.Address;
|
var address = session.ConnectedClient.RemoteEndPoint.Address;
|
||||||
var hwId = session.ConnectedClient.UserData.HWId;
|
var hwId = session.ConnectedClient.UserData.HWId;
|
||||||
return new LocatedPlayerData(userId, address, hwId);
|
return new LocatedPlayerData(userId, address, hwId, session.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check database for past players.
|
// Check database for past players.
|
||||||
var record = await _db.GetPlayerRecordByUserId(userId, cancel);
|
var record = await _db.GetPlayerRecordByUserId(userId, cancel);
|
||||||
if (record != null)
|
if (record != null)
|
||||||
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId);
|
return new LocatedPlayerData(record.UserId, record.LastSeenAddress, record.HWId, record.LastSeenUserName);
|
||||||
|
|
||||||
// If all else fails, ask the auth server.
|
// If all else fails, ask the auth server.
|
||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
var authServer = _configurationManager.GetCVar(CVars.AuthServer);
|
||||||
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
|
var requestUri = $"{authServer}api/query/userid?userid={WebUtility.UrlEncode(userId.UserId.ToString())}";
|
||||||
using var resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, requestUri), cancel);
|
using var resp = await client.GetAsync(requestUri, cancel);
|
||||||
|
|
||||||
if (resp.StatusCode == HttpStatusCode.NotFound)
|
if (resp.StatusCode == HttpStatusCode.NotFound)
|
||||||
return null;
|
return null;
|
||||||
@@ -118,7 +125,15 @@ namespace Content.Server.Administration
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new LocatedPlayerData(userId, null, null);
|
var responseData = await resp.Content.ReadFromJsonAsync<UserDataResponse>(cancellationToken: cancel);
|
||||||
|
|
||||||
|
if (responseData == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS("PlayerLocate", "Auth server returned null response!");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LocatedPlayerData(new NetUserId(responseData.UserId), null, null, responseData.UserName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
|
public async Task<LocatedPlayerData?> LookupIdByNameOrIdAsync(string playerName, CancellationToken cancel = default)
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Utility;
|
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.GameObjects.Components;
|
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
|
|
||||||
namespace Content.Shared.Administration
|
namespace Content.Shared.Administration
|
||||||
|
|||||||
@@ -154,6 +154,13 @@ namespace Content.Shared.CCVar
|
|||||||
public static readonly CVarDef<int> SoftMaxPlayers =
|
public static readonly CVarDef<int> SoftMaxPlayers =
|
||||||
CVarDef.Create("game.soft_max_players", 30, CVar.SERVERONLY | CVar.ARCHIVE);
|
CVarDef.Create("game.soft_max_players", 30, CVar.SERVERONLY | CVar.ARCHIVE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Discord
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static readonly CVarDef<string> DiscordAHelpWebhook =
|
||||||
|
CVarDef.Create("discord.ahelp_webhook", string.Empty, CVar.SERVERONLY);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Suspicion
|
* Suspicion
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
bwoink-system-starmute-message-no-other-users-primary = *System: Nobody is available to receive your message. Try pinging Game Admins on Discord.
|
bwoink-system-starmute-message-no-other-users = *System: Nobody is available to receive your message. Try pinging Game Admins on Discord.
|
||||||
|
|
||||||
bwoink-system-starmute-message-no-other-users-secondary = *System: Nobody is available to receive your message.
|
bwoink-system-starmute-message-no-other-users-webhook = *System: Your message has been relayed to the admins via discord.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user