mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 12:41:30 +00:00
[Discord Integration] Native Discord Integration (#2140)
* Start of discord integration work * more testing * Discord client work * More discord work * Cleanup * Handle retry timer response and max retries * Update base retry timer * Move Discord queue handler to UCS, add queuer to own thread * Post merge * Send up Zone::SendDiscordMessage * Start of discord integration work * more testing * Discord client work * More discord work * Cleanup * Move Discord queue handler to UCS, add queuer to own thread * Post merge * Push up tables * Quest API stuff. * Update 2022_05_07_discord_webhooks.sql * Post merge fixes * Push up manifest * Flip logging signs in logic from copy / paste of inverse logic before * Make sure we add new line to quest api sourced messages Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
This commit is contained in:
parent
8ef3e87370
commit
4639405fdf
@ -7,6 +7,7 @@ SET(common_sources
|
||||
compression.cpp
|
||||
condition.cpp
|
||||
content/world_content_service.cpp
|
||||
discord/discord.cpp
|
||||
crash.cpp
|
||||
crc16.cpp
|
||||
crc32.cpp
|
||||
@ -34,6 +35,7 @@ SET(common_sources
|
||||
event_sub.cpp
|
||||
expedition_lockout_timer.cpp
|
||||
extprofile.cpp
|
||||
discord_manager.cpp
|
||||
faction.cpp
|
||||
file_util.cpp
|
||||
guild_base.cpp
|
||||
@ -491,6 +493,8 @@ SET(common_headers
|
||||
database_schema.h
|
||||
dbcore.h
|
||||
deity.h
|
||||
discord/discord.h
|
||||
discord_manager.h
|
||||
dynamic_zone_base.h
|
||||
emu_constants.h
|
||||
emu_limits.h
|
||||
|
||||
@ -167,7 +167,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
|
||||
if (LogSys.log_settings[Logs::MySQLQuery].is_category_enabled == 1) {
|
||||
if ((strncasecmp(query, "select", 6) == 0)) {
|
||||
LogMySQLQuery(
|
||||
"{0} ({1} row{2} returned) ({3}s)",
|
||||
"{0}; -- ({1} row{2} returned) ({3}s)",
|
||||
query,
|
||||
requestResult.RowCount(),
|
||||
requestResult.RowCount() == 1 ? "" : "s",
|
||||
@ -176,7 +176,7 @@ MySQLRequestResult DBcore::QueryDatabase(const char *query, uint32 querylen, boo
|
||||
}
|
||||
else {
|
||||
LogMySQLQuery(
|
||||
"{0} ({1} row{2} affected) ({3}s)",
|
||||
"{0}; -- ({1} row{2} affected) ({3}s)",
|
||||
query,
|
||||
requestResult.RowsAffected(),
|
||||
requestResult.RowsAffected() == 1 ? "" : "s",
|
||||
|
||||
91
common/discord/discord.cpp
Normal file
91
common/discord/discord.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include "discord.h"
|
||||
#include "../http/httplib.h"
|
||||
#include "../json/json.h"
|
||||
#include "../string_util.h"
|
||||
#include "../eqemu_logsys.h"
|
||||
|
||||
constexpr int MAX_RETRIES = 10;
|
||||
|
||||
void Discord::SendWebhookMessage(const std::string &message, const std::string &webhook_url)
|
||||
{
|
||||
// validate
|
||||
if (webhook_url.empty()) {
|
||||
LogDiscord("[webhook_url] is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// validate
|
||||
if (webhook_url.find("http://") == std::string::npos && webhook_url.find("https://") == std::string::npos) {
|
||||
LogDiscord("[webhook_url] [{}] does not contain a valid http/s prefix.", webhook_url);
|
||||
return;
|
||||
}
|
||||
|
||||
// split
|
||||
auto s = SplitString(webhook_url, '/');
|
||||
|
||||
// url
|
||||
std::string base_url = fmt::format("{}//{}", s[0], s[2]);
|
||||
std::string endpoint = replace_string(webhook_url, base_url, "");
|
||||
|
||||
// client
|
||||
httplib::Client cli(base_url.c_str());
|
||||
cli.set_connection_timeout(0, 15000000); // 15 sec
|
||||
cli.set_read_timeout(15, 0); // 15 seconds
|
||||
cli.set_write_timeout(15, 0); // 15 seconds
|
||||
httplib::Headers headers = {
|
||||
{"Content-Type", "application/json"}
|
||||
};
|
||||
|
||||
// payload
|
||||
Json::Value p;
|
||||
p["content"] = message;
|
||||
std::stringstream payload;
|
||||
payload << p;
|
||||
|
||||
bool retry = true;
|
||||
int retries = 0;
|
||||
int retry_timer = 1000;
|
||||
while (retry) {
|
||||
if (auto res = cli.Post(endpoint.c_str(), payload.str(), "application/json")) {
|
||||
if (res->status != 200 && res->status != 204) {
|
||||
LogError("[Discord Client] Code [{}] Error [{}]", res->status, res->body);
|
||||
}
|
||||
if (res->status == 429) {
|
||||
if (!res->body.empty()) {
|
||||
std::stringstream ss(res->body);
|
||||
Json::Value response;
|
||||
|
||||
try {
|
||||
ss >> response;
|
||||
}
|
||||
catch (std::exception const &ex) {
|
||||
LogDiscord("JSON serialization failure [{}] via [{}]", ex.what(), res->body);
|
||||
}
|
||||
|
||||
retry_timer = std::stoi(response["retry_after"].asString()) + 500;
|
||||
}
|
||||
|
||||
LogDiscord("Rate limited... retrying message in [{}ms]", retry_timer);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_timer + 500));
|
||||
}
|
||||
if (res->status == 204) {
|
||||
retry = false;
|
||||
}
|
||||
if (retries > MAX_RETRIES) {
|
||||
LogDiscord("Retries exceeded for message [{}]", message);
|
||||
retry = false;
|
||||
}
|
||||
|
||||
retries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Discord::FormatDiscordMessage(uint16 category_id, const std::string &message)
|
||||
{
|
||||
if (category_id == Logs::LogCategory::MySQLQuery) {
|
||||
return fmt::format("```sql\n{}\n```", message);
|
||||
}
|
||||
|
||||
return message + "\n";
|
||||
}
|
||||
15
common/discord/discord.h
Normal file
15
common/discord/discord.h
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef EQEMU_DISCORD_H
|
||||
#define EQEMU_DISCORD_H
|
||||
|
||||
|
||||
#include <string>
|
||||
#include "../types.h"
|
||||
|
||||
class Discord {
|
||||
public:
|
||||
static void SendWebhookMessage(const std::string& message, const std::string& webhook_url);
|
||||
static std::string FormatDiscordMessage(uint16 category_id, const std::string& message);
|
||||
};
|
||||
|
||||
|
||||
#endif //EQEMU_DISCORD_H
|
||||
65
common/discord_manager.cpp
Normal file
65
common/discord_manager.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "discord_manager.h"
|
||||
#include "../common/discord/discord.h"
|
||||
#include "../common/eqemu_logsys.h"
|
||||
#include "../common/string_util.h"
|
||||
|
||||
void DiscordManager::QueueWebhookMessage(uint32 webhook_id, const std::string &message)
|
||||
{
|
||||
webhook_queue_lock.lock();
|
||||
webhook_message_queue[webhook_id].emplace_back(message);
|
||||
webhook_queue_lock.unlock();
|
||||
}
|
||||
|
||||
constexpr int MAX_MESSAGE_LENGTH = 1900;
|
||||
|
||||
void DiscordManager::ProcessMessageQueue()
|
||||
{
|
||||
if (webhook_message_queue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
webhook_queue_lock.lock();
|
||||
for (auto &q: webhook_message_queue) {
|
||||
LogDiscord("Processing [{}] messages in queue for webhook ID [{}]...", q.second.size(), q.first);
|
||||
|
||||
auto webhook = LogSys.discord_webhooks[q.first];
|
||||
std::string message;
|
||||
|
||||
for (auto &m: q.second) {
|
||||
// next message would become too large
|
||||
bool next_message_too_large = ((int) m.length() + (int) message.length()) > MAX_MESSAGE_LENGTH;
|
||||
if (next_message_too_large) {
|
||||
Discord::SendWebhookMessage(
|
||||
message,
|
||||
webhook.webhook_url
|
||||
);
|
||||
message = "";
|
||||
}
|
||||
|
||||
message += m;
|
||||
|
||||
// one single message was too large
|
||||
// this should rarely happen but the message will need to be split
|
||||
if ((int) message.length() > MAX_MESSAGE_LENGTH) {
|
||||
for (unsigned mi = 0; mi < message.length(); mi += MAX_MESSAGE_LENGTH) {
|
||||
Discord::SendWebhookMessage(
|
||||
message.substr(mi, MAX_MESSAGE_LENGTH),
|
||||
webhook.webhook_url
|
||||
);
|
||||
}
|
||||
message = "";
|
||||
}
|
||||
}
|
||||
|
||||
// final flush
|
||||
if (!message.empty()) {
|
||||
Discord::SendWebhookMessage(
|
||||
message,
|
||||
webhook.webhook_url
|
||||
);
|
||||
}
|
||||
|
||||
webhook_message_queue.erase(q.first);
|
||||
}
|
||||
webhook_queue_lock.unlock();
|
||||
}
|
||||
19
common/discord_manager.h
Normal file
19
common/discord_manager.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef EQEMU_DISCORD_MANAGER_H
|
||||
#define EQEMU_DISCORD_MANAGER_H
|
||||
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "../common/types.h"
|
||||
|
||||
class DiscordManager {
|
||||
public:
|
||||
void QueueWebhookMessage(uint32 webhook_id, const std::string& message);
|
||||
void ProcessMessageQueue();
|
||||
private:
|
||||
std::mutex webhook_queue_lock{};
|
||||
std::map<uint32, std::vector<std::string>> webhook_message_queue{};
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@ -23,6 +23,8 @@
|
||||
#include "platform.h"
|
||||
#include "string_util.h"
|
||||
#include "misc.h"
|
||||
#include "discord/discord.h"
|
||||
#include "repositories/discord_webhooks_repository.h"
|
||||
#include "repositories/logsys_categories_repository.h"
|
||||
|
||||
#include <iostream>
|
||||
@ -46,6 +48,7 @@ std::ofstream process_log;
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
|
||||
#endif
|
||||
|
||||
@ -89,7 +92,7 @@ namespace Console {
|
||||
EQEmuLogSys::EQEmuLogSys()
|
||||
{
|
||||
on_log_gmsay_hook = [](uint16 log_type, const std::string &) {};
|
||||
on_log_console_hook = [](uint16 debug_level, uint16 log_type, const std::string &) {};
|
||||
on_log_console_hook = [](uint16 log_type, const std::string &) {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,6 +111,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
|
||||
log_settings[log_category_id].log_to_console = 0;
|
||||
log_settings[log_category_id].log_to_file = 0;
|
||||
log_settings[log_category_id].log_to_gmsay = 0;
|
||||
log_settings[log_category_id].log_to_discord = 0;
|
||||
log_settings[log_category_id].is_category_enabled = 0;
|
||||
}
|
||||
|
||||
@ -135,6 +139,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
|
||||
log_settings[Logs::ChecksumVerification].log_to_console = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::ChecksumVerification].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::CombatRecord].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||
log_settings[Logs::Discord].log_to_console = static_cast<uint8>(Logs::General);
|
||||
|
||||
/**
|
||||
* RFC 5424
|
||||
@ -154,7 +159,8 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
|
||||
const bool log_to_console = log_settings[log_category_id].log_to_console > 0;
|
||||
const bool log_to_file = log_settings[log_category_id].log_to_file > 0;
|
||||
const bool log_to_gmsay = log_settings[log_category_id].log_to_gmsay > 0;
|
||||
const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay;
|
||||
const bool log_to_discord = log_settings[log_category_id].log_to_discord > 0;
|
||||
const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay || log_to_discord;
|
||||
if (is_category_enabled) {
|
||||
log_settings[log_category_id].is_category_enabled = 1;
|
||||
}
|
||||
@ -221,41 +227,12 @@ std::string EQEmuLogSys::FormatOutMessageString(
|
||||
return return_string + "[" + Logs::LogCategoryName[log_category] + "] " + in_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param debug_level
|
||||
* @param log_category
|
||||
* @param message
|
||||
*/
|
||||
void EQEmuLogSys::ProcessGMSay(
|
||||
uint16 debug_level,
|
||||
uint16 log_category,
|
||||
const std::string &message
|
||||
)
|
||||
{
|
||||
/**
|
||||
* Enabling Netcode based GMSay output creates a feedback loop that ultimately ends in a crash
|
||||
*/
|
||||
if (log_category == Logs::LogCategory::Netcode) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes that actually support hooks
|
||||
*/
|
||||
if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformZone ||
|
||||
EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformWorld
|
||||
) {
|
||||
on_log_gmsay_hook(log_category, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param debug_level
|
||||
* @param log_category
|
||||
* @param message
|
||||
*/
|
||||
void EQEmuLogSys::ProcessLogWrite(
|
||||
uint16 debug_level,
|
||||
uint16 log_category,
|
||||
const std::string &message
|
||||
)
|
||||
@ -273,10 +250,9 @@ void EQEmuLogSys::ProcessLogWrite(
|
||||
crash_log.close();
|
||||
}
|
||||
|
||||
char time_stamp[80];
|
||||
EQEmuLogSys::SetCurrentTimeStamp(time_stamp);
|
||||
|
||||
if (process_log) {
|
||||
char time_stamp[80];
|
||||
EQEmuLogSys::SetCurrentTimeStamp(time_stamp);
|
||||
process_log << time_stamp << " " << message << std::endl;
|
||||
}
|
||||
}
|
||||
@ -372,7 +348,7 @@ uint16 EQEmuLogSys::GetGMSayColorFromCategory(uint16 log_category)
|
||||
* @param log_category
|
||||
* @param message
|
||||
*/
|
||||
void EQEmuLogSys::ProcessConsoleMessage(uint16 debug_level, uint16 log_category, const std::string &message)
|
||||
void EQEmuLogSys::ProcessConsoleMessage(uint16 log_category, const std::string &message)
|
||||
{
|
||||
#ifdef _WINDOWS
|
||||
HANDLE console_handle;
|
||||
@ -390,7 +366,7 @@ void EQEmuLogSys::ProcessConsoleMessage(uint16 debug_level, uint16 log_category,
|
||||
std::cout << EQEmuLogSys::GetLinuxConsoleColorFromCategory(log_category) << message << LC_RESET << std::endl;
|
||||
#endif
|
||||
|
||||
on_log_console_hook(debug_level, log_category, message);
|
||||
on_log_console_hook(log_category, message);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -447,28 +423,28 @@ void EQEmuLogSys::Out(
|
||||
...
|
||||
)
|
||||
{
|
||||
bool log_to_console = true;
|
||||
if (log_settings[log_category].log_to_console < debug_level) {
|
||||
log_to_console = false;
|
||||
}
|
||||
bool log_to_console = log_settings[log_category].log_to_console > 0 &&
|
||||
log_settings[log_category].log_to_console >= debug_level;
|
||||
bool log_to_file = log_settings[log_category].log_to_file > 0 &&
|
||||
log_settings[log_category].log_to_file >= debug_level;
|
||||
bool log_to_gmsay = log_settings[log_category].log_to_gmsay > 0 &&
|
||||
log_settings[log_category].log_to_gmsay >= debug_level &&
|
||||
log_category != Logs::LogCategory::Netcode &&
|
||||
(EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformZone ||
|
||||
EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformWorld);
|
||||
bool log_to_discord = EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformZone &&
|
||||
log_settings[log_category].log_to_discord > 0 &&
|
||||
log_settings[log_category].log_to_discord >= debug_level &&
|
||||
log_settings[log_category].discord_webhook_id > 0 &&
|
||||
log_settings[log_category].discord_webhook_id < MAX_DISCORD_WEBHOOK_ID;
|
||||
|
||||
bool log_to_file = true;
|
||||
if (log_settings[log_category].log_to_file < debug_level) {
|
||||
log_to_file = false;
|
||||
}
|
||||
|
||||
bool log_to_gmsay = true;
|
||||
if (log_settings[log_category].log_to_gmsay < debug_level) {
|
||||
log_to_gmsay = false;
|
||||
}
|
||||
|
||||
const bool nothing_to_log = !log_to_console && !log_to_file && !log_to_gmsay;
|
||||
// bail out if nothing to log
|
||||
const bool nothing_to_log = !log_to_console && !log_to_file && !log_to_gmsay && !log_to_discord;
|
||||
if (nothing_to_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string prefix;
|
||||
|
||||
if (RuleB(Logging, PrintFileFunctionAndLine)) {
|
||||
prefix = fmt::format("[{0}::{1}:{2}] ", base_file_name(file), func, line);
|
||||
}
|
||||
@ -481,13 +457,16 @@ void EQEmuLogSys::Out(
|
||||
std::string output_debug_message = EQEmuLogSys::FormatOutMessageString(log_category, prefix + output_message);
|
||||
|
||||
if (log_to_console) {
|
||||
EQEmuLogSys::ProcessConsoleMessage(debug_level, log_category, output_debug_message);
|
||||
EQEmuLogSys::ProcessConsoleMessage(log_category, output_debug_message);
|
||||
}
|
||||
if (log_to_gmsay) {
|
||||
EQEmuLogSys::ProcessGMSay(debug_level, log_category, output_debug_message);
|
||||
on_log_gmsay_hook(log_category, message);
|
||||
}
|
||||
if (log_to_file) {
|
||||
EQEmuLogSys::ProcessLogWrite(debug_level, log_category, output_debug_message);
|
||||
EQEmuLogSys::ProcessLogWrite(log_category, output_debug_message);
|
||||
}
|
||||
if (log_to_discord && on_log_discord_hook) {
|
||||
on_log_discord_hook(log_category, log_settings[log_category].discord_webhook_id, output_message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -630,16 +609,20 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
continue;
|
||||
}
|
||||
|
||||
log_settings[c.log_category_id].log_to_console = static_cast<uint8>(c.log_to_console);
|
||||
log_settings[c.log_category_id].log_to_file = static_cast<uint8>(c.log_to_file);
|
||||
log_settings[c.log_category_id].log_to_gmsay = static_cast<uint8>(c.log_to_gmsay);
|
||||
log_settings[c.log_category_id].log_to_console = static_cast<uint8>(c.log_to_console);
|
||||
log_settings[c.log_category_id].log_to_file = static_cast<uint8>(c.log_to_file);
|
||||
log_settings[c.log_category_id].log_to_gmsay = static_cast<uint8>(c.log_to_gmsay);
|
||||
log_settings[c.log_category_id].log_to_discord = static_cast<uint8>(c.log_to_discord);
|
||||
log_settings[c.log_category_id].discord_webhook_id = c.discord_webhook_id;
|
||||
|
||||
// Determine if any output method is enabled for the category
|
||||
// and set it to 1 so it can used to check if category is enabled
|
||||
const bool log_to_console = log_settings[c.log_category_id].log_to_console > 0;
|
||||
const bool log_to_file = log_settings[c.log_category_id].log_to_file > 0;
|
||||
const bool log_to_gmsay = log_settings[c.log_category_id].log_to_gmsay > 0;
|
||||
const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay;
|
||||
const bool log_to_discord = log_settings[c.log_category_id].log_to_discord > 0 &&
|
||||
log_settings[c.log_category_id].discord_webhook_id > 0;
|
||||
const bool is_category_enabled = log_to_console || log_to_file || log_to_gmsay || log_to_discord;
|
||||
|
||||
if (is_category_enabled) {
|
||||
log_settings[c.log_category_id].is_category_enabled = 1;
|
||||
@ -669,6 +652,7 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
new_category.log_to_console = log_settings[i].log_to_console;
|
||||
new_category.log_to_gmsay = log_settings[i].log_to_gmsay;
|
||||
new_category.log_to_file = log_settings[i].log_to_file;
|
||||
new_category.log_to_discord = log_settings[i].log_to_discord;
|
||||
|
||||
LogsysCategoriesRepository::InsertOne(*m_database, new_category);
|
||||
}
|
||||
@ -676,6 +660,14 @@ EQEmuLogSys *EQEmuLogSys::LoadLogDatabaseSettings()
|
||||
|
||||
LogInfo("Loaded [{}] log categories", categories.size());
|
||||
|
||||
auto webhooks = DiscordWebhooksRepository::All(*m_database);
|
||||
if (!webhooks.empty()) {
|
||||
for (auto &w: webhooks) {
|
||||
discord_webhooks[w.id] = {w.id, w.webhook_name, w.webhook_url};
|
||||
}
|
||||
LogInfo("Loaded [{}] Discord webhooks", webhooks.size());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -685,3 +677,4 @@ EQEmuLogSys *EQEmuLogSys::SetDatabase(Database *db)
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -130,6 +130,7 @@ namespace Logs {
|
||||
ChecksumVerification,
|
||||
CombatRecord,
|
||||
Hate,
|
||||
Discord,
|
||||
MaxCategoryID /* Don't Remove this */
|
||||
};
|
||||
|
||||
@ -218,6 +219,7 @@ namespace Logs {
|
||||
"ChecksumVerification",
|
||||
"CombatRecord",
|
||||
"Hate",
|
||||
"Discord",
|
||||
};
|
||||
}
|
||||
|
||||
@ -225,6 +227,8 @@ namespace Logs {
|
||||
|
||||
class Database;
|
||||
|
||||
constexpr uint16 MAX_DISCORD_WEBHOOK_ID = 300;
|
||||
|
||||
class EQEmuLogSys {
|
||||
public:
|
||||
EQEmuLogSys();
|
||||
@ -287,9 +291,19 @@ public:
|
||||
uint8 log_to_file;
|
||||
uint8 log_to_console;
|
||||
uint8 log_to_gmsay;
|
||||
uint8 log_to_discord;
|
||||
int discord_webhook_id;
|
||||
uint8 is_category_enabled; /* When any log output in a category > 0, set this to 1 as (Enabled) */
|
||||
};
|
||||
|
||||
struct OriginationInfo {
|
||||
std::string zone_short_name;
|
||||
std::string zone_long_name;
|
||||
int instance_id;
|
||||
};
|
||||
|
||||
OriginationInfo origination_info{};
|
||||
|
||||
/**
|
||||
* Internally used memory reference for all log settings per category
|
||||
* These are loaded via DB and have defaults loaded in LoadLogSettingsDefaults
|
||||
@ -297,24 +311,38 @@ public:
|
||||
*/
|
||||
LogSettings log_settings[Logs::LogCategory::MaxCategoryID]{};
|
||||
|
||||
struct DiscordWebhooks {
|
||||
int id;
|
||||
std::string webhook_name;
|
||||
std::string webhook_url;
|
||||
};
|
||||
|
||||
DiscordWebhooks discord_webhooks[MAX_DISCORD_WEBHOOK_ID]{};
|
||||
|
||||
bool file_logs_enabled = false;
|
||||
|
||||
int log_platform = 0;
|
||||
std::string platform_file_name;
|
||||
int log_platform = 0;
|
||||
std::string platform_file_name;
|
||||
|
||||
|
||||
// gmsay
|
||||
uint16 GetGMSayColorFromCategory(uint16 log_category);
|
||||
|
||||
EQEmuLogSys * SetGMSayHandler(std::function<void(uint16 log_type, const std::string &)> f) {
|
||||
EQEmuLogSys *SetGMSayHandler(std::function<void(uint16 log_type, const std::string &)> f)
|
||||
{
|
||||
on_log_gmsay_hook = f;
|
||||
return this;
|
||||
}
|
||||
|
||||
EQEmuLogSys *SetDiscordHandler(std::function<void(uint16 log_category, int webhook_id, const std::string &)> f)
|
||||
{
|
||||
on_log_discord_hook = f;
|
||||
return this;
|
||||
}
|
||||
|
||||
// console
|
||||
void SetConsoleHandler(
|
||||
std::function<void(
|
||||
uint16 debug_level,
|
||||
uint16 log_type,
|
||||
const std::string &
|
||||
)> f
|
||||
@ -328,18 +356,17 @@ public:
|
||||
private:
|
||||
|
||||
// reference to database
|
||||
Database *m_database;
|
||||
|
||||
std::function<void(uint16 log_category, const std::string &)> on_log_gmsay_hook;
|
||||
std::function<void(uint16 debug_level, uint16 log_category, const std::string &)> on_log_console_hook;
|
||||
Database *m_database;
|
||||
std::function<void(uint16 log_category, const std::string &)> on_log_gmsay_hook;
|
||||
std::function<void(uint16 log_category, int webhook_id, const std::string &)> on_log_discord_hook;
|
||||
std::function<void(uint16 log_category, const std::string &)> on_log_console_hook;
|
||||
|
||||
std::string FormatOutMessageString(uint16 log_category, const std::string &in_message);
|
||||
std::string GetLinuxConsoleColorFromCategory(uint16 log_category);
|
||||
uint16 GetWindowsConsoleColorFromCategory(uint16 log_category);
|
||||
|
||||
void ProcessConsoleMessage(uint16 debug_level, uint16 log_category, const std::string &message);
|
||||
void ProcessGMSay(uint16 debug_level, uint16 log_category, const std::string &message);
|
||||
void ProcessLogWrite(uint16 debug_level, uint16 log_category, const std::string &message);
|
||||
void ProcessConsoleMessage(uint16 log_category, const std::string &message);
|
||||
void ProcessLogWrite(uint16 log_category, const std::string &message);
|
||||
bool IsRfc5424LogCategory(uint16 log_category);
|
||||
};
|
||||
|
||||
|
||||
@ -726,6 +726,16 @@
|
||||
OutF(LogSys, Logs::Detail, Logs::Hate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogDiscord(message, ...) do {\
|
||||
if (LogSys.log_settings[Logs::Discord].is_category_enabled == 1)\
|
||||
OutF(LogSys, Logs::General, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define LogDiscordDetail(message, ...) do {\
|
||||
if (LogSys.log_settings[Logs::Discord].is_category_enabled == 1)\
|
||||
OutF(LogSys, Logs::Detail, Logs::Discord, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
} while (0)
|
||||
|
||||
#define Log(debug_level, log_category, message, ...) do {\
|
||||
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
|
||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||
|
||||
336
common/repositories/base/base_discord_webhooks_repository.h
Normal file
336
common/repositories/base/base_discord_webhooks_repository.h
Normal file
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* DO NOT MODIFY THIS FILE
|
||||
*
|
||||
* This repository was automatically generated and is NOT to be modified directly.
|
||||
* Any repository modifications are meant to be made to the repository extending the base.
|
||||
* Any modifications to base repositories are to be made by the generator only
|
||||
*
|
||||
* @generator ./utils/scripts/generators/repository-generator.pl
|
||||
* @docs https://eqemu.gitbook.io/server/in-development/developer-area/repositories
|
||||
*/
|
||||
|
||||
#ifndef EQEMU_BASE_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
#define EQEMU_BASE_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
|
||||
#include "../../database.h"
|
||||
#include "../../string_util.h"
|
||||
#include <ctime>
|
||||
|
||||
class BaseDiscordWebhooksRepository {
|
||||
public:
|
||||
struct DiscordWebhooks {
|
||||
int id;
|
||||
std::string webhook_name;
|
||||
std::string webhook_url;
|
||||
time_t created_at;
|
||||
time_t deleted_at;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
{
|
||||
return std::string("id");
|
||||
}
|
||||
|
||||
static std::vector<std::string> Columns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"webhook_name",
|
||||
"webhook_url",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<std::string> SelectColumns()
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"webhook_name",
|
||||
"webhook_url",
|
||||
"UNIX_TIMESTAMP(created_at)",
|
||||
"UNIX_TIMESTAMP(deleted_at)",
|
||||
};
|
||||
}
|
||||
|
||||
static std::string ColumnsRaw()
|
||||
{
|
||||
return std::string(implode(", ", Columns()));
|
||||
}
|
||||
|
||||
static std::string SelectColumnsRaw()
|
||||
{
|
||||
return std::string(implode(", ", SelectColumns()));
|
||||
}
|
||||
|
||||
static std::string TableName()
|
||||
{
|
||||
return std::string("discord_webhooks");
|
||||
}
|
||||
|
||||
static std::string BaseSelect()
|
||||
{
|
||||
return fmt::format(
|
||||
"SELECT {} FROM {}",
|
||||
SelectColumnsRaw(),
|
||||
TableName()
|
||||
);
|
||||
}
|
||||
|
||||
static std::string BaseInsert()
|
||||
{
|
||||
return fmt::format(
|
||||
"INSERT INTO {} ({}) ",
|
||||
TableName(),
|
||||
ColumnsRaw()
|
||||
);
|
||||
}
|
||||
|
||||
static DiscordWebhooks NewEntity()
|
||||
{
|
||||
DiscordWebhooks entry{};
|
||||
|
||||
entry.id = 0;
|
||||
entry.webhook_name = "";
|
||||
entry.webhook_url = "";
|
||||
entry.created_at = 0;
|
||||
entry.deleted_at = 0;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
static DiscordWebhooks GetDiscordWebhooksEntry(
|
||||
const std::vector<DiscordWebhooks> &discord_webhookss,
|
||||
int discord_webhooks_id
|
||||
)
|
||||
{
|
||||
for (auto &discord_webhooks : discord_webhookss) {
|
||||
if (discord_webhooks.id == discord_webhooks_id) {
|
||||
return discord_webhooks;
|
||||
}
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static DiscordWebhooks FindOne(
|
||||
Database& db,
|
||||
int discord_webhooks_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE id = {} LIMIT 1",
|
||||
BaseSelect(),
|
||||
discord_webhooks_id
|
||||
)
|
||||
);
|
||||
|
||||
auto row = results.begin();
|
||||
if (results.RowCount() == 1) {
|
||||
DiscordWebhooks entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.webhook_name = row[1] ? row[1] : "";
|
||||
entry.webhook_url = row[2] ? row[2] : "";
|
||||
entry.created_at = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
|
||||
entry.deleted_at = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
return NewEntity();
|
||||
}
|
||||
|
||||
static int DeleteOne(
|
||||
Database& db,
|
||||
int discord_webhooks_id
|
||||
)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {} = {}",
|
||||
TableName(),
|
||||
PrimaryKey(),
|
||||
discord_webhooks_id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int UpdateOne(
|
||||
Database& db,
|
||||
DiscordWebhooks discord_webhooks_entry
|
||||
)
|
||||
{
|
||||
std::vector<std::string> update_values;
|
||||
|
||||
auto columns = Columns();
|
||||
|
||||
update_values.push_back(columns[1] + " = '" + EscapeString(discord_webhooks_entry.webhook_name) + "'");
|
||||
update_values.push_back(columns[2] + " = '" + EscapeString(discord_webhooks_entry.webhook_url) + "'");
|
||||
update_values.push_back(columns[3] + " = FROM_UNIXTIME(" + (discord_webhooks_entry.created_at > 0 ? std::to_string(discord_webhooks_entry.created_at) : "null") + ")");
|
||||
update_values.push_back(columns[4] + " = FROM_UNIXTIME(" + (discord_webhooks_entry.deleted_at > 0 ? std::to_string(discord_webhooks_entry.deleted_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"UPDATE {} SET {} WHERE {} = {}",
|
||||
TableName(),
|
||||
implode(", ", update_values),
|
||||
PrimaryKey(),
|
||||
discord_webhooks_entry.id
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static DiscordWebhooks InsertOne(
|
||||
Database& db,
|
||||
DiscordWebhooks discord_webhooks_entry
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
insert_values.push_back(std::to_string(discord_webhooks_entry.id));
|
||||
insert_values.push_back("'" + EscapeString(discord_webhooks_entry.webhook_name) + "'");
|
||||
insert_values.push_back("'" + EscapeString(discord_webhooks_entry.webhook_url) + "'");
|
||||
insert_values.push_back("FROM_UNIXTIME(" + (discord_webhooks_entry.created_at > 0 ? std::to_string(discord_webhooks_entry.created_at) : "null") + ")");
|
||||
insert_values.push_back("FROM_UNIXTIME(" + (discord_webhooks_entry.deleted_at > 0 ? std::to_string(discord_webhooks_entry.deleted_at) : "null") + ")");
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES ({})",
|
||||
BaseInsert(),
|
||||
implode(",", insert_values)
|
||||
)
|
||||
);
|
||||
|
||||
if (results.Success()) {
|
||||
discord_webhooks_entry.id = results.LastInsertedID();
|
||||
return discord_webhooks_entry;
|
||||
}
|
||||
|
||||
discord_webhooks_entry = NewEntity();
|
||||
|
||||
return discord_webhooks_entry;
|
||||
}
|
||||
|
||||
static int InsertMany(
|
||||
Database& db,
|
||||
std::vector<DiscordWebhooks> discord_webhooks_entries
|
||||
)
|
||||
{
|
||||
std::vector<std::string> insert_chunks;
|
||||
|
||||
for (auto &discord_webhooks_entry: discord_webhooks_entries) {
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
insert_values.push_back(std::to_string(discord_webhooks_entry.id));
|
||||
insert_values.push_back("'" + EscapeString(discord_webhooks_entry.webhook_name) + "'");
|
||||
insert_values.push_back("'" + EscapeString(discord_webhooks_entry.webhook_url) + "'");
|
||||
insert_values.push_back("FROM_UNIXTIME(" + (discord_webhooks_entry.created_at > 0 ? std::to_string(discord_webhooks_entry.created_at) : "null") + ")");
|
||||
insert_values.push_back("FROM_UNIXTIME(" + (discord_webhooks_entry.deleted_at > 0 ? std::to_string(discord_webhooks_entry.deleted_at) : "null") + ")");
|
||||
|
||||
insert_chunks.push_back("(" + implode(",", insert_values) + ")");
|
||||
}
|
||||
|
||||
std::vector<std::string> insert_values;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} VALUES {}",
|
||||
BaseInsert(),
|
||||
implode(",", insert_chunks)
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static std::vector<DiscordWebhooks> All(Database& db)
|
||||
{
|
||||
std::vector<DiscordWebhooks> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{}",
|
||||
BaseSelect()
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
DiscordWebhooks entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.webhook_name = row[1] ? row[1] : "";
|
||||
entry.webhook_url = row[2] ? row[2] : "";
|
||||
entry.created_at = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
|
||||
entry.deleted_at = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static std::vector<DiscordWebhooks> GetWhere(Database& db, std::string where_filter)
|
||||
{
|
||||
std::vector<DiscordWebhooks> all_entries;
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"{} WHERE {}",
|
||||
BaseSelect(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
all_entries.reserve(results.RowCount());
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row) {
|
||||
DiscordWebhooks entry{};
|
||||
|
||||
entry.id = atoi(row[0]);
|
||||
entry.webhook_name = row[1] ? row[1] : "";
|
||||
entry.webhook_url = row[2] ? row[2] : "";
|
||||
entry.created_at = strtoll(row[3] ? row[3] : "-1", nullptr, 10);
|
||||
entry.deleted_at = strtoll(row[4] ? row[4] : "-1", nullptr, 10);
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return all_entries;
|
||||
}
|
||||
|
||||
static int DeleteWhere(Database& db, std::string where_filter)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"DELETE FROM {} WHERE {}",
|
||||
TableName(),
|
||||
where_filter
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
static int Truncate(Database& db)
|
||||
{
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
"TRUNCATE TABLE {}",
|
||||
TableName()
|
||||
)
|
||||
);
|
||||
|
||||
return (results.Success() ? results.RowsAffected() : 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_BASE_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
@ -24,6 +24,8 @@ public:
|
||||
int log_to_console;
|
||||
int log_to_file;
|
||||
int log_to_gmsay;
|
||||
int log_to_discord;
|
||||
int discord_webhook_id;
|
||||
};
|
||||
|
||||
static std::string PrimaryKey()
|
||||
@ -39,6 +41,8 @@ public:
|
||||
"log_to_console",
|
||||
"log_to_file",
|
||||
"log_to_gmsay",
|
||||
"log_to_discord",
|
||||
"discord_webhook_id",
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,6 +54,8 @@ public:
|
||||
"log_to_console",
|
||||
"log_to_file",
|
||||
"log_to_gmsay",
|
||||
"log_to_discord",
|
||||
"discord_webhook_id",
|
||||
};
|
||||
}
|
||||
|
||||
@ -95,6 +101,8 @@ public:
|
||||
entry.log_to_console = 0;
|
||||
entry.log_to_file = 0;
|
||||
entry.log_to_gmsay = 0;
|
||||
entry.log_to_discord = 0;
|
||||
entry.discord_webhook_id = 0;
|
||||
|
||||
return entry;
|
||||
}
|
||||
@ -135,6 +143,8 @@ public:
|
||||
entry.log_to_console = atoi(row[2]);
|
||||
entry.log_to_file = atoi(row[3]);
|
||||
entry.log_to_gmsay = atoi(row[4]);
|
||||
entry.log_to_discord = atoi(row[5]);
|
||||
entry.discord_webhook_id = atoi(row[6]);
|
||||
|
||||
return entry;
|
||||
}
|
||||
@ -173,6 +183,8 @@ public:
|
||||
update_values.push_back(columns[2] + " = " + std::to_string(logsys_categories_entry.log_to_console));
|
||||
update_values.push_back(columns[3] + " = " + std::to_string(logsys_categories_entry.log_to_file));
|
||||
update_values.push_back(columns[4] + " = " + std::to_string(logsys_categories_entry.log_to_gmsay));
|
||||
update_values.push_back(columns[5] + " = " + std::to_string(logsys_categories_entry.log_to_discord));
|
||||
update_values.push_back(columns[6] + " = " + std::to_string(logsys_categories_entry.discord_webhook_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@ -199,6 +211,8 @@ public:
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_console));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_file));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_gmsay));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_discord));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.discord_webhook_id));
|
||||
|
||||
auto results = db.QueryDatabase(
|
||||
fmt::format(
|
||||
@ -233,6 +247,8 @@ public:
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_console));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_file));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_gmsay));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.log_to_discord));
|
||||
insert_values.push_back(std::to_string(logsys_categories_entry.discord_webhook_id));
|
||||
|
||||
insert_chunks.push_back("(" + implode(",", insert_values) + ")");
|
||||
}
|
||||
@ -271,6 +287,8 @@ public:
|
||||
entry.log_to_console = atoi(row[2]);
|
||||
entry.log_to_file = atoi(row[3]);
|
||||
entry.log_to_gmsay = atoi(row[4]);
|
||||
entry.log_to_discord = atoi(row[5]);
|
||||
entry.discord_webhook_id = atoi(row[6]);
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
@ -300,6 +318,8 @@ public:
|
||||
entry.log_to_console = atoi(row[2]);
|
||||
entry.log_to_file = atoi(row[3]);
|
||||
entry.log_to_gmsay = atoi(row[4]);
|
||||
entry.log_to_discord = atoi(row[5]);
|
||||
entry.discord_webhook_id = atoi(row[6]);
|
||||
|
||||
all_entries.push_back(entry);
|
||||
}
|
||||
|
||||
70
common/repositories/discord_webhooks_repository.h
Normal file
70
common/repositories/discord_webhooks_repository.h
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* EQEmulator: Everquest Server Emulator
|
||||
* Copyright (C) 2001-2020 EQEmulator Development Team (https://github.com/EQEmu/Server)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY except by those people which sell it, which
|
||||
* are required to give you total support for your newly bought product;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef EQEMU_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
#define EQEMU_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
|
||||
#include "../database.h"
|
||||
#include "../string_util.h"
|
||||
#include "base/base_discord_webhooks_repository.h"
|
||||
|
||||
class DiscordWebhooksRepository: public BaseDiscordWebhooksRepository {
|
||||
public:
|
||||
|
||||
/**
|
||||
* This file was auto generated and can be modified and extended upon
|
||||
*
|
||||
* Base repository methods are automatically
|
||||
* generated in the "base" version of this repository. The base repository
|
||||
* is immutable and to be left untouched, while methods in this class
|
||||
* are used as extension methods for more specific persistence-layer
|
||||
* accessors or mutators.
|
||||
*
|
||||
* Base Methods (Subject to be expanded upon in time)
|
||||
*
|
||||
* Note: Not all tables are designed appropriately to fit functionality with all base methods
|
||||
*
|
||||
* InsertOne
|
||||
* UpdateOne
|
||||
* DeleteOne
|
||||
* FindOne
|
||||
* GetWhere(std::string where_filter)
|
||||
* DeleteWhere(std::string where_filter)
|
||||
* InsertMany
|
||||
* All
|
||||
*
|
||||
* Example custom methods in a repository
|
||||
*
|
||||
* DiscordWebhooksRepository::GetByZoneAndVersion(int zone_id, int zone_version)
|
||||
* DiscordWebhooksRepository::GetWhereNeverExpires()
|
||||
* DiscordWebhooksRepository::GetWhereXAndY()
|
||||
* DiscordWebhooksRepository::DeleteWhereXAndY()
|
||||
*
|
||||
* Most of the above could be covered by base methods, but if you as a developer
|
||||
* find yourself re-using logic for other parts of the code, its best to just make a
|
||||
* method that can be re-used easily elsewhere especially if it can use a base repository
|
||||
* method and encapsulate filters there
|
||||
*/
|
||||
|
||||
// Custom extended repository methods here
|
||||
|
||||
};
|
||||
|
||||
#endif //EQEMU_DISCORD_WEBHOOKS_REPOSITORY_H
|
||||
@ -219,6 +219,7 @@
|
||||
#define ServerOP_UCSServerStatusReply 0x4005
|
||||
#define ServerOP_UCSServerStatusRequest 0x4006
|
||||
#define ServerOP_UpdateSchedulerEvents 0x4007
|
||||
#define ServerOP_DiscordWebhookMessage 0x4008
|
||||
|
||||
#define ServerOP_ReloadAAData 0x4100
|
||||
#define ServerOP_ReloadAlternateCurrencies 0x4101
|
||||
@ -1449,6 +1450,11 @@ struct QSMerchantLogTransaction_Struct {
|
||||
QSTransactionItems_Struct items[0];
|
||||
};
|
||||
|
||||
struct DiscordWebhookMessage_Struct {
|
||||
uint32 webhook_id;
|
||||
char message[2000];
|
||||
};
|
||||
|
||||
struct QSGeneralQuery_Struct {
|
||||
char QueryString[0];
|
||||
};
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||
*/
|
||||
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9184
|
||||
#define CURRENT_BINARY_DATABASE_VERSION 9185
|
||||
|
||||
#ifdef BOTS
|
||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9028
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.2)
|
||||
|
||||
SET(qserv_sources
|
||||
database.cpp
|
||||
lfguild.cpp
|
||||
queryserv.cpp
|
||||
queryservconfig.cpp
|
||||
worldserver.cpp
|
||||
database.cpp
|
||||
lfguild.cpp
|
||||
queryserv.cpp
|
||||
queryservconfig.cpp
|
||||
worldserver.cpp
|
||||
)
|
||||
|
||||
SET(qserv_headers
|
||||
database.h
|
||||
lfguild.h
|
||||
queryservconfig.h
|
||||
worldserver.h
|
||||
database.h
|
||||
lfguild.h
|
||||
queryservconfig.h
|
||||
worldserver.h
|
||||
)
|
||||
|
||||
ADD_EXECUTABLE(queryserv ${qserv_sources} ${qserv_headers})
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "../common/servertalk.h"
|
||||
#include "../common/platform.h"
|
||||
#include "../common/crash.h"
|
||||
#include "../common/string_util.h"
|
||||
#include "../common/event/event_loop.h"
|
||||
#include "../common/timer.h"
|
||||
#include "database.h"
|
||||
@ -32,21 +33,24 @@
|
||||
#include "worldserver.h"
|
||||
#include <list>
|
||||
#include <signal.h>
|
||||
#include <thread>
|
||||
|
||||
volatile bool RunLoops = true;
|
||||
|
||||
Database database;
|
||||
LFGuildManager lfguildmanager;
|
||||
std::string WorldShortName;
|
||||
Database database;
|
||||
LFGuildManager lfguildmanager;
|
||||
std::string WorldShortName;
|
||||
const queryservconfig *Config;
|
||||
WorldServer *worldserver = 0;
|
||||
EQEmuLogSys LogSys;
|
||||
WorldServer *worldserver = 0;
|
||||
EQEmuLogSys LogSys;
|
||||
|
||||
void CatchSignal(int sig_num) {
|
||||
void CatchSignal(int sig_num)
|
||||
{
|
||||
RunLoops = false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main()
|
||||
{
|
||||
RegisterExecutablePlatform(ExePlatformQueryServ);
|
||||
LogSys.LoadLogSettingsDefaults();
|
||||
set_exception_handler();
|
||||
@ -58,7 +62,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Config = queryservconfig::get();
|
||||
Config = queryservconfig::get();
|
||||
WorldShortName = Config->ShortName;
|
||||
|
||||
LogInfo("Connecting to MySQL");
|
||||
@ -69,7 +73,8 @@ int main() {
|
||||
Config->QSDatabaseUsername.c_str(),
|
||||
Config->QSDatabasePassword.c_str(),
|
||||
Config->QSDatabaseDB.c_str(),
|
||||
Config->QSDatabasePort)) {
|
||||
Config->QSDatabasePort
|
||||
)) {
|
||||
LogInfo("Cannot continue without a database connection");
|
||||
return 1;
|
||||
}
|
||||
@ -78,11 +83,11 @@ int main() {
|
||||
->LoadLogDatabaseSettings()
|
||||
->StartFileLogs();
|
||||
|
||||
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
|
||||
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
|
||||
LogInfo("Could not set signal handler");
|
||||
return 1;
|
||||
}
|
||||
if (signal(SIGTERM, CatchSignal) == SIG_ERR) {
|
||||
if (signal(SIGTERM, CatchSignal) == SIG_ERR) {
|
||||
LogInfo("Could not set signal handler");
|
||||
return 1;
|
||||
}
|
||||
@ -94,10 +99,11 @@ int main() {
|
||||
/* Load Looking For Guild Manager */
|
||||
lfguildmanager.LoadDatabase();
|
||||
|
||||
while(RunLoops) {
|
||||
while (RunLoops) {
|
||||
Timer::SetCurrentTime();
|
||||
if(LFGuildExpireTimer.Check())
|
||||
if (LFGuildExpireTimer.Check()) {
|
||||
lfguildmanager.ExpireEntries();
|
||||
}
|
||||
|
||||
EQ::EventLoop::Get().Process();
|
||||
Sleep(5);
|
||||
@ -105,7 +111,8 @@ int main() {
|
||||
LogSys.CloseFileLogs();
|
||||
}
|
||||
|
||||
void UpdateWindowTitle(char* iNewTitle) {
|
||||
void UpdateWindowTitle(char *iNewTitle)
|
||||
{
|
||||
#ifdef _WINDOWS
|
||||
char tmp[500];
|
||||
if (iNewTitle) {
|
||||
|
||||
@ -37,10 +37,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
extern WorldServer worldserver;
|
||||
extern WorldServer worldserver;
|
||||
extern const queryservconfig *Config;
|
||||
extern Database database;
|
||||
extern LFGuildManager lfguildmanager;
|
||||
extern Database database;
|
||||
extern LFGuildManager lfguildmanager;
|
||||
|
||||
WorldServer::WorldServer()
|
||||
{
|
||||
@ -52,7 +52,13 @@ WorldServer::~WorldServer()
|
||||
|
||||
void WorldServer::Connect()
|
||||
{
|
||||
m_connection = std::make_unique<EQ::Net::ServertalkClient>(Config->WorldIP, Config->WorldTCPPort, false, "QueryServ", Config->SharedKey);
|
||||
m_connection = std::make_unique<EQ::Net::ServertalkClient>(
|
||||
Config->WorldIP,
|
||||
Config->WorldTCPPort,
|
||||
false,
|
||||
"QueryServ",
|
||||
Config->SharedKey
|
||||
);
|
||||
m_connection->OnMessage(std::bind(&WorldServer::HandleMessage, this, std::placeholders::_1, std::placeholders::_2));
|
||||
}
|
||||
|
||||
@ -80,109 +86,109 @@ bool WorldServer::Connected() const
|
||||
void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
{
|
||||
switch (opcode) {
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
case ServerOP_KeepAlive: {
|
||||
break;
|
||||
}
|
||||
case ServerOP_Speech: {
|
||||
Server_Speech_Struct *SSS = (Server_Speech_Struct*)p.Data();
|
||||
std::string tmp1 = SSS->from;
|
||||
std::string tmp2 = SSS->to;
|
||||
database.AddSpeech(tmp1.c_str(), tmp2.c_str(), SSS->message, SSS->minstatus, SSS->guilddbid, SSS->type);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogTrades: {
|
||||
QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct*)p.Data();
|
||||
database.LogPlayerTrade(QS, QS->_detail_count);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerDropItem: {
|
||||
QSPlayerDropItem_Struct *QS = (QSPlayerDropItem_Struct *) p.Data();
|
||||
database.LogPlayerDropItem(QS);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogHandins: {
|
||||
QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct*)p.Data();
|
||||
database.LogPlayerHandin(QS, QS->_detail_count);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogNPCKills: {
|
||||
QSPlayerLogNPCKill_Struct *QS = (QSPlayerLogNPCKill_Struct*)p.Data();
|
||||
uint32 Members = (uint32)(p.Length() - sizeof(QSPlayerLogNPCKill_Struct));
|
||||
if (Members > 0) Members = Members / sizeof(QSPlayerLogNPCKillsPlayers_Struct);
|
||||
database.LogPlayerNPCKill(QS, Members);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogDeletes: {
|
||||
QSPlayerLogDelete_Struct *QS = (QSPlayerLogDelete_Struct*)p.Data();
|
||||
uint32 Items = QS->char_count;
|
||||
database.LogPlayerDelete(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogMoves: {
|
||||
QSPlayerLogMove_Struct *QS = (QSPlayerLogMove_Struct*)p.Data();
|
||||
uint32 Items = QS->char_count;
|
||||
database.LogPlayerMove(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogMerchantTransactions: {
|
||||
QSMerchantLogTransaction_Struct *QS = (QSMerchantLogTransaction_Struct*)p.Data();
|
||||
uint32 Items = QS->char_count + QS->merchant_count;
|
||||
database.LogMerchantTransaction(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QueryServGeneric: {
|
||||
/*
|
||||
The purpose of ServerOP_QueryServerGeneric is so that we don't have to add code to world just to relay packets
|
||||
each time we add functionality to queryserv.
|
||||
case 0: {
|
||||
break;
|
||||
}
|
||||
case ServerOP_KeepAlive: {
|
||||
break;
|
||||
}
|
||||
case ServerOP_Speech: {
|
||||
Server_Speech_Struct *SSS = (Server_Speech_Struct *) p.Data();
|
||||
std::string tmp1 = SSS->from;
|
||||
std::string tmp2 = SSS->to;
|
||||
database.AddSpeech(tmp1.c_str(), tmp2.c_str(), SSS->message, SSS->minstatus, SSS->guilddbid, SSS->type);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogTrades: {
|
||||
QSPlayerLogTrade_Struct *QS = (QSPlayerLogTrade_Struct *) p.Data();
|
||||
database.LogPlayerTrade(QS, QS->_detail_count);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerDropItem: {
|
||||
QSPlayerDropItem_Struct *QS = (QSPlayerDropItem_Struct *) p.Data();
|
||||
database.LogPlayerDropItem(QS);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogHandins: {
|
||||
QSPlayerLogHandin_Struct *QS = (QSPlayerLogHandin_Struct *) p.Data();
|
||||
database.LogPlayerHandin(QS, QS->_detail_count);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogNPCKills: {
|
||||
QSPlayerLogNPCKill_Struct *QS = (QSPlayerLogNPCKill_Struct *) p.Data();
|
||||
uint32 Members = (uint32) (p.Length() - sizeof(QSPlayerLogNPCKill_Struct));
|
||||
if (Members > 0) { Members = Members / sizeof(QSPlayerLogNPCKillsPlayers_Struct); }
|
||||
database.LogPlayerNPCKill(QS, Members);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogDeletes: {
|
||||
QSPlayerLogDelete_Struct *QS = (QSPlayerLogDelete_Struct *) p.Data();
|
||||
uint32 Items = QS->char_count;
|
||||
database.LogPlayerDelete(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogMoves: {
|
||||
QSPlayerLogMove_Struct *QS = (QSPlayerLogMove_Struct *) p.Data();
|
||||
uint32 Items = QS->char_count;
|
||||
database.LogPlayerMove(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSPlayerLogMerchantTransactions: {
|
||||
QSMerchantLogTransaction_Struct *QS = (QSMerchantLogTransaction_Struct *) p.Data();
|
||||
uint32 Items = QS->char_count + QS->merchant_count;
|
||||
database.LogMerchantTransaction(QS, Items);
|
||||
break;
|
||||
}
|
||||
case ServerOP_QueryServGeneric: {
|
||||
/*
|
||||
The purpose of ServerOP_QueryServerGeneric is so that we don't have to add code to world just to relay packets
|
||||
each time we add functionality to queryserv.
|
||||
|
||||
A ServerOP_QueryServGeneric packet has the following format:
|
||||
A ServerOP_QueryServGeneric packet has the following format:
|
||||
|
||||
uint32 SourceZoneID
|
||||
uint32 SourceInstanceID
|
||||
char OriginatingCharacterName[0]
|
||||
- Null terminated name of the character this packet came from. This could be just
|
||||
- an empty string if it has no meaning in the context of a particular packet.
|
||||
uint32 Type
|
||||
uint32 SourceZoneID
|
||||
uint32 SourceInstanceID
|
||||
char OriginatingCharacterName[0]
|
||||
- Null terminated name of the character this packet came from. This could be just
|
||||
- an empty string if it has no meaning in the context of a particular packet.
|
||||
uint32 Type
|
||||
|
||||
The 'Type' field is a 'sub-opcode'. A value of 0 is used for the LFGuild packets. The next feature to be added
|
||||
to queryserv would use 1, etc.
|
||||
The 'Type' field is a 'sub-opcode'. A value of 0 is used for the LFGuild packets. The next feature to be added
|
||||
to queryserv would use 1, etc.
|
||||
|
||||
Obviously, any fields in the packet following the 'Type' will be unique to the particular type of packet. The
|
||||
'Generic' in the name of this ServerOP code relates to the four header fields.
|
||||
*/
|
||||
Obviously, any fields in the packet following the 'Type' will be unique to the particular type of packet. The
|
||||
'Generic' in the name of this ServerOP code relates to the four header fields.
|
||||
*/
|
||||
|
||||
auto from = p.GetCString(8);
|
||||
uint32 Type = p.GetUInt32(8 + from.length() + 1);
|
||||
auto from = p.GetCString(8);
|
||||
uint32 Type = p.GetUInt32(8 + from.length() + 1);
|
||||
|
||||
switch (Type) {
|
||||
case QSG_LFGuild: {
|
||||
switch (Type) {
|
||||
case QSG_LFGuild: {
|
||||
ServerPacket pack;
|
||||
pack.pBuffer = (uchar *) p.Data();
|
||||
pack.opcode = opcode;
|
||||
pack.size = (uint32) p.Length();
|
||||
lfguildmanager.HandlePacket(&pack);
|
||||
pack.pBuffer = nullptr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogInfo("Received unhandled ServerOP_QueryServGeneric", Type);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSSendQuery: {
|
||||
/* Process all packets here */
|
||||
ServerPacket pack;
|
||||
pack.pBuffer = (uchar*)p.Data();
|
||||
pack.opcode = opcode;
|
||||
pack.size = (uint32)p.Length();
|
||||
lfguildmanager.HandlePacket(&pack);
|
||||
pack.pBuffer = (uchar *) p.Data();
|
||||
pack.opcode = opcode;
|
||||
pack.size = (uint32) p.Length();
|
||||
|
||||
database.GeneralQueryReceive(&pack);
|
||||
pack.pBuffer = nullptr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LogInfo("Received unhandled ServerOP_QueryServGeneric", Type);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerOP_QSSendQuery: {
|
||||
/* Process all packets here */
|
||||
ServerPacket pack;
|
||||
pack.pBuffer = (uchar*)p.Data();
|
||||
pack.opcode = opcode;
|
||||
pack.size = (uint32)p.Length();
|
||||
|
||||
database.GeneralQueryReceive(&pack);
|
||||
pack.pBuffer = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,24 +18,25 @@
|
||||
#ifndef WORLDSERVER_H
|
||||
#define WORLDSERVER_H
|
||||
|
||||
#include <mutex>
|
||||
#include "../common/eq_packet_structs.h"
|
||||
#include "../common/net/servertalk_client_connection.h"
|
||||
|
||||
class WorldServer
|
||||
{
|
||||
public:
|
||||
WorldServer();
|
||||
~WorldServer();
|
||||
class WorldServer {
|
||||
public:
|
||||
WorldServer();
|
||||
~WorldServer();
|
||||
|
||||
void Connect();
|
||||
bool SendPacket(ServerPacket* pack);
|
||||
std::string GetIP() const;
|
||||
uint16 GetPort() const;
|
||||
bool Connected() const;
|
||||
void Connect();
|
||||
bool SendPacket(ServerPacket *pack);
|
||||
std::string GetIP() const;
|
||||
uint16 GetPort() const;
|
||||
bool Connected() const;
|
||||
|
||||
void HandleMessage(uint16 opcode, const EQ::Net::Packet &p);
|
||||
private:
|
||||
std::unique_ptr<EQ::Net::ServertalkClient> m_connection;
|
||||
|
||||
void HandleMessage(uint16 opcode, const EQ::Net::Packet &p);
|
||||
private:
|
||||
std::unique_ptr<EQ::Net::ServertalkClient> m_connection;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
10
ucs/ucs.cpp
10
ucs/ucs.cpp
@ -37,12 +37,14 @@
|
||||
|
||||
#include "../common/net/tcp_server.h"
|
||||
#include "../common/net/servertalk_client_connection.h"
|
||||
#include "../common/discord_manager.h"
|
||||
|
||||
ChatChannelList *ChannelList;
|
||||
Clientlist *g_Clientlist;
|
||||
EQEmuLogSys LogSys;
|
||||
Database database;
|
||||
WorldServer *worldserver = nullptr;
|
||||
DiscordManager discord_manager;
|
||||
|
||||
const ucsconfig *Config;
|
||||
|
||||
@ -87,6 +89,12 @@ void CatchSignal(int sig_num) {
|
||||
}
|
||||
}
|
||||
|
||||
void DiscordQueueListener() {
|
||||
while (caught_loop == 0) {
|
||||
discord_manager.ProcessMessageQueue();
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
RegisterExecutablePlatform(ExePlatformUCS);
|
||||
@ -165,6 +173,8 @@ int main() {
|
||||
std::signal(SIGKILL, CatchSignal);
|
||||
std::signal(SIGSEGV, CatchSignal);
|
||||
|
||||
std::thread(DiscordQueueListener).detach();
|
||||
|
||||
worldserver = new WorldServer;
|
||||
|
||||
// uncomment to simulate timed crash for catching SIGSEV
|
||||
|
||||
@ -26,6 +26,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "clientlist.h"
|
||||
#include "ucsconfig.h"
|
||||
#include "database.h"
|
||||
#include "../common/discord_manager.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
@ -35,10 +36,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
extern WorldServer worldserver;
|
||||
extern Clientlist *g_Clientlist;
|
||||
extern WorldServer worldserver;
|
||||
extern Clientlist *g_Clientlist;
|
||||
extern const ucsconfig *Config;
|
||||
extern Database database;
|
||||
extern Database database;
|
||||
extern DiscordManager discord_manager;
|
||||
|
||||
void ProcessMailTo(Client *c, std::string from, std::string subject, std::string message);
|
||||
|
||||
@ -72,6 +74,16 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p)
|
||||
{
|
||||
break;
|
||||
}
|
||||
case ServerOP_DiscordWebhookMessage: {
|
||||
auto *q = (DiscordWebhookMessage_Struct *) p.Data();
|
||||
|
||||
discord_manager.QueueWebhookMessage(
|
||||
q->webhook_id,
|
||||
q->message
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case ServerOP_UCSMessage:
|
||||
{
|
||||
char *Buffer = (char *)pack->pBuffer;
|
||||
|
||||
@ -438,6 +438,7 @@
|
||||
9182|2022_05_02_npc_types_int64.sql|SHOW COLUMNS FROM `npc_types` LIKE 'hp'|missing|bigint
|
||||
9183|2022_05_07_merchant_data_buckets.sql|SHOW COLUMNS FROM `merchantlist` LIKE 'bucket_comparison'|empty
|
||||
9184|2022_05_21_schema_consistency.sql|SELECT * FROM db_version WHERE version >= 9184|empty|
|
||||
9185|2022_05_07_discord_webhooks.sql|SHOW TABLES LIKE 'discord_webhooks'|empty|
|
||||
|
||||
# Upgrade conditions:
|
||||
# This won't be needed after this system is implemented, but it is used database that are not
|
||||
|
||||
15
utils/sql/git/required/2022_05_07_discord_webhooks.sql
Normal file
15
utils/sql/git/required/2022_05_07_discord_webhooks.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE discord_webhooks
|
||||
(
|
||||
id INT auto_increment primary key NULL,
|
||||
webhook_name varchar(100) NULL,
|
||||
webhook_url varchar(255) NULL,
|
||||
created_at DATETIME NULL,
|
||||
deleted_at DATETIME NULL
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_general_ci;
|
||||
|
||||
ALTER TABLE logsys_categories
|
||||
ADD log_to_discord smallint(11) default 0 AFTER log_to_gmsay;
|
||||
ALTER TABLE logsys_categories
|
||||
ADD discord_webhook_id int(11) default 0 AFTER log_to_discord;
|
||||
@ -20,6 +20,7 @@
|
||||
|
||||
#include "world_server_command_handler.h"
|
||||
#include "../common/eqemu_logsys.h"
|
||||
#include "../common/discord/discord.h"
|
||||
#include "../common/json/json.h"
|
||||
#include "../common/version.h"
|
||||
#include "worlddb.h"
|
||||
@ -30,6 +31,7 @@
|
||||
#include "../common/rulesys.h"
|
||||
#include "../common/repositories/instance_list_repository.h"
|
||||
#include "../common/repositories/zone_repository.h"
|
||||
#include "../zone/queryserv.h"
|
||||
|
||||
namespace WorldserverCommandHandler {
|
||||
|
||||
@ -157,14 +159,14 @@ namespace WorldserverCommandHandler {
|
||||
*/
|
||||
void DatabaseGetSchema(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||
{
|
||||
description = "Displays server database schema";
|
||||
description = "Displays server database schema";
|
||||
|
||||
if (cmd[{"-h", "--help"}]) {
|
||||
return;
|
||||
}
|
||||
|
||||
Json::Value player_tables_json;
|
||||
std::vector<std::string> player_tables = DatabaseSchema::GetPlayerTables();
|
||||
std::vector<std::string> player_tables = DatabaseSchema::GetPlayerTables();
|
||||
for (const auto &table: player_tables) {
|
||||
player_tables_json.append(table);
|
||||
}
|
||||
@ -176,19 +178,19 @@ namespace WorldserverCommandHandler {
|
||||
}
|
||||
|
||||
Json::Value server_tables_json;
|
||||
std::vector<std::string> server_tables = DatabaseSchema::GetServerTables();
|
||||
std::vector<std::string> server_tables = DatabaseSchema::GetServerTables();
|
||||
for (const auto &table: server_tables) {
|
||||
server_tables_json.append(table);
|
||||
}
|
||||
|
||||
Json::Value login_tables_json;
|
||||
std::vector<std::string> login_tables = DatabaseSchema::GetLoginTables();
|
||||
std::vector<std::string> login_tables = DatabaseSchema::GetLoginTables();
|
||||
for (const auto &table: login_tables) {
|
||||
login_tables_json.append(table);
|
||||
}
|
||||
|
||||
Json::Value state_tables_json;
|
||||
std::vector<std::string> state_tables = DatabaseSchema::GetStateTables();
|
||||
std::vector<std::string> state_tables = DatabaseSchema::GetStateTables();
|
||||
for (const auto &table: state_tables) {
|
||||
state_tables_json.append(table);
|
||||
}
|
||||
|
||||
@ -1245,6 +1245,7 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) {
|
||||
loginserverlist.SendAccountUpdate(pack);
|
||||
break;
|
||||
}
|
||||
case ServerOP_DiscordWebhookMessage:
|
||||
case ServerOP_UCSMailMessage: {
|
||||
UCSLink.SendPacket(pack);
|
||||
break;
|
||||
|
||||
@ -888,9 +888,8 @@ Json::Value ApiSetLoggingLevel(EQ::Net::WebsocketServerConnection *connection, J
|
||||
void RegisterApiLogEvent(std::unique_ptr<EQ::Net::WebsocketServer> &server)
|
||||
{
|
||||
LogSys.SetConsoleHandler(
|
||||
[&](uint16 debug_level, uint16 log_category, const std::string &msg) {
|
||||
[&](uint16 log_category, const std::string &msg) {
|
||||
Json::Value data;
|
||||
data["debug_level"] = debug_level;
|
||||
data["log_category"] = log_category;
|
||||
data["msg"] = msg;
|
||||
server->DispatchEvent(EQ::Net::SubscriptionEventLog, data, 50);
|
||||
|
||||
@ -2465,7 +2465,7 @@ XS(XS__istaskenabled) {
|
||||
} else {
|
||||
Perl_croak(aTHX_ "Usage: quest::istaskenabled(int task_id)");
|
||||
}
|
||||
|
||||
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -2483,7 +2483,7 @@ XS(XS__istaskactive) {
|
||||
} else {
|
||||
Perl_croak(aTHX_ "Usage: quest::istaskactive(int task_id)");
|
||||
}
|
||||
|
||||
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -2502,7 +2502,7 @@ XS(XS__istaskactivityactive) {
|
||||
} else {
|
||||
Perl_croak(aTHX_ "Usage: quest::istaskactivityactive(int task_id, int activity_id)");
|
||||
}
|
||||
|
||||
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -2818,7 +2818,7 @@ XS(XS__istaskappropriate) {
|
||||
} else {
|
||||
Perl_croak(aTHX_ "Usage: quest::istaskaappropriate(int task_id)");
|
||||
}
|
||||
|
||||
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -3386,7 +3386,7 @@ XS(XS__CheckInstanceByCharID) {
|
||||
|
||||
uint16 instance_id = (int) SvUV(ST(0));
|
||||
uint32 char_id = (int) SvUV(ST(1));
|
||||
RETVAL = quest_manager.CheckInstanceByCharID(instance_id, char_id);
|
||||
RETVAL = quest_manager.CheckInstanceByCharID(instance_id, char_id);
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -3660,7 +3660,7 @@ XS(XS__IsRunning) {
|
||||
bool RETVAL;
|
||||
dXSTARG;
|
||||
|
||||
RETVAL = quest_manager.IsRunning();
|
||||
RETVAL = quest_manager.IsRunning();
|
||||
ST(0) = boolSV(RETVAL);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
@ -8426,20 +8426,34 @@ XS(XS__commify) {
|
||||
}
|
||||
|
||||
XS(XS__checknamefilter);
|
||||
XS(XS__checknamefilter) {
|
||||
XS(XS__checknamefilter)
|
||||
{
|
||||
dXSARGS;
|
||||
if (items != 1) {
|
||||
Perl_croak(aTHX_ "Usage: quest::checknamefilter(string name)");
|
||||
}
|
||||
|
||||
dXSTARG;
|
||||
std::string name = (std::string) SvPV_nolen(ST(0));
|
||||
bool passes = database.CheckNameFilter(name);
|
||||
ST(0) = boolSV(passes);
|
||||
std::string name = (std::string) SvPV_nolen(ST(0));
|
||||
bool passes = database.CheckNameFilter(name);
|
||||
ST(0) = boolSV(passes);
|
||||
sv_2mortal(ST(0));
|
||||
XSRETURN(1);
|
||||
}
|
||||
|
||||
XS(XS__discordsend);
|
||||
XS(XS__discordsend) {
|
||||
dXSARGS;
|
||||
if (items != 2)
|
||||
Perl_croak(aTHX_ "Usage: quest::discordsend(string webhook_name, string message)");
|
||||
{
|
||||
std::string webhook_name = (std::string) SvPV_nolen(ST(0));
|
||||
std::string message = (std::string) SvPV_nolen(ST(1));
|
||||
zone->SendDiscordMessage(webhook_name, message);
|
||||
}
|
||||
XSRETURN_EMPTY;
|
||||
}
|
||||
|
||||
/*
|
||||
This is the callback perl will look for to setup the
|
||||
quest package's XSUBs
|
||||
@ -8704,6 +8718,7 @@ EXTERN_C XS(boot_quest) {
|
||||
newXS(strcpy(buf, "disable_spawn2"), XS__disable_spawn2, file);
|
||||
newXS(strcpy(buf, "disablerecipe"), XS__disablerecipe, file);
|
||||
newXS(strcpy(buf, "disabletask"), XS__disabletask, file);
|
||||
newXS(strcpy(buf, "discordsend"), XS__discordsend, file);
|
||||
newXS(strcpy(buf, "doanim"), XS__doanim, file);
|
||||
newXS(strcpy(buf, "echo"), XS__echo, file);
|
||||
newXS(strcpy(buf, "emote"), XS__emote, file);
|
||||
|
||||
@ -3392,10 +3392,16 @@ std::string lua_commify(std::string number) {
|
||||
return commify(number);
|
||||
}
|
||||
|
||||
bool lua_check_name_filter(std::string name) {
|
||||
bool lua_check_name_filter(std::string name)
|
||||
{
|
||||
return database.CheckNameFilter(name);
|
||||
}
|
||||
|
||||
void lua_discord_send(std::string webhook_name, std::string message)
|
||||
{
|
||||
zone->SendDiscordMessage(webhook_name, message);
|
||||
}
|
||||
|
||||
#define LuaCreateNPCParse(name, c_type, default_value) do { \
|
||||
cur = table[#name]; \
|
||||
if(luabind::type(cur) != LUA_TNIL) { \
|
||||
@ -3850,6 +3856,7 @@ luabind::scope lua_register_general() {
|
||||
luabind::def("get_environmental_damage_name", &lua_get_environmental_damage_name),
|
||||
luabind::def("commify", &lua_commify),
|
||||
luabind::def("check_name_filter", &lua_check_name_filter),
|
||||
luabind::def("discord_send", &lua_discord_send),
|
||||
|
||||
/*
|
||||
Cross Zone
|
||||
|
||||
@ -24,12 +24,14 @@ Copyright (C) 2001-2014 EQEMu Development Team (http://eqemulator.net)
|
||||
|
||||
|
||||
extern WorldServer worldserver;
|
||||
extern QueryServ* QServ;
|
||||
extern QueryServ *QServ;
|
||||
|
||||
QueryServ::QueryServ(){
|
||||
QueryServ::QueryServ()
|
||||
{
|
||||
}
|
||||
|
||||
QueryServ::~QueryServ(){
|
||||
QueryServ::~QueryServ()
|
||||
{
|
||||
}
|
||||
|
||||
void QueryServ::SendQuery(std::string Query)
|
||||
@ -44,7 +46,9 @@ void QueryServ::SendQuery(std::string Query)
|
||||
void QueryServ::PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc)
|
||||
{
|
||||
std::string query = StringFormat(
|
||||
"INSERT INTO `qs_player_events` (event, char_id, event_desc, time) VALUES (%i, %i, '%s', UNIX_TIMESTAMP(now()))",
|
||||
Event_Type, Character_ID, EscapeString(Event_Desc).c_str());
|
||||
"INSERT INTO `qs_player_events` (event, char_id, event_desc, time) VALUES (%i, %i, '%s', UNIX_TIMESTAMP(now()))",
|
||||
Event_Type,
|
||||
Character_ID,
|
||||
EscapeString(Event_Desc).c_str());
|
||||
SendQuery(query);
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ enum PlayerGenericLogEventTypes {
|
||||
Player_Log_Issued_Commands,
|
||||
Player_Log_Money_Transactions,
|
||||
Player_Log_Alternate_Currency_Transactions,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class QueryServ{
|
||||
@ -32,4 +32,4 @@ class QueryServ{
|
||||
void PlayerLogEvent(int Event_Type, int Character_ID, std::string Event_Desc);
|
||||
};
|
||||
|
||||
#endif /* QUERYSERV_ZONE_H */
|
||||
#endif /* QUERYSERV_ZONE_H */
|
||||
|
||||
@ -57,6 +57,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#include "../common/shared_tasks.h"
|
||||
#include "shared_task_zone_messaging.h"
|
||||
#include "dialogue_window.h"
|
||||
#include "queryserv.h"
|
||||
|
||||
extern EntityList entity_list;
|
||||
extern Zone* zone;
|
||||
@ -208,6 +209,8 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
|
||||
else {
|
||||
LogInfo("World assigned Port: [{}] for this zone", sci->port);
|
||||
ZoneConfig::SetZonePort(sci->port);
|
||||
|
||||
LogSys.SetDiscordHandler(&Zone::DiscordWebhookMessageHandler);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -655,7 +655,7 @@ void Zone::LoadNewMerchantData(uint32 merchantid) {
|
||||
|
||||
void Zone::GetMerchantDataForZoneLoad() {
|
||||
LogInfo("Loading Merchant Lists");
|
||||
|
||||
|
||||
auto query = fmt::format(
|
||||
SQL (
|
||||
SELECT
|
||||
@ -1209,6 +1209,11 @@ bool Zone::Init(bool is_static) {
|
||||
//MODDING HOOK FOR ZONE INIT
|
||||
mod_init();
|
||||
|
||||
// logging origination information
|
||||
LogSys.origination_info.zone_short_name = zone->short_name;
|
||||
LogSys.origination_info.zone_long_name = zone->long_name;
|
||||
LogSys.origination_info.instance_id = zone->instanceid;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2776,3 +2781,32 @@ void Zone::SendReloadMessage(std::string reload_type)
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void Zone::SendDiscordMessage(int webhook_id, const std::string& message)
|
||||
{
|
||||
if (worldserver.Connected()) {
|
||||
auto pack = new ServerPacket(ServerOP_DiscordWebhookMessage, sizeof(DiscordWebhookMessage_Struct) + 1);
|
||||
auto *q = (DiscordWebhookMessage_Struct *) pack->pBuffer;
|
||||
|
||||
strn0cpy(q->message, message.c_str(), 2000);
|
||||
q->webhook_id = webhook_id;
|
||||
|
||||
worldserver.SendPacket(pack);
|
||||
safe_delete(pack);
|
||||
}
|
||||
}
|
||||
|
||||
void Zone::SendDiscordMessage(const std::string& webhook_name, const std::string &message)
|
||||
{
|
||||
bool not_found = true;
|
||||
for (auto & w : LogSys.discord_webhooks) {
|
||||
if (w.webhook_name == webhook_name) {
|
||||
SendDiscordMessage(w.id, message + "\n");
|
||||
not_found = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not_found) {
|
||||
LogDiscord("[SendDiscordMessage] Did not find valid webhook by webhook name [{}]", webhook_name);
|
||||
}
|
||||
}
|
||||
|
||||
66
zone/zone.h
66
zone/zone.h
@ -35,6 +35,8 @@
|
||||
#include "aa_ability.h"
|
||||
#include "pathfinder_interface.h"
|
||||
#include "global_loot_manager.h"
|
||||
#include "queryserv.h"
|
||||
#include "../common/discord/discord.h"
|
||||
|
||||
class DynamicZone;
|
||||
|
||||
@ -147,9 +149,10 @@ public:
|
||||
const char *GetSpellBlockedMessage(uint32 spell_id, const glm::vec3 &location);
|
||||
|
||||
EQ::Random random;
|
||||
EQTime zone_time;
|
||||
EQTime zone_time;
|
||||
|
||||
ZonePoint *GetClosestZonePoint(const glm::vec3 &location, const char *to_name, Client *client, float max_distance = 40000.0f);
|
||||
ZonePoint *
|
||||
GetClosestZonePoint(const glm::vec3 &location, const char *to_name, Client *client, float max_distance = 40000.0f);
|
||||
|
||||
inline bool BuffTimersSuspended() const { return newzone_data.SuspendBuffs != 0; };
|
||||
inline bool HasMap() { return zonemap != nullptr; }
|
||||
@ -179,7 +182,7 @@ public:
|
||||
void DumpMerchantList(uint32 npcid);
|
||||
int SaveTempItem(uint32 merchantid, uint32 npcid, uint32 item, int32 charges, bool sold = false);
|
||||
int32 MobsAggroCount() { return aggroedmobs; }
|
||||
DynamicZone* GetDynamicZone();
|
||||
DynamicZone *GetDynamicZone();
|
||||
|
||||
IPathfinder *pathing;
|
||||
LinkedList<NPC_Emote_Struct *> NPCEmoteList;
|
||||
@ -187,31 +190,31 @@ public:
|
||||
LinkedList<ZonePoint *> zone_point_list;
|
||||
std::vector<ZonePointsRepository::ZonePoints> virtual_zone_point_list;
|
||||
|
||||
Map *zonemap;
|
||||
Map *zonemap;
|
||||
MercTemplate *GetMercTemplate(uint32 template_id);
|
||||
NewZone_Struct newzone_data;
|
||||
NewZone_Struct newzone_data;
|
||||
QGlobalCache *CreateQGlobals()
|
||||
{
|
||||
qGlobals = new QGlobalCache();
|
||||
return qGlobals;
|
||||
}
|
||||
QGlobalCache *GetQGlobals() { return qGlobals; }
|
||||
SpawnConditionManager spawn_conditions;
|
||||
SpawnGroupList spawn_group_list;
|
||||
SpawnConditionManager spawn_conditions;
|
||||
SpawnGroupList spawn_group_list;
|
||||
|
||||
std::list<AltCurrencyDefinition_Struct> AlternateCurrencies;
|
||||
std::list<InternalVeteranReward> VeteranRewards;
|
||||
std::map<uint32, LDoNTrapTemplate *> ldon_trap_list;
|
||||
std::map<uint32, MercTemplate> merc_templates;
|
||||
std::map<uint32, NPCType *> merctable;
|
||||
std::map<uint32, NPCType *> npctable;
|
||||
std::map<uint32, std::list<LDoNTrapTemplate *> > ldon_trap_entry_list;
|
||||
std::map<uint32, std::list<MerchantList> > merchanttable;
|
||||
std::map<uint32, std::list<MercSpellEntry> > merc_spells_list;
|
||||
std::map<uint32, std::list<MercStanceInfo> > merc_stance_list;
|
||||
std::map<uint32, std::list<TempMerchantList> > tmpmerchanttable;
|
||||
std::map<uint32, std::string> adventure_entry_list_flavor;
|
||||
std::map<uint32, ZoneEXPModInfo> level_exp_mod;
|
||||
std::list<AltCurrencyDefinition_Struct> AlternateCurrencies;
|
||||
std::list<InternalVeteranReward> VeteranRewards;
|
||||
std::map<uint32, LDoNTrapTemplate *> ldon_trap_list;
|
||||
std::map<uint32, MercTemplate> merc_templates;
|
||||
std::map<uint32, NPCType *> merctable;
|
||||
std::map<uint32, NPCType *> npctable;
|
||||
std::map<uint32, std::list<LDoNTrapTemplate *> > ldon_trap_entry_list;
|
||||
std::map<uint32, std::list<MerchantList> > merchanttable;
|
||||
std::map<uint32, std::list<MercSpellEntry> > merc_spells_list;
|
||||
std::map<uint32, std::list<MercStanceInfo> > merc_stance_list;
|
||||
std::map<uint32, std::list<TempMerchantList> > tmpmerchanttable;
|
||||
std::map<uint32, std::string> adventure_entry_list_flavor;
|
||||
std::map<uint32, ZoneEXPModInfo> level_exp_mod;
|
||||
|
||||
std::pair<AA::Ability *, AA::Rank *> GetAlternateAdvancementAbilityAndRank(int id, int points_spent);
|
||||
|
||||
@ -223,7 +226,7 @@ public:
|
||||
std::vector<GridEntriesRepository::GridEntry> zone_grid_entries;
|
||||
|
||||
std::unordered_map<uint32, std::unique_ptr<DynamicZone>> dynamic_zone_cache;
|
||||
std::unordered_map<uint32, std::unique_ptr<Expedition>> expedition_cache;
|
||||
std::unordered_map<uint32, std::unique_ptr<Expedition>> expedition_cache;
|
||||
|
||||
time_t weather_timer;
|
||||
Timer spawn2_timer;
|
||||
@ -344,10 +347,11 @@ public:
|
||||
fmt::format(
|
||||
"--- {}",
|
||||
message_split[iter]
|
||||
).c_str()
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
entity_list.MessageStatus(
|
||||
0,
|
||||
AccountStatus::QuestTroupe,
|
||||
@ -357,6 +361,22 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static void SendDiscordMessage(int webhook_id, const std::string& message);
|
||||
static void SendDiscordMessage(const std::string& webhook_name, const std::string& message);
|
||||
static void DiscordWebhookMessageHandler(uint16 log_category, int webhook_id, const std::string &message)
|
||||
{
|
||||
std::string message_prefix;
|
||||
if (!LogSys.origination_info.zone_short_name.empty()) {
|
||||
message_prefix = fmt::format(
|
||||
"[**{}**] **Zone** [**{}**] ",
|
||||
Logs::LogCategoryName[log_category],
|
||||
LogSys.origination_info.zone_short_name
|
||||
);
|
||||
}
|
||||
|
||||
SendDiscordMessage(webhook_id, message_prefix + Discord::FormatDiscordMessage(log_category, message));
|
||||
};
|
||||
|
||||
double GetMaxMovementUpdateRange() const { return max_movement_update_range; }
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user