1
0

discord.inc 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //Make sure this include isn't already included...
  2. #if defined DISCORD_INCLUDED
  3. #endinput
  4. #endif
  5. #define DISCORD_INCLUDED
  6. //Declare the namespace
  7. #define Discord: Discord_
  8. /*
  9. @Author Dylan/Dy1zan
  10. @Date 02/06/2018
  11. This file is an include adding discord functionality
  12. to the Python middle-man server.
  13. Dependencies:
  14. 1. a_samp
  15. 2. socket
  16. 3. SAMPSON (a_json)
  17. */
  18. /* ====================================================================
  19. Definitions & Variables
  20. =======================================================================*/
  21. //Used to define new message types sent from Discord
  22. #define DiscordResponse:%1(%2) \
  23. forward dresponse_%1(%2); \
  24. public dresponse_%1(%2)
  25. #define MAX_DISCORD_STRING 144
  26. #define MAX_DISCORD_BUFFER 1024
  27. static Socket:g_socket;
  28. static bool:g_alive;
  29. static g_ip[45];
  30. static g_port;
  31. forward Discord:reconnect();
  32. /* ====================================================================
  33. Functions - Initalization & Deinitalization
  34. =======================================================================*/
  35. Discord:connect(ip[], port) {
  36. format(g_ip, sizeof g_ip, "%s", ip);
  37. g_port = port;
  38. g_socket = socket_create(TCP);
  39. Log:info("Discord", "Connecting to Discord...");
  40. if(!is_socket_valid(g_socket)) {
  41. Log:info("Discord", "An error has occured when creating the socket.");
  42. return;
  43. }
  44. socket_connect(g_socket, ip, port);
  45. /*
  46. socket_connect does not gurantee successful connection.
  47. It is also asynchronous.
  48. Therefore, onSocketAnswer needs to be used, where the server
  49. replies with a "hey, I am connected"
  50. */
  51. g_alive = true;
  52. Log:info("Discord", "Successfully connected to the Discord middle-man on %s:%d.", ip, port);
  53. }
  54. Discord:disconnect() {
  55. if(g_alive) socket_destroy(g_socket);
  56. return true;
  57. }
  58. /* ====================================================================
  59. SOCKET CALLBACKS
  60. =======================================================================*/
  61. public onSocketClose(Socket: id) {
  62. //Connection lost TODO: automatically reconnect (requires Timer)
  63. Log:info("Discord", "The connection to the Discord middle-man has been closed. Attempting to reconnect...");
  64. g_alive = false;
  65. SetTimer("Discord_reconnect", 8000, false);
  66. return true;
  67. }
  68. public Discord:reconnect() {
  69. if(!is_socket_valid(g_socket)) {
  70. g_socket = socket_create(TCP);
  71. }
  72. new connected = socket_connect(g_socket, g_ip, g_port);
  73. if(connected) Log:info("Discord", "Successfully reconnected to the Discord middle-man on %s:d", g_ip, g_port);
  74. g_alive = true;
  75. }
  76. /*
  77. Invoked when there is an incoming message from Discord.
  78. Reads the 'type' of Discord message, and then passes it to
  79. the implemented function e.g DiscordResponse:[type](...)
  80. */
  81. public onSocketAnswer(Socket:id, data[], data_len) {
  82. new JSONNode:jsonData = json_parse_string(data);
  83. if(!jsonData) return false;
  84. new type[20], func[32];
  85. json_get_string(jsonData, type, sizeof type, "type");
  86. //invoke the function defined by the response type
  87. format(func, sizeof func, "dresponse_%s", type);
  88. CallLocalFunction(func, "s", data);
  89. Log:debug("Discord:onSocketAnswer", "%s", data);
  90. return true;
  91. }
  92. /* ====================================================================
  93. In addition to SAMPSON,
  94. =======================================================================*/
  95. Discord:json_escape(const string[], output[], len) {
  96. new pos, c, i;
  97. while((c = string[pos++]) > 0) {
  98. if(i == len) {
  99. i = 0;
  100. break;
  101. }
  102. if(c == '"' || c == '\\') {
  103. if((i + 2) > len) {
  104. i = 0;
  105. break;
  106. }
  107. output[i++] = '\\';
  108. output[i++] = c;
  109. }
  110. else {
  111. output[i++] = c;
  112. }
  113. }
  114. output[i] = EOS;
  115. return i;
  116. }
  117. /* ====================================================================
  118. BASIC REQUEST TYPES
  119. =======================================================================*/
  120. /*
  121. This sends a basic request to Discord. This will output the content
  122. as a basic formatted embedded message. e.g !admins
  123. */
  124. stock Discord:sendBasicRequest(channel[], title[], message[], color) {
  125. //Escape the input...
  126. new
  127. escapedTitle[MAX_DISCORD_STRING],
  128. escapedMessage[MAX_DISCORD_STRING];
  129. Discord:json_escape(message, escapedMessage, MAX_DISCORD_STRING);
  130. Discord:json_escape(title, escapedTitle, MAX_DISCORD_STRING);
  131. //Produce JSON and send it to the Python server.
  132. new output[MAX_DISCORD_BUFFER];
  133. format(output, sizeof output, "{\"type\":\"basic\",\"color\":\"%i\", \"channel\":\"%s\", \"title\":\"%s\",\"message\":\"%s\", \"time\":\"%s\"}\r\n", color, channel, escapedTitle, escapedMessage, Discord:GetTimeString());
  134. socket_send(g_socket, output, strlen(output));
  135. return 1;
  136. }
  137. /*
  138. This function outputs to Discord without an embed.
  139. */
  140. stock Discord:sendTableMessage(channel[], title[], message[], color) {
  141. //Escape the input...
  142. new
  143. escapedTitle[MAX_DISCORD_STRING],
  144. escapedMessage[MAX_DISCORD_STRING];
  145. Discord:json_escape(message, escapedMessage, MAX_DISCORD_STRING);
  146. Discord:json_escape(title, escapedTitle, MAX_DISCORD_STRING);
  147. //Produce JSON & send it to the Python server
  148. new output[MAX_DISCORD_BUFFER];
  149. format(output, sizeof output, "{\"type\":\"table\",\"color\":\"%i\", \"channel\":\"%s\", \"title\":\"%s\",\"message\":\"%s\", \"time\":\"%s\"}\r\n", color, channel, escapedTitle, escapedMessage, Discord:GetTimeString());
  150. socket_send(g_socket, output, strlen(output));
  151. return 1;
  152. }
  153. /*
  154. This function relates a player to the request sent to Discord. Similar to
  155. {@link sendBasicRequest}, except includes the player's name.
  156. */
  157. stock Discord:sendPlayerRequest(channel[], playerid, title[], message[], color) {
  158. //Escape the input...
  159. new
  160. escapedTitle[MAX_DISCORD_STRING],
  161. escapedMessage[MAX_DISCORD_STRING];
  162. Discord:json_escape(message, escapedMessage, MAX_DISCORD_STRING);
  163. Discord:json_escape(title, escapedTitle, MAX_DISCORD_STRING);
  164. //Produce JSON & send it to the Python server
  165. new output[MAX_DISCORD_BUFFER];
  166. format(output, sizeof output, "{\"type\":\"basic_player\",\"color\":\"%i\", \"channel\":\"%s\", \"title\":\"%s\", \"player\":\"%s\",\"message\":\"%s\", \"time\":\"%s\"}\r\n", color, channel, escapedTitle, PlayerICName(playerid), escapedMessage, Discord:GetTimeString());
  167. socket_send(g_socket, output, strlen(output));
  168. return 1;
  169. }
  170. /* ====================================================================
  171. UTIL
  172. =======================================================================*/
  173. /*
  174. Gets the current server time as a string.
  175. Dependency: FixHour (SA:RP gamemode)
  176. */
  177. stock Discord:GetTimeString() {
  178. new string[10];
  179. new h, m, s;
  180. gettime(h, m, s);
  181. h = FixHour(h);
  182. format(string, sizeof(string), "%02d:%02d:%02d", h, m, s);
  183. return string;
  184. }