From a17e0452a0344e5bf928e4220e2bc059ed17e3d9 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 16 Jun 2024 16:42:50 -0700 Subject: [PATCH] Add discord webhooks --- lib/main.dart | 2 + lib/pages/DiscordConfigPage.dart | 91 ++++++++++++++++++++++++++++++ lib/pages/ServerSettings.dart | 24 ++++++++ lib/statemachine.dart | 27 ++++++++- lib/structs/discordHookHelper.dart | 57 +++++++++++++++++++ lib/structs/settingsEntry.dart | 9 +++ 6 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 lib/pages/DiscordConfigPage.dart create mode 100644 lib/structs/discordHookHelper.dart diff --git a/lib/main.dart b/lib/main.dart index 1d21bf8..6cf8126 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:libac_dart/packets/packets.dart'; import 'package:servermanager/packets/ClientPackets.dart'; import 'package:servermanager/pages/Constants.dart'; +import 'package:servermanager/pages/DiscordConfigPage.dart'; import 'package:servermanager/pages/GameServerPage.dart'; import 'package:servermanager/pages/ModManager.dart'; import 'package:servermanager/pages/autorestart.dart'; @@ -33,6 +34,7 @@ class MyApp extends StatelessWidget { "/server/ports": (context) => ServerSettingsPage(), "/server/mods": (context) => ModManager(settings: appSettings), "/server/mods/edit": (context) => ModPage(), + "/server/discord": (context) => DiscordConfigPage() }); } } diff --git a/lib/pages/DiscordConfigPage.dart b/lib/pages/DiscordConfigPage.dart new file mode 100644 index 0000000..f6a4bca --- /dev/null +++ b/lib/pages/DiscordConfigPage.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:servermanager/pages/Constants.dart'; +import 'package:servermanager/structs/discordHookHelper.dart'; +import 'package:servermanager/structs/settings.dart'; + +class DiscordConfigPage extends StatefulWidget { + @override + State createState() => DiscordConfigurationState(); +} + +class DiscordConfigurationState extends State { + TextEditingController ServerNameController = TextEditingController(); + TextEditingController URLController = TextEditingController(); + + @override + void didChangeDependencies() { + Settings settings = Settings(); + final discord = settings.inst!.discord; + + if (discord != null) { + ServerNameController.text = discord.serverName; + URLController.text = discord.url; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Conan Exiles Server Manager - Discord Hook Settings"), + backgroundColor: Constants.TITLEBAR_COLOR, + actions: [ + IconButton( + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + "Discord Webhook has been successfully reset and will no longer be used"))); + + Settings settings = Settings(); + settings.inst!.discord = null; + + Navigator.pop(context); + }, + icon: Icon(Icons.clear_all)) + ], + ), + floatingActionButton: ElevatedButton( + onPressed: () { + Navigator.pop( + context, + DiscordHookProps( + url: URLController.text, + serverName: ServerNameController.text)); + }, + child: Text("Submit"), + ), + body: SingleChildScrollView( + child: Column( + children: [ + Row( + children: [ + SizedBox( + width: 256, + child: ListTile( + title: Text("Server Name"), + )), + Expanded( + child: TextField( + controller: ServerNameController, + )) + ], + ), + Row( + children: [ + SizedBox( + width: 256, + child: ListTile( + title: Text("URL"), + )), + Expanded( + child: TextField( + controller: URLController, + )) + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/ServerSettings.dart b/lib/pages/ServerSettings.dart index eadc2ed..f263886 100644 --- a/lib/pages/ServerSettings.dart +++ b/lib/pages/ServerSettings.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:servermanager/structs/discordHookHelper.dart'; +import 'package:servermanager/structs/settings.dart'; import '../structs/serversettings.dart'; @@ -106,6 +108,28 @@ class ServerSettingsState extends State { )), ], ), + Row( + children: [ + ListTile( + title: Text("Discord WebHook"), + onTap: () async { + var response = + await Navigator.pushNamed(context, "/server/discord"); + + if (response == null) + return; + else { + DiscordHookProps editResult = + response as DiscordHookProps; + Settings settings = Settings(); + settings.inst!.discord = editResult; + + setState(() {}); + } + }, + ) + ], + ), Row( children: [ ElevatedButton( diff --git a/lib/statemachine.dart b/lib/statemachine.dart index 2f4ec52..5ceaff6 100644 --- a/lib/statemachine.dart +++ b/lib/statemachine.dart @@ -6,6 +6,7 @@ import 'package:libac_dart/utils/IOTools.dart'; import 'package:libac_dart/utils/TimeUtils.dart'; import 'package:servermanager/game.dart'; import 'package:servermanager/structs/SessionData.dart'; +import 'package:servermanager/structs/discordHookHelper.dart'; import 'package:servermanager/structs/mod.dart'; import 'package:servermanager/structs/settings.dart'; import 'package:servermanager/wine.dart'; @@ -130,6 +131,12 @@ class StateMachine { PacketServer.socket!.close(); }); + DiscordHookHelper.sendWebHook( + settings.inst!.discord, + DiscordHookProps.OFFLINE_ALERT, + "Server is now offline", + "The server is shutting down"); + changeState(States.Inactive); } else if (currentState == States.Starting) { // Server startup in progress @@ -213,6 +220,12 @@ class StateMachine { SessionData.timer = settings.inst!.timer.time.copy(); changeState(States.PreStart); + DiscordHookHelper.sendWebHook( + settings.inst!.discord, + DiscordHookProps.ONLINE_ALERT, + "Server is now starting up", + "The server is starting up now, it should appear on the server list in a few minutes"); + resetKillswitch(); SessionData.enableRestartTimer = settings.inst!.timer.enabled; @@ -284,14 +297,24 @@ class StateMachine { if (send && SessionData.enableRestartTimer) { // Send the alert message SessionData.CURRENT_INTERVAL = current; + int alertColor = 0; if (current.type == WarnType.Intrusive) { print("Sending alert '${current.warning}'"); settings.sendRconCommand("broadcast ${current.warning}"); + + // Set discord alert color + alertColor = DiscordHookProps.ALERT_INTRUSIVE; } else if (current.type == WarnType.NonIntrusive) { print("Sending chat message '${current.warning}'"); - settings.sendRconCommand( - "ast chat \"global\" \"${current.warning}\""); + //settings.sendRconCommand( + // "ast chat \"global\" \"${current.warning}\""); + + // Set discord alert color + alertColor = DiscordHookProps.ALERT; } + + DiscordHookHelper.sendWebHook(settings.inst!.discord, alertColor, + "Server Restart Alert", "${current.warning}"); } // Check Shutdown Pending diff --git a/lib/structs/discordHookHelper.dart b/lib/structs/discordHookHelper.dart new file mode 100644 index 0000000..870cc0a --- /dev/null +++ b/lib/structs/discordHookHelper.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:libac_dart/nbt/impl/CompoundTag.dart'; +import 'package:libac_dart/nbt/impl/StringTag.dart'; + +class DiscordHookHelper { + static Future sendWebHook(DiscordHookProps? props, int colorCode, + String title, String content) async { + if (props == null) return; // The webhook setting is not yet set up + var js = json.encode({ + "content": "", + "embeds": [ + { + "title": title, + "description": content, + "color": colorCode, + "author": {"name": props.serverName} + } + ], + "attachments": [] + }); + + Dio dio = Dio(); + dio.post(props.url, data: js); + } +} + +class DiscordHookProps { + String url; + String serverName; + + DiscordHookProps({required this.url, required this.serverName}); + + CompoundTag serialize() { + CompoundTag ct = CompoundTag(); + ct.put(TAG_URL, StringTag.valueOf(url)); + ct.put(TAG_SERVER_NAME, StringTag.valueOf(serverName)); + + return ct; + } + + static DiscordHookProps deserialize(CompoundTag ct) { + return DiscordHookProps( + url: ct.get(TAG_URL)!.asString(), + serverName: ct.get(TAG_SERVER_NAME)!.asString()); + } + + static const String TAG_URL = "url"; + static const String TAG_SERVER_NAME = "serverName"; + static const String TAG_NAME = "discord"; + + static const int ONLINE_ALERT = 1869056; + static const int OFFLINE_ALERT = 8716288; + static const int ALERT = 21893; // non-intrusive + static const int ALERT_INTRUSIVE = 6291589; +} diff --git a/lib/structs/settingsEntry.dart b/lib/structs/settingsEntry.dart index 1ef91df..ee2817d 100644 --- a/lib/structs/settingsEntry.dart +++ b/lib/structs/settingsEntry.dart @@ -5,11 +5,13 @@ import 'package:libac_dart/nbt/impl/ListTag.dart'; import 'package:libac_dart/utils/TimeUtils.dart'; import 'package:servermanager/structs/autorestarts.dart'; import 'package:servermanager/structs/credentials.dart'; +import 'package:servermanager/structs/discordHookHelper.dart'; import 'package:servermanager/structs/mod.dart'; import 'package:servermanager/structs/serversettings.dart'; class SettingsEntry { List mods = []; + DiscordHookProps? discord; Credentials? steam_creds; bool pterodactylMode = true; // Default is to be compatible AutomaticRestartInfo timer = @@ -33,6 +35,10 @@ class SettingsEntry { st.pterodactylMode = NbtUtils.readBoolean(tag, "pterodactyl"); } + if (tag.containsKey(DiscordHookProps.TAG_NAME)) + st.discord = DiscordHookProps.deserialize( + tag.get(DiscordHookProps.TAG_NAME)!.asCompoundTag()); + st.mods.clear(); ListTag lMods = tag.get("mods") as ListTag; for (Tag tag in lMods.value) { @@ -55,6 +61,9 @@ class SettingsEntry { } tag.put("mods", lMods); + if (discord != null) + tag.put(DiscordHookProps.TAG_NAME, discord!.serialize()); + return tag; } }