Skip to content

Instantly share code, notes, and snippets.

@vstumpf
Last active November 2, 2023 06:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vstumpf/8fa4ab356be882e538f1794c2e39fb34 to your computer and use it in GitHub Desktop.
Save vstumpf/8fa4ab356be882e538f1794c2e39fb34 to your computer and use it in GitHub Desktop.
The diff for trog-discord-bot for rathena servers.

This is version 1.3, updated on November 1, 2023

diff --git a/conf/discord_athena.conf b/conf/discord_athena.conf
new file mode 100644
index 000000000..a90adb5d5
--- /dev/null
+++ b/conf/discord_athena.conf
@@ -0,0 +1,30 @@
+//--------------------------------------------------------------
+// Troglodytes Discord-Server Configuration File
+//--------------------------------------------------------------
+
+// The username and token given by the bot.
+//server_id:
+//token:
+
+// Troglodytes-Discord Server IP
+// This is the IP to connect to for the Troglodytes discord integration
+discord_ip: troglodytes.net
+
+// Troglodytes-Discord Server Port
+discord_port: 7851
+
+// The discord channels and RO channels to link
+// can go up to 5 channels
+// discord_channelX is the ID of the channel
+// ro_channelX must match the name in channels.conf
+
+
+//discord_channel0: 467936171763433475
+//ro_channel0: #main
+
+//discord_channel1: 467936182169370624
+//ro_channel1: #trade
+
+//discord_request_channel: 467936182169370632
+
+import: conf/import/discord_conf.txt
diff --git a/conf/import-tmpl/discord_conf.txt b/conf/import-tmpl/discord_conf.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/common/socket.cpp b/src/common/socket.cpp
index be321db81..cdfbdfa24 100644
--- a/src/common/socket.cpp
+++ b/src/common/socket.cpp
@@ -96,6 +96,9 @@ int sock2fd(SOCKET s)
return fd;
}
+SOCKET fd2sock_ext(int fd) {
+ return fd2sock(fd);
+}
/// Inserts the socket into the global array of sockets.
/// Returns a new fd associated with the socket.
@@ -597,7 +600,7 @@ int make_listen_bind(uint32 ip, uint16 port)
return fd;
}
-int make_connection(uint32 ip, uint16 port, bool silent,int timeout) {
+int make_connection(uint32 ip, uint16 port, bool silent,int timeout, bool nonblocking) {
struct sockaddr_in remote_address;
int fd;
int result;
@@ -687,8 +690,13 @@ int make_connection(uint32 ip, uint16 port, bool silent,int timeout) {
}
// Keep the socket in non-blocking mode, since we would set it to non-blocking here on unix. [Lemongrass]
#else
+ if (nonblocking)
+ set_nonblocking(fd, 1);
result = sConnect(fd, (struct sockaddr *)(&remote_address), sizeof(struct sockaddr_in));
if( result == SOCKET_ERROR ) {
+ if (nonblocking && errno == EINPROGRESS) {
+ return fd;
+ }
if( !silent )
ShowError("make_connection: connect failed (socket #%d, %s)!\n", fd, error_msg());
do_close(fd);
@@ -722,6 +730,33 @@ int make_connection(uint32 ip, uint16 port, bool silent,int timeout) {
return fd;
}
+/**
+ * For use with the non-blocking make_connection, add the fd to readfds
+ */
+int add_readfd(int fd, uint32 ip) {
+#ifndef SOCKET_EPOLL
+ // Select Based Event Dispatcher
+ sFD_SET(fd,&readfds);
+#else
+ // Epoll based Event Dispatcher
+ epevent.data.fd = fd;
+ epevent.events = EPOLLIN;
+
+ if( epoll_ctl( epfd, EPOLL_CTL_ADD, fd, &epevent ) == SOCKET_ERROR ){
+ ShowError( "make_connection: failed to add socket #%d to epoll event dispatcher: %s\n", fd, error_msg() );
+ sClose(fd);
+ return -1;
+ }
+#endif
+
+ if (fd_max <= fd) fd_max = fd + 1;
+
+ create_session(fd, recv_to_fifo, send_from_fifo, default_func_parse);
+ session[fd]->client_addr = ip;
+
+ return fd;
+}
+
static int create_session(int fd, RecvFunc func_recv, SendFunc func_send, ParseFunc func_parse)
{
CREATE(session[fd], struct socket_data, 1);
@@ -1439,7 +1474,8 @@ void do_close(int fd)
#ifndef SOCKET_EPOLL
// Select based Event Dispatcher
- sFD_CLR(fd, &readfds);// this needs to be done before closing the socket
+ if (sFD_ISSET(fd, &readfds))
+ sFD_CLR(fd, &readfds);// this needs to be done before closing the socket
#else
// Epoll based Event Dispatcher
epevent.data.fd = fd;
diff --git a/src/common/socket.hpp b/src/common/socket.hpp
index e96d723fa..61dc24c28 100644
--- a/src/common/socket.hpp
+++ b/src/common/socket.hpp
@@ -126,7 +126,8 @@ extern bool session_isActive(int fd);
// Function prototype declaration
int make_listen_bind(uint32 ip, uint16 port);
-int make_connection(uint32 ip, uint16 port, bool silent, int timeout);
+int make_connection(uint32 ip, uint16 port, bool silent, int timeout, bool nonblocking=false);
+int add_readfd(int fd, uint32 ip);
int realloc_fifo(int fd, unsigned int rfifo_size, unsigned int wfifo_size);
int realloc_writefifo(int fd, size_t addition);
int WFIFOSET(int fd, size_t len);
@@ -192,4 +193,9 @@ void send_shortlist_add_fd(int fd);
void send_shortlist_do_sends();
#endif
+// Only for windows
+#ifdef WIN32
+SOCKET fd2sock_ext(int fd);
+#endif
+
#endif /* SOCKET_HPP */
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index 79e299d4e..85b615d2d 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -31,6 +31,7 @@
#include "chrif.hpp"
#include "clan.hpp"
#include "clif.hpp"
+#include "disif.hpp"
#include "duel.hpp"
#include "elemental.hpp"
#include "guild.hpp"
@@ -4318,10 +4319,21 @@ ACMD_FUNC(reload) {
} else if (strstr(command, "attendancedb") || strncmp(message, "attendancedb", 4) == 0) {
attendance_db.reload();
clif_displaymessage(fd, msg_txt(sd, 795)); // Attendance database has been reloaded.
+ } else if (strstr(command, "discordconf") || strncmp(message, "discordconf", 4) == 0) {
+ reload_disif();
}
return 0;
}
+
+/**
+ * disable discord
+*/
+ACMD_FUNC(disablediscord) {
+ stop_disif();
+ return 0;
+}
+
/*==========================================
* @partysharelvl <share_range> [Akinari]
* Updates char server party share level range in runtime
@@ -9140,6 +9152,7 @@ ACMD_FUNC(request)
intif_wis_message_to_gm(sd->status.name, PC_PERM_RECEIVE_REQUESTS, atcmd_output);
clif_messagecolor(&sd->bl, color_table[COLOR_LIGHT_GREEN], atcmd_output, false, SELF);
clif_displaymessage(sd->fd,msg_txt(sd,279)); // @request sent.
+ disif_send_request_to_disc(*sd, message);
return 0;
}
@@ -10954,6 +10967,8 @@ void atcommand_basecommands(void) {
ACMD_DEF2("reloadinstancedb", reload),
ACMD_DEF2("reloadachievementdb",reload),
ACMD_DEF2("reloadattendancedb",reload),
+ ACMD_DEF2("reloaddiscordconf", reload),
+ ACMD_DEF(disablediscord),
ACMD_DEF(partysharelvl),
ACMD_DEF(mapinfo),
ACMD_DEF(dye),
diff --git a/src/map/channel.cpp b/src/map/channel.cpp
index f1cd24101..f1b3c5891 100644
--- a/src/map/channel.cpp
+++ b/src/map/channel.cpp
@@ -16,6 +16,7 @@
#include "battle.hpp"
#include "clif.hpp" //clif_chsys_msg
+#include "disif.hpp"
#include "guild.hpp"
#include "map.hpp" //msg_conf
#include "pc.hpp"
@@ -455,6 +456,10 @@ int channel_send(struct Channel *channel, struct map_session_data *sd, const cha
color = channel_config.colors[sd->fontcolor];
safesnprintf(output, CHAT_SIZE_MAX, "%s %s : %s", channel->alias, sd->status.name, msg);
clif_channel_msg(channel,output,color);
+ if (channel->discord_id) {
+ safesnprintf(output, CHAT_SIZE_MAX, "%s : %s", sd->status.name, msg);
+ disif_send_message_to_disc(channel, output);
+ }
sd->channel_tick[idx] = gettick();
}
return 0;
diff --git a/src/map/channel.hpp b/src/map/channel.hpp
index 19b8605f2..39a6ed2f9 100644
--- a/src/map/channel.hpp
+++ b/src/map/channel.hpp
@@ -53,6 +53,7 @@ struct Channel {
DBMap *banned; ///< List of banned chars -> char_id
unsigned short group_count; ///< Number of group id
unsigned short *groups; ///< List of group id, only these groups can join the channel
+ uint64 discord_id; // Discord channel to forward to
};
struct chan_banentry {
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 768f479bd..228301bb5 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -34,6 +34,7 @@
#include "chrif.hpp"
#include "clan.hpp"
#include "clif.hpp"
+#include "disif.hpp"
#include "elemental.hpp"
#include "guild.hpp"
#include "homunculus.hpp"
@@ -11898,6 +11899,10 @@ void clif_parse_WisMessage(int fd, struct map_session_data* sd)
}
return;
}
+ } else if (strcmpi(target, "@discord") == 0) {
+ disif_discord_wis(*sd, target, message);
+ clif_wis_end(fd, 0); // 0: success to send whisper
+ return;
}
// searching destination character
diff --git a/src/map/disif.cpp b/src/map/disif.cpp
new file mode 100644
index 000000000..aa5791420
--- /dev/null
+++ b/src/map/disif.cpp
@@ -0,0 +1,858 @@
+#include "disif.hpp"
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cstdarg>
+#include <ctime>
+#include <regex>
+
+#include "../common/cbasetypes.hpp"
+#include "../common/socket.hpp"
+#include "../common/timer.hpp"
+#include "../common/malloc.hpp"
+#include "../common/showmsg.hpp"
+#include "../common/strlib.hpp"
+#include "../common/utils.hpp"
+#include "../common/conf.hpp"
+
+#include "clif.hpp"
+#include "channel.hpp"
+#include "pc.hpp"
+
+struct mmo_dis_server discord{};
+
+static TIMER_FUNC(check_connect_discord_server);
+static TIMER_FUNC(check_accept_discord_server);
+
+static std::string parse_item_link(const std::string &msg);
+
+// Received packet Lengths from discord-server
+int dis_recv_packet_length[] = {
+ 0, 43, 3, -1, -1, -1, -1, 2, 2, -1, -1, 10, 4, 0, 0, 0, //0D00
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0D10
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0D20
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 //0D30
+};
+
+// says whether the discord server is connected or not
+int disif_isconnected(void) {
+ return (session_isValid(discord.fd) && discord.state == DiscordState::connected);
+}
+
+
+/**
+ * Map-serv request to login into discord-server
+ * @param fd : discord-server fd to log into
+ * @return 0:request sent
+ * 0D01 <server_id>.Q <token>.24B (DZ_CONNECT)
+ */
+int disif_connect(int fd) {
+ ShowStatus("Logging in to discord server...\n");
+ WFIFOHEAD(fd, 50);
+ WFIFOW(fd,0) = 0xd01;
+ WFIFOQ(fd,2) = discord.server_id;
+ memcpy(WFIFOP(fd,10), discord.token, TOKEN_LENGTH);
+ WFIFOSET(fd, 43);
+
+ return 0;
+}
+
+/**
+* Parse discord server login attempt ack
+* @param fd : file descriptor to parse, (link to discord)
+* 0D02 <error code>.B
+*/
+int disif_parse_loginack(int fd) {
+ if (RFIFOREST(fd) < 3)
+ return 0;
+ char error = RFIFOB(fd, 2);
+ RFIFOSKIP(fd, 3);
+ if (error) {
+ ShowError("Discord server rejected connection, contact nitrous for the reason\n");
+ discord.state = DiscordState::stopped;
+ return 1;
+ }
+
+ ShowInfo("Discord server has connected\n");
+ discord.connect_seconds = 10;
+ disif_send_conf();
+ return 0;
+}
+
+/**
+ * Send keepalive to discord server
+*/
+void disif_keepalive(int fd) {
+ WFIFOHEAD(fd,2);
+ WFIFOW(fd,0) = 0xd07;
+ WFIFOSET(fd,2);
+}
+
+
+/**
+ * Parse keepalive ack from discord server
+*/
+int disif_parse_keepaliveack(int fd) {
+ session[fd]->flag.ping = 0;
+ return 1;
+}
+
+
+/**
+* Parse discord server message and send to chat channel
+* @param fd : file descriptor to parse
+* 0D03 <packet len>.W <channel id>.Q <user name>.24B <message>.?B
+*/
+
+int disif_parse_message_from_disc(int fd) {
+ int len;
+ struct Channel * channel = nullptr;
+ char username[NAME_LENGTH];
+ char msg[CHAT_SIZE_MAX];
+ char output[CHAT_SIZE_MAX];
+
+ if (RFIFOREST(fd) < 4)
+ return 0;
+
+ len = RFIFOW(fd, 2);
+
+ if (RFIFOREST(fd) < len)
+ return 0;
+
+ auto channel_id = RFIFOQ(fd, 4);
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ auto &chn = discord.channels[i];
+ if (chn.disc_channel_id == channel_id) {
+ channel = chn.channel;
+ }
+ }
+
+ if (channel == nullptr) {
+ ShowWarning("Discord server sending to non-existing channel %llu, REPORT THIS!\n", channel_id);
+ return 1;
+ }
+
+ safestrncpy(username, RFIFOCP(fd, 12), NAME_LENGTH);
+ safestrncpy(msg, RFIFOCP(fd, 36), CHAT_SIZE_MAX - 4 - strlen(channel->alias) - strlen(username));
+
+ safesnprintf(output, CHAT_SIZE_MAX, "%s %s : %s", channel->alias, username, msg);
+ clif_channel_msg(channel, output, channel->color);
+
+ return 1;
+}
+
+
+/**
+* Send channel message to discord server
+* @param channel : channel that sent the message
+* @param msg : message that was sent
+* 0D04 <packet len>.W <channel id>.Q <message>.?B
+*/
+int disif_send_message_to_disc(struct Channel *channel, char *msg) {
+ unsigned short msg_len = 0, len = 0;
+
+ if (!channel || !msg || discord.fd == -1 || discord.state != DiscordState::connected)
+ return 0;
+
+ auto newmsg = parse_item_link(msg);
+
+ msg_len = (unsigned short)(newmsg.length() + 1);
+
+ return disif_send_message_tochan(channel->discord_id, newmsg.c_str(), msg_len);
+}
+
+/**
+ * Send a message to discord to the request category/channel from a player
+ * Either 0D04 packet or 0D09
+ */
+int disif_send_request_to_disc(const map_session_data& sd, const char * message) {
+ if (!message || discord.fd == -1 || discord.state != DiscordState::connected)
+ return 0;
+
+ char output[CHAT_SIZE_MAX + NAME_LENGTH + 3];
+ auto msg_len = safesnprintf(output, sizeof(output), "%s : %s", sd.status.name, message) + 1;
+ if (!discord.request_category_id) {
+ // we're not premium, use normal message
+ return disif_send_message_tochan(discord.request_channel_id, output, msg_len);
+ }
+
+ WFIFOHEAD(discord.fd, 8 + msg_len);
+ WFIFOW(discord.fd, 0) = 0xD09;
+ WFIFOW(discord.fd, 2) = 8 + msg_len;
+ WFIFOL(discord.fd, 4) = sd.status.account_id;
+ safestrncpy(WFIFOCP(discord.fd, 8), output, msg_len);
+ WFIFOSET(discord.fd, 8 + msg_len);
+ return 1;
+}
+
+/**
+ * Send a whisper to the user from a discord channel
+ * 0d0a <packet len>.W <account id>.L <message>.?B
+*/
+int disif_parse_send_message_toplayer(int fd) {
+ if (RFIFOREST(fd) < 4)
+ return 0;
+
+ int len = RFIFOW(fd, 2);
+
+ if (RFIFOREST(fd) < len)
+ return 0;
+
+ int accid = RFIFOL(fd, 4);
+ char *msg = RFIFOCP(fd, 8);
+
+ map_session_data *sd = map_id2sd(accid);
+ if (!sd) {
+ return 1;
+ }
+
+ clif_wis_message(sd, "@discord", msg, strlen(msg) + 1, 99);
+ return 1;
+}
+
+int disif_send_message_tochan(uint64 cid, const char *msg, uint16 len) {
+ if (discord.fd == -1 || discord.state != DiscordState::connected)
+ return 0;
+
+ WFIFOHEAD(discord.fd, len + 12);
+ WFIFOW(discord.fd, 0) = 0xD04;
+ WFIFOW(discord.fd, 2) = len + 12;
+ WFIFOQ(discord.fd, 4) = cid;
+ safestrncpy(WFIFOCP(discord.fd, 12), msg, len);
+ WFIFOSET(discord.fd, len + 12);
+ return 0;
+}
+
+
+/**
+ * Send the channels to listen to
+ * 0D05 <packet len>.W <count>.W {<channel id>.Q}*count
+*/
+int disif_send_conf() {
+ if (discord.fd == -1 || discord.state != DiscordState::connected)
+ return 0;
+
+ uint16 count = 0;
+
+ WFIFOHEAD(discord.fd, 6 + (MAX_CHANNELS + 1) * 8);
+ WFIFOW(discord.fd, 0) = 0xD05;
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ auto &chn = discord.channels[i];
+ if (chn.disc_channel_id && chn.channel) {
+ WFIFOQ(discord.fd, 6 + count * 8) = chn.disc_channel_id;
+ count++;
+ }
+ }
+
+ if (discord.request_channel_id) {
+ WFIFOQ(discord.fd, 6 + count * 8) = discord.request_channel_id;
+ count++;
+ }
+
+ WFIFOW(discord.fd, 2) = 6 + count * 8;
+ WFIFOW(discord.fd, 4) = count;
+ WFIFOSET(discord.fd, 6 + count * 8);
+ return 1;
+}
+
+/**
+ * List of channels that have errors
+ * If count is 0, no errors!
+ * 0D06 <packet len>.W <count>.W {<channel id>.Q}*count
+*/
+int disif_parse_conf_ack(int fd) {
+ if (RFIFOREST(fd) < 4)
+ return 0;
+
+ int len = RFIFOW(fd, 2);
+
+ if (RFIFOREST(fd) < len)
+ return 0;
+
+ int count = RFIFOW(fd, 4);
+ for (int i = 0; i < count; i++) {
+ auto id = RFIFOQ(fd, 6 + i * 8);
+ for (int j = 0; j < MAX_CHANNELS; j++) {
+ auto &chn = discord.channels[j];
+ if (id == chn.disc_channel_id) {
+ ShowError("Discord channel with id [%llu](%s) does not exist, ignoring\n", id, chn.channel->name);
+ chn.disc_channel_id = 0;
+ chn.channel->discord_id = 0;
+ chn.channel = nullptr;
+ }
+ }
+ }
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ auto & chn = discord.channels[i];
+ if (!chn.disc_channel_id || !chn.channel)
+ continue;
+ chn.channel->discord_id = chn.disc_channel_id;
+ }
+
+ if (discord.request_category_id) {
+ disif_send_request_category(discord.request_category_id);
+ }
+ return 1;
+}
+
+/**
+ * Send the request category to discord
+ * 0D0B <packet len>.W <category id>.Q
+*/
+int disif_send_request_category(uint64 cid) {
+ if (discord.fd == -1 || discord.state != DiscordState::connected)
+ return 0;
+
+ WFIFOHEAD(discord.fd, 10);
+ WFIFOW(discord.fd, 0) = 0xD0B;
+ WFIFOQ(discord.fd, 2) = cid;
+ WFIFOSET(discord.fd, 10);
+ return 1;
+}
+
+/**
+ * Parse the request category ack
+ * 0 - ok
+ * 1 - chn doesn't exist
+ * 2 - chn not in guild
+ * 3 - chn not a category
+ * 4 - we don't have manage channel permissions
+ * 5 - you aint premium
+
+ * 0D0C <error code>.W
+*/
+int disif_parse_request_category_ack(int fd) {
+ if (RFIFOREST(fd) < 4)
+ return 0;
+ uint16 err = RFIFOW(fd, 2);
+ switch(err) {
+ case 0:
+ ShowInfo("Discord request category set\n");
+ return 1;
+ case 1:
+ ShowWarning("Discord request category does not exist\n");
+ break;
+ case 2:
+ ShowWarning("Discord request category not in guild\n");
+ break;
+ case 3:
+ ShowWarning("Discord request category is not a category\n");
+ break;
+ case 4:
+ ShowWarning("Discord request category we don't have manage channel permissions\n");
+ break;
+ case 5:
+ ShowWarning("Discord request category is a premium feature\n");
+ break;
+ default:
+ ShowWarning("Unknown Error for Discord Request Category, please report %hu\n", err);
+ break;
+ }
+ // if we got here, there must have been an error, so unset it
+ discord.request_category_id = 0;
+ return 1;
+}
+
+// sets map-server's username for discord
+void disif_setserverid(char *id) {
+ discord.server_id = strtoull(id, nullptr, 10);
+}
+
+// sets map-server's token for discord
+void disif_settoken(char *pwd) {
+ memcpy(discord.token, pwd, TOKEN_LENGTH);
+}
+
+
+/** Returns the length of the next complete packet to process,
+* or 0 if no complete packet exists in the queue.
+*
+* @param length The minimum allowed length, or -1 for dynamic lookup
+*/
+int dis_check_length(int fd, int length)
+{
+ if (length == -1)
+ {// variable-length packet
+ if (RFIFOREST(fd) < 4)
+ return 0;
+ length = RFIFOW(fd, 2);
+ }
+
+ if ((int)RFIFOREST(fd) < length)
+ return 0;
+
+ return length;
+}
+const char base62_dictionary[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
+
+uint32 base62_decode(const std::string &msg) {
+ uint32 result = 0;
+ for (size_t i = 0; i < msg.length(); i++) {
+ for (size_t j = 0; j < 62; j++) {
+ if (msg[i] == base62_dictionary[j]) {
+ result = result * 62 + j;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/**
+ * Parse an item link string
+ */
+static std::string parse_item_link(const std::string &msg) {
+#if PACKETVER >= 20160113
+ const std::string start_tag = R"(<ITEML>)";
+ const std::string closing_tag = R"(</ITEML>)";
+#else // PACKETVER >= 20151104
+ const std::string start_tag = "<ITEM>";
+ const std::string closing_tag = "</ITEM>";
+#endif
+
+ static std::regex item_regex(start_tag + R"!(((\w{5})(\d)(\w+)[^<]*))!" + closing_tag);
+
+ std::smatch match;
+ std::string retstr = msg;
+ while (std::regex_search(retstr, match, item_regex)) {
+ auto itemdb = item_db.find(base62_decode(match[4].str()));
+ if (!itemdb) {
+ ShowError("Tried to parse itemlink for unknown item %s.\n", match[4].str().c_str());
+ return msg;
+ }
+
+ retstr = std::regex_replace(retstr, item_regex, "!<!<" + match[1].str() + ">!>![" + itemdb->name + "]",
+ std::regex_constants::format_first_only);
+ }
+ return retstr;
+}
+
+/**
+
+/**
+* Entry point from discord server to map-server.
+* Function that checks incoming command, then splits it to the correct handler.
+* If not found any hander here transmis packet to inter
+* @param fd: file descriptor to parse, (link to discord server)
+* @return 0=invalid server,marked for disconnection,unknow packet; 1=success
+*/
+int disif_parse(int fd) {
+ if (discord.fd != fd) {
+ ShowDebug("disif_parse: Disconnecting invalid session #%d (is not a discord-server)\n", fd);
+ do_close(fd);
+ return 0;
+ }
+ if (session[fd]->flag.eof)
+ {
+ do_close(fd);
+ discord.fd = -1;
+ disif_on_disconnect();
+ return 0;
+ } else if (session[fd]->flag.ping) {
+ if (DIFF_TICK(last_tick, session[fd]->rdata_tick) > (stall_time * 2)) {
+ set_eof(fd);
+ return 0;
+ } else if (session[fd]->flag.ping != 2) {
+ disif_keepalive(fd);
+ session[fd]->flag.ping = 2;
+ }
+ }
+
+ if (RFIFOREST(fd) < 2)
+ return 0;
+
+ int cmd;
+ int len = 0;
+ cmd = RFIFOW(fd, 0);
+ // Check is valid packet entry
+ if (cmd < 0x0D00 || cmd >= 0x0D00 + ARRAYLENGTH(dis_recv_packet_length) || dis_recv_packet_length[cmd - 0x0D00] == 0) {
+ //invalid cmd, just close it
+ ShowError("Unknown packet 0x%04x from discord server, disconnecting.\n", RFIFOW(fd, 0));
+ set_eof(fd);
+ return 0;
+ }
+
+ while (RFIFOREST(fd) >= 2) {
+ int next = 1;
+
+ // Check packet length
+ if ((len = dis_check_length(fd, dis_recv_packet_length[cmd - 0x0D00])) == 0) {
+ //invalid cmd, just close it
+ ShowError("Unknown packet 0x%04x from discord server, disconnecting.\n", RFIFOW(fd, 0));
+ set_eof(fd);
+ return 0;
+ }
+
+ if (len == -1) { // variable-length packet
+ if (RFIFOREST(fd) < 4)
+ return 0;
+
+ len = RFIFOW(fd, 2);
+ if (len < 4 || len > 32768) {
+ ShowWarning("disif_parse: Received packet 0x%04x specifies invalid packet_len (%d), disconnecting discord server #%d.\n", cmd, len, fd);
+#ifdef DUMP_INVALID_PACKET
+ ShowDump(RFIFOP(fd, 0), RFIFOREST(fd));
+#endif
+ set_eof(fd);
+ return 0;
+ }
+ }
+ if ((int)RFIFOREST(fd) < len)
+ return 0; // not enough data received to form the packet
+
+ switch (RFIFOW(fd, 0)) {
+ case 0x0d02: next = disif_parse_loginack(fd); return 0;
+ case 0x0d03: next = disif_parse_message_from_disc(fd); break;
+ case 0x0d06: next = disif_parse_conf_ack(fd); break;
+ case 0x0d08: next = disif_parse_keepaliveack(fd); break;
+ case 0x0d0a: next = disif_parse_send_message_toplayer(fd); break;
+ case 0x0d0c: next = disif_parse_request_category_ack(fd); break;
+ default:
+ ShowError("Unknown packet 0x%04x from discord server, disconnecting.\n", RFIFOW(fd, 0));
+ set_eof(fd);
+ return 0;
+ }
+ if (next == 0) return 0; //avoid processing rest of packet
+ RFIFOSKIP(fd, len);
+ }
+ return 1;
+}
+
+
+int disif_setip(const char* ip) {
+ char ip_str[16];
+
+ if (!(discord.ip = host2ip(ip))) {
+ ShowWarning("Failed to Resolve Discord Server Address! (%s)\n", ip);
+
+ return 0;
+ }
+
+ ShowInfo("Discord Server IP Address : '" CL_WHITE "%s" CL_RESET "' -> '" CL_WHITE "%s" CL_RESET "'.\n", ip, ip2str(discord.ip, ip_str));
+ return 1;
+}
+
+void disif_setport(uint16 port) {
+ discord.port = port;
+}
+
+void disif_setenabled(const char *opt) {
+ if (!config_switch(opt))
+ discord.state = DiscordState::stopped;
+}
+
+int disif_setdiscchannel(const char * w1, const char * w2) {
+ w1 = w1 + strlen("discord_channel");
+ int n = strtoul(w1, nullptr, 10);
+ uint64 id = strtoull(w2, nullptr, 10);
+ if (n > MAX_CHANNELS)
+ return 0;
+
+ discord.channels[n].disc_channel_id = id;
+ ShowInfo("Set channel #%d to id %llu\n", n, id);
+ return 1;
+}
+
+int disif_setrochannel(const char * w1, const char * w2) {
+ w1 = w1 + strlen("ro_channel");
+ int n = strtoul(w1, nullptr, 10);
+ if (n >= MAX_CHANNELS)
+ return 0;
+
+ char channel_name[CHAN_NAME_LENGTH];
+
+ safestrncpy(channel_name, w2, sizeof(channel_name));
+ auto * channel = channel_name2channel(channel_name, nullptr, 0);
+ if (!channel) {
+ ShowError("Channel with name %s does not exist, ignoring for discord\n", w2);
+ return 0;
+ }
+
+ discord.channels[n].channel = channel;
+ ShowInfo("Set channel #%d to name %s\n", n, channel_name);
+ return 1;
+}
+
+int disif_setrequestchannel(const char * w2) {
+ uint64 id = strtoull(w2, nullptr, 10);
+ discord.request_channel_id = id;
+ ShowInfo("Set request channel to id %llu\n", id);
+ return 1;
+}
+
+int disif_setrequestcategory(const char * w2) {
+ uint64 id = strtoull(w2, nullptr, 10);
+ discord.request_category_id = id;
+ ShowInfo("Set request category to id %llu\n", id);
+ return 1;
+}
+
+/**
+ * A player PMed @discord, send the message to discord request category
+ * Premium only
+*/
+int disif_discord_wis(const map_session_data &sd, const char *target, const char *msg) {
+ if (!msg || !*msg || !discord.request_category_id)
+ return 0;
+
+ return disif_send_request_to_disc(sd, msg);
+}
+
+/**
+* Called when the connection to discord Server is disconnected.
+*/
+void disif_on_disconnect() {
+ ShowStatus("Discord-server has disconnected.\n");
+ if (discord.connect_timer)
+ delete_timer(discord.connect_timer, check_connect_discord_server);
+
+ if (discord.state == DiscordState::stopped)
+ return;
+ discord.connect_timer =
+ add_timer(gettick() + (discord.connect_seconds * 1000), check_connect_discord_server, 0, 0);
+}
+
+#ifdef WIN32
+#define sErrno WSAGetLastError()
+#define S_EINTR WSAEINTR
+#define sFD_SET(fd, set) FD_SET(fd2sock_ext(fd), set)
+#else
+#define sErrno errno
+#define S_EINTR EINTR
+#define sFD_SET(fd, set) FD_SET(fd, set)
+#endif
+
+/*==========================================
+ * timerFunction
+ * Chk the connection to discord server, (if it down)
+ *------------------------------------------*/
+static TIMER_FUNC(check_connect_discord_server){
+ discord.connect_timer = 0;
+ discord.connect_seconds += 5;
+
+ if (discord.state == DiscordState::stopped)
+ return 0;
+
+ if (discord.fd <= 0 || session[discord.fd] == NULL) {
+ ShowStatus("Attempting to connect to Discord Server. Please wait.\n");
+
+ if (discord.state == DiscordState::connencting) {
+ // after 10 seconds, just close
+ ShowError("10 seconds waiting for accept from discord server, restarting connection\n");
+ do_close(discord.fd);
+ delete_timer(discord.accept_timer, check_accept_discord_server);
+ discord.state = DiscordState::disconnected;
+ discord.connect_timer = add_timer(gettick() + 1000, check_connect_discord_server, 0, 0);
+ return 0;
+ }
+
+ discord.fd = make_connection(discord.ip, discord.port, false, 10, true);
+
+ if (discord.fd == -1) { // Attempt to connect later. [Skotlex]
+ ShowInfo("make_connection failed, will retry in %s seconds\n", discord.connect_seconds);
+ discord.connect_timer = add_timer(gettick() + (discord.connect_seconds * 1000), check_connect_discord_server, 0, 0);
+ return 0;
+ }
+
+ discord.state = DiscordState::connencting;
+ discord.accept_timer = add_timer(gettick() + 1000, check_accept_discord_server, 0, 0);
+ }
+
+ return 0;
+}
+
+/**
+ * Use a non-blocking select to check if discord fd is connected
+ */
+static TIMER_FUNC(check_accept_discord_server) {
+ discord.accept_timer = 0;
+ if (discord.fd <= 0) {
+ ShowError("Discord Server fd invalid, can't accept\n");
+ return 0;
+ }
+
+ fd_set dfd;
+ FD_ZERO(&dfd);
+ sFD_SET(discord.fd, &dfd);
+
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ auto ret = select(discord.fd + 1, nullptr, &dfd, nullptr, &tv);
+ if (ret < 0) {
+ ShowError("Discord select failed [%d], retrying in %d seconds\n", sErrno, discord.connect_seconds);
+ discord.fd = 0;
+ discord.state = DiscordState::disconnected;
+ if (!discord.connect_timer) {
+ discord.connect_timer = add_timer(gettick() + (discord.connect_seconds * 1000), check_connect_discord_server, 0, 0);
+ }
+ return 0;
+ } else if (ret == 0) {
+ // ShowInfo("Still haven't connected to discord server, will retry in 1s\n");
+ discord.accept_timer = add_timer(gettick() + 1000, check_accept_discord_server, 0, 0);
+ return 0;
+ }
+#ifdef WIN32
+ int err = 0;
+ int err_len = sizeof(err);
+ if (getsockopt(fd2sock_ext(discord.fd), SOL_SOCKET, SO_ERROR, (char *)&err, &err_len)) {
+ ShowError("getsockopt failed!?\n");
+ }
+#else
+ int err = 0;
+ socklen_t err_len = sizeof(err);
+ if (getsockopt(discord.fd, SOL_SOCKET, SO_ERROR, &err, &err_len)) {
+ ShowError("getsockopt failed!?\n");
+ }
+#endif
+ if (err) {
+ ShowError("Discord connect failed, retrying in %d seconds\n", discord.connect_seconds);
+ discord.fd = 0;
+ discord.state = DiscordState::disconnected;
+ if (!discord.connect_timer) {
+ discord.connect_timer = add_timer(gettick() + (discord.connect_seconds * 1000), check_connect_discord_server, 0, 0);
+ }
+ return 0;
+ }
+ ShowInfo("Discord server connection was accepted!\n");
+ add_readfd(discord.fd, discord.ip);
+ discord.state = DiscordState::connected;
+ session[discord.fd]->func_parse = disif_parse;
+ session[discord.fd]->flag.server = 1;
+ realloc_fifo(discord.fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK);
+
+ if (discord.connect_timer) {
+ delete_timer(discord.connect_timer, check_connect_discord_server);
+ discord.connect_timer = 0;
+ }
+
+ // only do this when we ack
+ // discord.connect_seconds = 10;
+ disif_connect(discord.fd);
+ return 0;
+}
+
+/*==========================================
+ * Read discord server configuration files (conf/discord_athena.conf...)
+ *------------------------------------------*/
+int discord_config_read(const char *cfgName)
+{
+ char line[1024], w1[32], w2[1024];
+ FILE *fp;
+
+ fp = fopen(cfgName,"r");
+ if(fp == NULL) {
+ ShowError("Discord configuration file not found at: %s\n", cfgName);
+ return 1;
+ }
+
+ while(fgets(line, sizeof(line), fp)) {
+ char* ptr;
+
+ if( line[0] == '/' && line[1] == '/' )
+ continue;
+ if( (ptr = strstr(line, "//")) != NULL )
+ *ptr = '\n'; //Strip comments
+ if( sscanf(line, "%31[^:]: %1023[^\t\r\n]", w1, w2) < 2 )
+ continue;
+
+ //Strip trailing spaces
+ ptr = w2 + strlen(w2);
+ while (--ptr >= w2 && *ptr == ' ');
+ ptr++;
+ *ptr = '\0';
+
+ if (strcmpi(w1, "server_id") == 0) {
+ disif_setserverid(w2);
+ }
+ else if (strcmpi(w1, "token") == 0) {
+ disif_settoken(w2);
+ }
+ else if (strcmpi(w1, "discord_ip") == 0)
+ disif_setip(w2);
+ else if (strcmpi(w1, "discord_port") == 0)
+ disif_setport(atoi(w2));
+ else if (strncmpi(w1, "discord_channel", strlen("discord_channel")) == 0)
+ disif_setdiscchannel(w1, w2);
+ else if (strncmpi(w1, "ro_channel", strlen("ro_channel")) == 0)
+ disif_setrochannel(w1, w2);
+ else if (strcmpi(w1, "import") == 0)
+ discord_config_read(w2);
+ else if (strcmpi(w1, "enable") == 0)
+ disif_setenabled(w2);
+ else if (strcmpi(w1, "discord_request_channel") == 0)
+ disif_setrequestchannel(w2);
+ else if (strcmpi(w1, "discord_request_category") == 0)
+ disif_setrequestcategory(w2);
+ else
+ ShowWarning("Unknown setting '%s' in file %s\n", w1, cfgName);
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+void do_init_disif(void) {
+ discord_config_read("conf/discord_athena.conf");
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ auto &chn = discord.channels[i];
+ if (!chn.disc_channel_id || !chn.channel) {
+ chn.disc_channel_id = 0;
+ chn.channel = nullptr;
+ }
+ }
+
+ add_timer_func_list(check_connect_discord_server, "check_connect_discord_server");
+ add_timer_func_list(check_accept_discord_server, "check_accept_discord_server");
+
+ // establish map-discord connection if not present
+ discord.connect_timer = add_timer(gettick() + 1000, check_connect_discord_server, 0, 0);
+ discord.connect_seconds = 10;
+}
+
+void do_final_disif(void) {
+
+}
+
+void reload_disif(void) {
+ set_eof(discord.fd);
+
+ if (discord.connect_timer) {
+ delete_timer(discord.connect_timer, check_connect_discord_server);
+ discord.connect_timer = 0;
+ }
+
+ if (discord.accept_timer) {
+ delete_timer(discord.accept_timer, check_accept_discord_server);
+ discord.accept_timer = 0;
+ }
+
+ discord_config_read("conf/discord_athena.conf");
+
+ for (int i = 0; i < MAX_CHANNELS; i++) {
+ auto &chn = discord.channels[i];
+ if (!chn.disc_channel_id || !chn.channel) {
+ chn.disc_channel_id = 0;
+ chn.channel = nullptr;
+ }
+ }
+
+ discord.state = DiscordState::disconnected;
+ // establish map-discord connection if not present
+ discord.connect_timer = add_timer(gettick() + 10000, check_connect_discord_server, 0, 0);
+}
+
+
+void stop_disif(void) {
+ set_eof(discord.fd);
+ discord.state = DiscordState::stopped;
+}
\ No newline at end of file
diff --git a/src/map/disif.hpp b/src/map/disif.hpp
new file mode 100644
index 000000000..2bfa0652d
--- /dev/null
+++ b/src/map/disif.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "../config/core.hpp"
+#include "../common/core.hpp" // CORE_ST_LAST
+#include "../common/msg_conf.hpp"
+#include "../common/mmo.hpp"
+
+#include "channel.hpp"
+
+#define TOKEN_LENGTH 32 + 1
+
+enum class DiscordState {
+ disconnected,
+ connencting,
+ connected,
+ stopped,
+};
+
+// must be below 10
+#define MAX_CHANNELS 5
+
+struct discord_channel {
+ uint64 disc_channel_id;
+ Channel *channel;
+};
+
+struct mmo_dis_server {
+ int fd;
+ uint32 ip;
+ uint16 port;
+ uint64 server_id;
+ char token[TOKEN_LENGTH];
+ DiscordState state{DiscordState::disconnected};
+
+ int connect_timer;
+ // the amount of seconds to wait before next connect attempt
+ int connect_seconds;
+ int accept_timer;
+
+ struct discord_channel channels[MAX_CHANNELS];
+ uint64 request_channel_id;
+ uint64 request_category_id;
+};
+
+
+int disif_parse_loginack(int fd);
+int disif_parse_message_from_disc(int fd);
+int disif_send_message_to_disc(struct Channel *channel, char *msg);
+int disif_send_request_to_disc(const map_session_data &sd, const char *message);
+int disif_send_message_tochan(uint64 cid, const char *msg, uint16 len);
+
+int disif_send_conf();
+int disif_send_request_category(uint64 cid);
+
+int disif_setdiscchannel(const char * w1, const char * w2);
+int disif_setrochannel(const char * w1, const char * w2);
+int disif_setrequestchannel(const char * w2);
+
+int disif_discord_wis(const map_session_data &sd, const char *target, const char *msg);
+
+void do_init_disif(void);
+void do_final_disif(void);
+int disif_parse(int fd);
+void disif_on_disconnect();
+void reload_disif(void);
+void stop_disif(void);
diff --git a/src/map/intif.cpp b/src/map/intif.cpp
index a3a761072..344377780 100644
--- a/src/map/intif.cpp
+++ b/src/map/intif.cpp
@@ -18,6 +18,7 @@
#include "chrif.hpp"
#include "clan.hpp"
#include "clif.hpp"
+#include "disif.hpp"
#include "elemental.hpp"
#include "guild.hpp"
#include "homunculus.hpp"
diff --git a/src/map/map-server.vcxproj b/src/map/map-server.vcxproj
index 943bc5d2f..eddd5e899 100644
--- a/src/map/map-server.vcxproj
+++ b/src/map/map-server.vcxproj
@@ -196,6 +196,7 @@
<ClInclude Include="clif_packetdb.hpp" />
<ClInclude Include="clif_shuffle.hpp" />
<ClInclude Include="date.hpp" />
+ <ClInclude Include="disif.hpp" />
<ClInclude Include="duel.hpp" />
<ClInclude Include="elemental.hpp" />
<ClInclude Include="guild.hpp" />
@@ -242,6 +243,7 @@
<ClCompile Include="clan.cpp" />
<ClCompile Include="clif.cpp" />
<ClCompile Include="date.cpp" />
+ <ClCompile Include="disif.cpp" />
<ClCompile Include="duel.cpp" />
<ClCompile Include="elemental.cpp" />
<ClCompile Include="guild.cpp" />
diff --git a/src/map/map-server.vcxproj.filters b/src/map/map-server.vcxproj.filters
index 147286c91..ca9a0d85f 100644
--- a/src/map/map-server.vcxproj.filters
+++ b/src/map/map-server.vcxproj.filters
@@ -152,6 +152,9 @@
<ClInclude Include="vending.hpp">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="disif.hpp">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="achievement.cpp">
@@ -280,5 +283,8 @@
<ClCompile Include="vending.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="disif.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 076bcc144..ced22a6b7 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -33,6 +33,7 @@
#include "chrif.hpp"
#include "clan.hpp"
#include "clif.hpp"
+#include "disif.hpp"
#include "duel.hpp"
#include "elemental.hpp"
#include "guild.hpp"
@@ -4880,6 +4881,7 @@ void do_final(void){
do_final_clan();
#ifndef MAP_GENERATOR
do_final_clif();
+ do_final_disif();
#endif
do_final_npc();
do_final_quest();
@@ -5281,6 +5283,9 @@ int do_init(int argc, char *argv[])
do_init_vending();
do_init_buyingstore();
+#ifndef MAP_GENERATOR
+ do_init_disif();
+#endif
npc_event_do_oninit(); // Init npcs (OnInit)
if (battle_config.pk_mode)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment