· 6 years ago · Oct 12, 2019, 05:26 PM
1#pragma semicolon 1
2
3#define PLUGIN_VERSION "0.1.103"
4
5#include <sourcemod>
6#include <discord>
7#include <SteamWorks>
8#include <smjansson>
9
10#include "discord/DiscordRequest.sp"
11#include "discord/SendMessage.sp"
12#include "discord/GetGuilds.sp"
13#include "discord/GetGuildChannels.sp"
14#include "discord/ListenToChannel.sp"
15#include "discord/SendWebHook.sp"
16#include "discord/reactions.sp"
17#include "discord/UserObject.sp"
18#include "discord/MessageObject.sp"
19#include "discord/GuildMembers.sp"
20#include "discord/GuildRole.sp"
21#include "discord/deletemessage.sp"
22
23//For rate limitation
24Handle hRateLimit = null;
25Handle hRateReset = null;
26Handle hRateLeft = null;
27
28public Plugin myinfo = {
29 name = "Discord API",
30 author = "Deathknife",
31 description = "",
32 version = PLUGIN_VERSION,
33 url = ""
34};
35
36public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
37 CreateNative("DiscordBot.GetToken", Native_DiscordBot_Token_Get);
38
39 //SendMessage.sp
40 CreateNative("DiscordBot.SendMessage", Native_DiscordBot_SendMessage);
41 CreateNative("DiscordBot.SendMessageToChannelID", Native_DiscordBot_SendMessageToChannel);
42 CreateNative("DiscordChannel.SendMessage", Native_DiscordChannel_SendMessage);
43
44 //deletemessage.sp
45 CreateNative("DiscordBot.DeleteMessageID", Native_DiscordBot_DeleteMessageID);
46 CreateNative("DiscordBot.DeleteMessage", Native_DiscordBot_DeleteMessage);
47
48 //ListenToChannel.sp
49 CreateNative("DiscordBot.StartTimer", Native_DiscordBot_StartTimer);
50
51 //GetGuilds.sp
52 CreateNative("DiscordBot.GetGuilds", Native_DiscordBot_GetGuilds);
53 //GetGuildChannels.sp
54 CreateNative("DiscordBot.GetGuildChannels", Native_DiscordBot_GetGuildChannels);
55 //GuildRole.sp
56 CreateNative("DiscordBot.GetGuildRoles", Native_DiscordBot_GetGuildRoles);
57
58 //reactions.sp
59 CreateNative("DiscordBot.AddReactionID", Native_DiscordBot_AddReaction);
60 CreateNative("DiscordBot.DeleteReactionID", Native_DiscordBot_DeleteReaction);
61 CreateNative("DiscordBot.GetReactionID", Native_DiscordBot_GetReaction);
62
63 //GuildMembers.sp
64 CreateNative("DiscordBot.GetGuildMembers", Native_DiscordBot_GetGuildMembers);
65 CreateNative("DiscordBot.GetGuildMembersAll", Native_DiscordBot_GetGuildMembersAll);
66
67 //CreateNative("DiscordChannel.Destroy", Native_DiscordChannel_Destroy);
68
69 //SendWebHook.sp
70 CreateNative("DiscordWebHook.Send", Native_DiscordWebHook_Send);
71 //CreateNative("DiscordWebHook.AddField", Native_DiscordWebHook_AddField);
72 //CreateNative("DiscordWebHook.DeleteFields", Native_DiscordWebHook_DeleteFields);
73
74 //UserObject.sp
75 CreateNative("DiscordUser.GetID", Native_DiscordUser_GetID);
76 CreateNative("DiscordUser.GetUsername", Native_DiscordUser_GetUsername);
77 CreateNative("DiscordUser.GetDiscriminator", Native_DiscordUser_GetDiscriminator);
78 CreateNative("DiscordUser.GetAvatar", Native_DiscordUser_GetAvatar);
79 CreateNative("DiscordUser.IsVerified", Native_DiscordUser_IsVerified);
80 CreateNative("DiscordUser.GetEmail", Native_DiscordUser_GetEmail);
81 CreateNative("DiscordUser.IsBot", Native_DiscordUser_IsBot);
82
83 //MessageObject.sp
84 CreateNative("DiscordMessage.GetID", Native_DiscordMessage_GetID);
85 CreateNative("DiscordMessage.IsPinned", Native_DiscordMessage_IsPinned);
86 CreateNative("DiscordMessage.GetAuthor", Native_DiscordMessage_GetAuthor);
87 CreateNative("DiscordMessage.GetContent", Native_DiscordMessage_GetContent);
88 CreateNative("DiscordMessage.GetChannelID", Native_DiscordMessage_GetChannelID);
89
90 RegPluginLibrary("discord-api");
91
92 return APLRes_Success;
93}
94
95public void OnPluginStart() {
96 hRateLeft = new StringMap();
97 hRateReset = new StringMap();
98 hRateLimit = new StringMap();
99}
100
101public int Native_DiscordBot_Token_Get(Handle plugin, int numParams) {
102 DiscordBot bot = GetNativeCell(1);
103 static char token[196];
104 JsonObjectGetString(bot, "token", token, sizeof(token));
105 SetNativeString(2, token, GetNativeCell(3));
106}
107
108stock void BuildAuthHeader(Handle request, DiscordBot Bot) {
109 static char buffer[256];
110 static char token[196];
111 JsonObjectGetString(Bot, "token", token, sizeof(token));
112 FormatEx(buffer, sizeof(buffer), "Bot %s", token);
113 SteamWorks_SetHTTPRequestHeaderValue(request, "Authorization", buffer);
114}
115
116
117stock Handle PrepareRequest(DiscordBot bot, char[] url, EHTTPMethod method=k_EHTTPMethodGET, Handle hJson=null, SteamWorksHTTPDataReceived DataReceived = INVALID_FUNCTION, SteamWorksHTTPRequestCompleted RequestCompleted = INVALID_FUNCTION) {
118 static char stringJson[16384];
119 stringJson[0] = '\0';
120 if(hJson != null) {
121 json_dump(hJson, stringJson, sizeof(stringJson), 0, true);
122 }
123
124 //Format url
125 static char turl[128];
126 FormatEx(turl, sizeof(turl), "https://discordapp.com/api/%s", url);
127
128 Handle request = SteamWorks_CreateHTTPRequest(method, turl);
129 if(request == null) {
130 return null;
131 }
132
133 if(bot != null) {
134 BuildAuthHeader(request, bot);
135 }
136
137 SteamWorks_SetHTTPRequestRawPostBody(request, "application/json; charset=UTF-8", stringJson, strlen(stringJson));
138
139 SteamWorks_SetHTTPRequestNetworkActivityTimeout(request, 30);
140
141 if(RequestCompleted == INVALID_FUNCTION) {
142 //I had some bugs previously where it wouldn't send request and return code 0 if I didn't set request completed.
143 //This is just a safety then, my issue could have been something else and I will test more later on
144 RequestCompleted = HTTPCompleted;
145 }
146
147 if(DataReceived == INVALID_FUNCTION) {
148 //Need to close the request handle
149 DataReceived = HTTPDataReceive;
150 }
151
152 SteamWorks_SetHTTPCallbacks(request, RequestCompleted, HeadersReceived, DataReceived);
153 if(hJson != null) delete hJson;
154
155 return request;
156}
157
158stock Handle PrepareRequestRaw(DiscordBot bot, char[] url, EHTTPMethod method=k_EHTTPMethodGET, Handle hJson=null, SteamWorksHTTPDataReceived DataReceived = INVALID_FUNCTION, SteamWorksHTTPRequestCompleted RequestCompleted = INVALID_FUNCTION) {
159 static char stringJson[16384];
160 stringJson[0] = '\0';
161 if(hJson != null) {
162 json_dump(hJson, stringJson, sizeof(stringJson), 0, true);
163 }
164
165 Handle request = SteamWorks_CreateHTTPRequest(method, url);
166 if(request == null) {
167 return null;
168 }
169
170 if(bot != null) {
171 BuildAuthHeader(request, bot);
172 }
173
174 SteamWorks_SetHTTPRequestRawPostBody(request, "application/json; charset=UTF-8", stringJson, strlen(stringJson));
175
176 SteamWorks_SetHTTPRequestNetworkActivityTimeout(request, 30);
177
178 if(RequestCompleted == INVALID_FUNCTION) {
179 //I had some bugs previously where it wouldn't send request and return code 0 if I didn't set request completed.
180 //This is just a safety then, my issue could have been something else and I will test more later on
181 RequestCompleted = HTTPCompleted;
182 }
183
184 if(DataReceived == INVALID_FUNCTION) {
185 //Need to close the request handle
186 DataReceived = HTTPDataReceive;
187 }
188
189 SteamWorks_SetHTTPCallbacks(request, RequestCompleted, HeadersReceived, DataReceived);
190 if(hJson != null) delete hJson;
191
192 return request;
193}
194
195public int HTTPCompleted(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statuscode, any data, any data2) {
196}
197
198public int HTTPDataReceive(Handle request, bool failure, int offset, int statuscode, any dp) {
199 delete request;
200}
201
202public int HeadersReceived(Handle request, bool failure, any data, any datapack) {
203 DataPack dp = view_as<DataPack>(datapack);
204 if(failure) {
205 delete dp;
206 return;
207 }
208
209 char xRateLimit[16];
210 char xRateLeft[16];
211 char xRateReset[32];
212
213 bool exists = false;
214
215 exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Limit", xRateLimit, sizeof(xRateLimit));
216 exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Remaining", xRateLeft, sizeof(xRateLeft));
217 exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Reset", xRateReset, sizeof(xRateReset));
218
219 //Get url
220 char route[128];
221 ResetPack(dp);
222 ReadPackString(dp, route, sizeof(route));
223 delete dp;
224
225 int reset = StringToInt(xRateReset);
226 if(reset > GetTime() + 3) {
227 reset = GetTime() + 3;
228 }
229
230 if(exists) {
231 SetTrieValue(hRateReset, route, reset);
232 SetTrieValue(hRateLeft, route, StringToInt(xRateLeft));
233 SetTrieValue(hRateLimit, route, StringToInt(xRateLimit));
234 }else {
235 SetTrieValue(hRateReset, route, -1);
236 SetTrieValue(hRateLeft, route, -1);
237 SetTrieValue(hRateLimit, route, -1);
238 }
239}
240
241/*
242This is rate limit imposing for per-route basis. Doesn't support global limit yet.
243 */
244public void DiscordSendRequest(Handle request, const char[] route) {
245 //Check for reset
246 int time = GetTime();
247 int resetTime;
248
249 int defLimit = 0;
250 if(!GetTrieValue(hRateLimit, route, defLimit)) {
251 defLimit = 1;
252 }
253
254 bool exists = GetTrieValue(hRateReset, route, resetTime);
255
256 if(!exists) {
257 SetTrieValue(hRateReset, route, GetTime() + 5);
258 SetTrieValue(hRateLeft, route, defLimit - 1);
259 SteamWorks_SendHTTPRequest(request);
260 return;
261 }
262
263 if(time == -1) {
264 //No x-rate-limit send
265 SteamWorks_SendHTTPRequest(request);
266 return;
267 }
268
269 if(time > resetTime) {
270 SetTrieValue(hRateLeft, route, defLimit - 1);
271 SteamWorks_SendHTTPRequest(request);
272 return;
273 }else {
274 int left;
275 GetTrieValue(hRateLeft, route, left);
276 if(left == 0) {
277 float remaining = float(resetTime) - float(time) + 1.0;
278 Handle dp = new DataPack();
279 WritePackCell(dp, request);
280 WritePackString(dp, route);
281 CreateTimer(remaining, SendRequestAgain, dp);
282 }else {
283 left--;
284 SetTrieValue(hRateLeft, route, left);
285 SteamWorks_SendHTTPRequest(request);
286 }
287 }
288}
289
290public Handle UrlToDP(char[] url) {
291 DataPack dp = new DataPack();
292 WritePackString(dp, url);
293 return dp;
294}
295
296public Action SendRequestAgain(Handle timer, any dp) {
297 ResetPack(dp);
298 Handle request = ReadPackCell(dp);
299 char route[128];
300 ReadPackString(dp, route, sizeof(route));
301 delete view_as<Handle>(dp);
302 DiscordSendRequest(request, route);
303}
304
305stock bool RenameJsonObject(Handle hJson, char[] key, char[] toKey) {
306 Handle hObject = json_object_get(hJson, key);
307 if(hObject != null) {
308 json_object_set_new(hJson, toKey, hObject);
309 json_object_del(hJson, key);
310 return true;
311 }
312 return false;
313}