Skip to content

Commit

Permalink
Уведомления о обновлениях сервера через Discord webhook (#920)
Browse files Browse the repository at this point in the history
# Описание PR
Добавлено отправление уведомлений в Discord при обновлении сервера.
Теперь, при получении обновлений на основной или дев сервер, будет
отправляться соответствующее сообщение в указанный webhook.

## Почему / Баланс
Это изменение необходимо для информирования команды и сообщества о
получении обновлений сервера.

**Ссылка на публикацию в Discord**
нет

## Техническая информация
- Реализована отправка сообщения через Discord webhook при получении
обновлений.
- Добавлена проверка наличия webhook URL в конфигурации.
- Отправляются сообщения с информацией об обновлениях, включая имя
сервера, описание, версию движка и билд версию.
- В случае успешного отправления сообщения также осуществляется пинг
указанной роли в Discord, если это указано в настройках.

### Новая переменная
- **`ShouldPingOnUpdate`**: Эта переменная конфигурации отвечает за
решение, нужно ли отправлять пинг в Discord при получении обновлений.
Если в конфиге указано `true`, будет отправлен пинг роли с ID
`1275740664264659017` (роль "Обновления") в конце сообщения. Если
значение переменной `false`, пинг не отправляется. В конфиге
`discord.server_update_webhook_ping`

## Медиа

![image](https://github.com/user-attachments/assets/4d3a4701-0a32-48c3-8b8f-88d3c37d9393)

![image](https://github.com/user-attachments/assets/3d90d537-34f8-46ae-a477-6947422b2d71)


## Требования
- [x] Я прочитал(а) и следую [Руководство по созданию пулл
реквестов](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
Я понимаю, что в противном случае мой ПР может быть закрыт по усмотрению
мейнтейнера.
- [x] Я добавил скриншоты/видео к этому пулл реквесту, демонстрирующие
его изменения в игре, **или** этот пулл реквест не требует демонстрации
в игре

## Критические изменения
Нет

**Чейнджлог**
:cl: Шрёдька
- add: Добавлено отправление уведомлений в Discord при обновлениях
сервера. Уведомления включают информацию о сервере и версии, а также
пинг роли в Discord для уведомления команды, если это указано в
настройках конфига.
  • Loading branch information
Schrodinger71 authored Dec 29, 2024
1 parent 66f6f0e commit 3493846
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Shared.Administration;
using Robust.Shared.Console;
using Content.Server.Administration;
using Content.Server.ServerUpdates;

namespace Content.Server.ADT.Administration.Commands;


[AdminCommand(AdminFlags.Permissions)]
public sealed class SendUpdateServerCommand : LocalizedCommands
{
[Dependency] private readonly ServerUpdateManager _serverManager = default!;
public override string Command => "send_updateserver_devtest";

public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
var player = shell.Player;
if (player == null)
{
shell.WriteError(LocalizationManager.GetString("shell-target-player-does-not-exist"));
return;
}

_serverManager.SendDiscordWebHookUpdateMessage();
}
}
89 changes: 89 additions & 0 deletions Content.Server/ServerUpdates/ServerUpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Shared.ADT.CCVar;
using Content.Server.Discord;
using Content.Server.GameTicking;

namespace Content.Server.ServerUpdates;

Expand All @@ -27,6 +30,8 @@ public sealed class ServerUpdateManager : IPostInjectInit
[Dependency] private readonly IBaseServer _server = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly DiscordWebhook _discord = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;

private ISawmill _sawmill = default!;

Expand Down Expand Up @@ -102,6 +107,7 @@ private void WatchdogOnUpdateReceived()
_chatManager.DispatchServerAnnouncement(Loc.GetString("server-updates-received"));
_updateOnRoundEnd = true;
ServerEmptyUpdateRestartCheck("update notification");
SendDiscordWebHookUpdateMessage(); // ADT-Tweak
}

/// <summary>
Expand Down Expand Up @@ -148,4 +154,87 @@ void IPostInjectInit.PostInject()
{
_sawmill = _logManager.GetSawmill("restart");
}
// ADT-Tweak-start: Отправка сообщения в Discord при обновлении сервера
public async void SendDiscordWebHookUpdateMessage()
{
if (!string.IsNullOrWhiteSpace(_cfg.GetCVar(ADTDiscordWebhookCCVars.DiscordServerUpdateWebhook)))
{
var webhookUrl = _cfg.GetCVar(ADTDiscordWebhookCCVars.DiscordServerUpdateWebhook);
if (webhookUrl == null)
return;

if (await _discord.GetWebhook(webhookUrl) is not { } webhookData)
return;

// Получение данных сервера
var serverName = _cfg.GetCVar<string>("game.hostname");
var serverDesc = _cfg.GetCVar<string>("game.desc");
var engineVersion = _cfg.GetCVar<string>("build.engine_version");
var buildVersion = _cfg.GetCVar<string>("build.version");

// Сообщение о перезапуске сервера
var descContent = "Обновление получено, сервер автоматически перезапустится для обновления в конце этого раунда.";

// Определение состояния раунда
var gameTicker = _entitySystemManager.GetEntitySystem<GameTicker>();
var roundDescription = gameTicker.RunLevel switch
{
GameRunLevel.PreRoundLobby => gameTicker.RoundId == 0
? "pre-round lobby after server restart"
: $"pre-round lobby for round {gameTicker.RoundId + 1}",
GameRunLevel.InRound => $"round {gameTicker.RoundId}",
GameRunLevel.PostRound => $"post-round {gameTicker.RoundId}",
_ => throw new ArgumentOutOfRangeException(nameof(gameTicker.RunLevel), $"{gameTicker.RunLevel} was not matched."),
};

// Формирование структуры embed
var embed = new WebhookEmbed
{
Title = "Обновление пришло",
Description = descContent,
Color = 0x0e9c00,
Footer = new WebhookEmbedFooter
{
Text = $"{serverName} ({roundDescription})"
},
Fields = new List<WebhookEmbedField>()
};

// Добавление полей только если они не пустые
AddIfNotEmpty(embed.Fields, "Название сервера", serverName);
AddIfNotEmpty(embed.Fields, "Описание сервера", serverDesc);
AddIfNotEmpty(embed.Fields, "RobustToolbox version", engineVersion);
AddIfNotEmpty(embed.Fields, "Build version", buildVersion);

// Формирование полезной нагрузки
var payload = new WebhookPayload
{
Embeds = new List<WebhookEmbed> { embed },
Username = Loc.GetString("username-webhook-update")
};

// Проверка, нужно ли добавлять пинг
var shouldPingOnUpdate = _cfg.GetCVar(ADTDiscordWebhookCCVars.ShouldPingOnUpdate);
if (shouldPingOnUpdate)
{
// Добавляем пинг в поле Content. Это будет сообщение, которое будет сверху
payload.Content = "<@&1275740664264659017>"; // ID роли "Обновления"
}

// Отправка сообщения в Discord
var identifier = webhookData.ToIdentifier();
payload.AllowedMentions.AllowRoleMentions();
await _discord.CreateMessage(identifier, payload);
}
}

// Вспомогательный метод для добавления полей в embed
private void AddIfNotEmpty(List<WebhookEmbedField> fields, string fieldName, string? fieldValue)
{
if (!string.IsNullOrWhiteSpace(fieldValue))
{
fields.Add(new WebhookEmbedField { Name = fieldName, Value = fieldValue, Inline = true });
}
}
// ADT-Tweak-end
}
16 changes: 15 additions & 1 deletion Content.Shared/ADT/CCVar/CCVars.WebhookDiscord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,19 @@ public sealed class ADTDiscordWebhookCCVars : CVars
/// URL of the Discord webhook which will relay adminwho info to the channel.
/// </summary>
public static readonly CVarDef<string> DiscordAdminwhoWebhook =
CVarDef.Create("discord.adminwho_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL);
CVarDef.Create("discord.adminwho_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE);

/// <summary>
/// This constant specifies a webhook that will send a message to Discord when a server updates.
/// </summary>
public static readonly CVarDef<string> DiscordServerUpdateWebhook =
CVarDef.Create("discord.server_update_webhook", string.Empty, CVar.SERVERONLY | CVar.CONFIDENTIAL | CVar.ARCHIVE);

/// <summary>
/// This constant specifies whether a ping should be sent to a specific Discord role
/// when the server update notification is triggered. If set to <c>true</c>, a ping will be sent to the role.
/// If set to <c>false</c>, no ping will be sent.
/// </summary>
public static readonly CVarDef<bool> ShouldPingOnUpdate =
CVarDef.Create("discord.server_update_webhook_ping", true, CVar.SERVERONLY | CVar.ARCHIVE);
}

0 comments on commit 3493846

Please sign in to comment.