313 lines
9.7 KiB
SourcePawn
313 lines
9.7 KiB
SourcePawn
#pragma semicolon 1
|
|
|
|
#define PLUGIN_VERSION "0.1.103"
|
|
|
|
#include <sourcemod>
|
|
#include <discord>
|
|
#include <SteamWorks>
|
|
#include <smjansson>
|
|
|
|
#include "discord/DiscordRequest.sp"
|
|
#include "discord/SendMessage.sp"
|
|
#include "discord/GetGuilds.sp"
|
|
#include "discord/GetGuildChannels.sp"
|
|
#include "discord/ListenToChannel.sp"
|
|
#include "discord/SendWebHook.sp"
|
|
#include "discord/reactions.sp"
|
|
#include "discord/UserObject.sp"
|
|
#include "discord/MessageObject.sp"
|
|
#include "discord/GuildMembers.sp"
|
|
#include "discord/GuildRole.sp"
|
|
#include "discord/deletemessage.sp"
|
|
|
|
//For rate limitation
|
|
Handle hRateLimit = null;
|
|
Handle hRateReset = null;
|
|
Handle hRateLeft = null;
|
|
|
|
public Plugin myinfo = {
|
|
name = "Discord API",
|
|
author = "Deathknife",
|
|
description = "",
|
|
version = PLUGIN_VERSION,
|
|
url = ""
|
|
};
|
|
|
|
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
|
|
CreateNative("DiscordBot.GetToken", Native_DiscordBot_Token_Get);
|
|
|
|
//SendMessage.sp
|
|
CreateNative("DiscordBot.SendMessage", Native_DiscordBot_SendMessage);
|
|
CreateNative("DiscordBot.SendMessageToChannelID", Native_DiscordBot_SendMessageToChannel);
|
|
CreateNative("DiscordChannel.SendMessage", Native_DiscordChannel_SendMessage);
|
|
|
|
//deletemessage.sp
|
|
CreateNative("DiscordBot.DeleteMessageID", Native_DiscordBot_DeleteMessageID);
|
|
CreateNative("DiscordBot.DeleteMessage", Native_DiscordBot_DeleteMessage);
|
|
|
|
//ListenToChannel.sp
|
|
CreateNative("DiscordBot.StartTimer", Native_DiscordBot_StartTimer);
|
|
|
|
//GetGuilds.sp
|
|
CreateNative("DiscordBot.GetGuilds", Native_DiscordBot_GetGuilds);
|
|
//GetGuildChannels.sp
|
|
CreateNative("DiscordBot.GetGuildChannels", Native_DiscordBot_GetGuildChannels);
|
|
//GuildRole.sp
|
|
CreateNative("DiscordBot.GetGuildRoles", Native_DiscordBot_GetGuildRoles);
|
|
|
|
//reactions.sp
|
|
CreateNative("DiscordBot.AddReactionID", Native_DiscordBot_AddReaction);
|
|
CreateNative("DiscordBot.DeleteReactionID", Native_DiscordBot_DeleteReaction);
|
|
CreateNative("DiscordBot.GetReactionID", Native_DiscordBot_GetReaction);
|
|
|
|
//GuildMembers.sp
|
|
CreateNative("DiscordBot.GetGuildMembers", Native_DiscordBot_GetGuildMembers);
|
|
CreateNative("DiscordBot.GetGuildMembersAll", Native_DiscordBot_GetGuildMembersAll);
|
|
|
|
//CreateNative("DiscordChannel.Destroy", Native_DiscordChannel_Destroy);
|
|
|
|
//SendWebHook.sp
|
|
CreateNative("DiscordWebHook.Send", Native_DiscordWebHook_Send);
|
|
//CreateNative("DiscordWebHook.AddField", Native_DiscordWebHook_AddField);
|
|
//CreateNative("DiscordWebHook.DeleteFields", Native_DiscordWebHook_DeleteFields);
|
|
|
|
//UserObject.sp
|
|
CreateNative("DiscordUser.GetID", Native_DiscordUser_GetID);
|
|
CreateNative("DiscordUser.GetUsername", Native_DiscordUser_GetUsername);
|
|
CreateNative("DiscordUser.GetDiscriminator", Native_DiscordUser_GetDiscriminator);
|
|
CreateNative("DiscordUser.GetAvatar", Native_DiscordUser_GetAvatar);
|
|
CreateNative("DiscordUser.IsVerified", Native_DiscordUser_IsVerified);
|
|
CreateNative("DiscordUser.GetEmail", Native_DiscordUser_GetEmail);
|
|
CreateNative("DiscordUser.IsBot", Native_DiscordUser_IsBot);
|
|
|
|
//MessageObject.sp
|
|
CreateNative("DiscordMessage.GetID", Native_DiscordMessage_GetID);
|
|
CreateNative("DiscordMessage.IsPinned", Native_DiscordMessage_IsPinned);
|
|
CreateNative("DiscordMessage.GetAuthor", Native_DiscordMessage_GetAuthor);
|
|
CreateNative("DiscordMessage.GetContent", Native_DiscordMessage_GetContent);
|
|
CreateNative("DiscordMessage.GetChannelID", Native_DiscordMessage_GetChannelID);
|
|
|
|
RegPluginLibrary("discord-api");
|
|
|
|
return APLRes_Success;
|
|
}
|
|
|
|
public void OnPluginStart() {
|
|
hRateLeft = new StringMap();
|
|
hRateReset = new StringMap();
|
|
hRateLimit = new StringMap();
|
|
}
|
|
|
|
public int Native_DiscordBot_Token_Get(Handle plugin, int numParams) {
|
|
DiscordBot bot = GetNativeCell(1);
|
|
static char token[196];
|
|
JsonObjectGetString(bot, "token", token, sizeof(token));
|
|
SetNativeString(2, token, GetNativeCell(3));
|
|
}
|
|
|
|
stock void BuildAuthHeader(Handle request, DiscordBot Bot) {
|
|
static char buffer[256];
|
|
static char token[196];
|
|
JsonObjectGetString(Bot, "token", token, sizeof(token));
|
|
FormatEx(buffer, sizeof(buffer), "Bot %s", token);
|
|
SteamWorks_SetHTTPRequestHeaderValue(request, "Authorization", buffer);
|
|
}
|
|
|
|
|
|
stock Handle PrepareRequest(DiscordBot bot, char[] url, EHTTPMethod method=k_EHTTPMethodGET, Handle hJson=null, SteamWorksHTTPDataReceived DataReceived = INVALID_FUNCTION, SteamWorksHTTPRequestCompleted RequestCompleted = INVALID_FUNCTION) {
|
|
static char stringJson[16384];
|
|
stringJson[0] = '\0';
|
|
if(hJson != null) {
|
|
json_dump(hJson, stringJson, sizeof(stringJson), 0, true);
|
|
}
|
|
|
|
//Format url
|
|
static char turl[128];
|
|
FormatEx(turl, sizeof(turl), "https://discordapp.com/api/%s", url);
|
|
|
|
Handle request = SteamWorks_CreateHTTPRequest(method, turl);
|
|
if(request == null) {
|
|
return null;
|
|
}
|
|
|
|
if(bot != null) {
|
|
BuildAuthHeader(request, bot);
|
|
}
|
|
|
|
SteamWorks_SetHTTPRequestRawPostBody(request, "application/json; charset=UTF-8", stringJson, strlen(stringJson));
|
|
|
|
SteamWorks_SetHTTPRequestNetworkActivityTimeout(request, 30);
|
|
|
|
if(RequestCompleted == INVALID_FUNCTION) {
|
|
//I had some bugs previously where it wouldn't send request and return code 0 if I didn't set request completed.
|
|
//This is just a safety then, my issue could have been something else and I will test more later on
|
|
RequestCompleted = HTTPCompleted;
|
|
}
|
|
|
|
if(DataReceived == INVALID_FUNCTION) {
|
|
//Need to close the request handle
|
|
DataReceived = HTTPDataReceive;
|
|
}
|
|
|
|
SteamWorks_SetHTTPCallbacks(request, RequestCompleted, HeadersReceived, DataReceived);
|
|
if(hJson != null) delete hJson;
|
|
|
|
return request;
|
|
}
|
|
|
|
stock Handle PrepareRequestRaw(DiscordBot bot, char[] url, EHTTPMethod method=k_EHTTPMethodGET, Handle hJson=null, SteamWorksHTTPDataReceived DataReceived = INVALID_FUNCTION, SteamWorksHTTPRequestCompleted RequestCompleted = INVALID_FUNCTION) {
|
|
static char stringJson[16384];
|
|
stringJson[0] = '\0';
|
|
if(hJson != null) {
|
|
json_dump(hJson, stringJson, sizeof(stringJson), 0, true);
|
|
}
|
|
|
|
Handle request = SteamWorks_CreateHTTPRequest(method, url);
|
|
if(request == null) {
|
|
return null;
|
|
}
|
|
|
|
if(bot != null) {
|
|
BuildAuthHeader(request, bot);
|
|
}
|
|
|
|
SteamWorks_SetHTTPRequestRawPostBody(request, "application/json; charset=UTF-8", stringJson, strlen(stringJson));
|
|
|
|
SteamWorks_SetHTTPRequestNetworkActivityTimeout(request, 30);
|
|
|
|
if(RequestCompleted == INVALID_FUNCTION) {
|
|
//I had some bugs previously where it wouldn't send request and return code 0 if I didn't set request completed.
|
|
//This is just a safety then, my issue could have been something else and I will test more later on
|
|
RequestCompleted = HTTPCompleted;
|
|
}
|
|
|
|
if(DataReceived == INVALID_FUNCTION) {
|
|
//Need to close the request handle
|
|
DataReceived = HTTPDataReceive;
|
|
}
|
|
|
|
SteamWorks_SetHTTPCallbacks(request, RequestCompleted, HeadersReceived, DataReceived);
|
|
if(hJson != null) delete hJson;
|
|
|
|
return request;
|
|
}
|
|
|
|
public int HTTPCompleted(Handle request, bool failure, bool requestSuccessful, EHTTPStatusCode statuscode, any data, any data2) {
|
|
}
|
|
|
|
public int HTTPDataReceive(Handle request, bool failure, int offset, int statuscode, any dp) {
|
|
delete request;
|
|
}
|
|
|
|
public int HeadersReceived(Handle request, bool failure, any data, any datapack) {
|
|
DataPack dp = view_as<DataPack>(datapack);
|
|
if(failure) {
|
|
delete dp;
|
|
return;
|
|
}
|
|
|
|
char xRateLimit[16];
|
|
char xRateLeft[16];
|
|
char xRateReset[32];
|
|
|
|
bool exists = false;
|
|
|
|
exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Limit", xRateLimit, sizeof(xRateLimit));
|
|
exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Remaining", xRateLeft, sizeof(xRateLeft));
|
|
exists = SteamWorks_GetHTTPResponseHeaderValue(request, "X-RateLimit-Reset", xRateReset, sizeof(xRateReset));
|
|
|
|
//Get url
|
|
char route[128];
|
|
ResetPack(dp);
|
|
ReadPackString(dp, route, sizeof(route));
|
|
delete dp;
|
|
|
|
int reset = StringToInt(xRateReset);
|
|
if(reset > GetTime() + 3) {
|
|
reset = GetTime() + 3;
|
|
}
|
|
|
|
if(exists) {
|
|
SetTrieValue(hRateReset, route, reset);
|
|
SetTrieValue(hRateLeft, route, StringToInt(xRateLeft));
|
|
SetTrieValue(hRateLimit, route, StringToInt(xRateLimit));
|
|
}else {
|
|
SetTrieValue(hRateReset, route, -1);
|
|
SetTrieValue(hRateLeft, route, -1);
|
|
SetTrieValue(hRateLimit, route, -1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
This is rate limit imposing for per-route basis. Doesn't support global limit yet.
|
|
*/
|
|
public void DiscordSendRequest(Handle request, const char[] route) {
|
|
//Check for reset
|
|
int time = GetTime();
|
|
int resetTime;
|
|
|
|
int defLimit = 0;
|
|
if(!GetTrieValue(hRateLimit, route, defLimit)) {
|
|
defLimit = 1;
|
|
}
|
|
|
|
bool exists = GetTrieValue(hRateReset, route, resetTime);
|
|
|
|
if(!exists) {
|
|
SetTrieValue(hRateReset, route, GetTime() + 5);
|
|
SetTrieValue(hRateLeft, route, defLimit - 1);
|
|
SteamWorks_SendHTTPRequest(request);
|
|
return;
|
|
}
|
|
|
|
if(time == -1) {
|
|
//No x-rate-limit send
|
|
SteamWorks_SendHTTPRequest(request);
|
|
return;
|
|
}
|
|
|
|
if(time > resetTime) {
|
|
SetTrieValue(hRateLeft, route, defLimit - 1);
|
|
SteamWorks_SendHTTPRequest(request);
|
|
return;
|
|
}else {
|
|
int left;
|
|
GetTrieValue(hRateLeft, route, left);
|
|
if(left == 0) {
|
|
float remaining = float(resetTime) - float(time) + 1.0;
|
|
Handle dp = new DataPack();
|
|
WritePackCell(dp, request);
|
|
WritePackString(dp, route);
|
|
CreateTimer(remaining, SendRequestAgain, dp);
|
|
}else {
|
|
left--;
|
|
SetTrieValue(hRateLeft, route, left);
|
|
SteamWorks_SendHTTPRequest(request);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Handle UrlToDP(char[] url) {
|
|
DataPack dp = new DataPack();
|
|
WritePackString(dp, url);
|
|
return dp;
|
|
}
|
|
|
|
public Action SendRequestAgain(Handle timer, any dp) {
|
|
ResetPack(dp);
|
|
Handle request = ReadPackCell(dp);
|
|
char route[128];
|
|
ReadPackString(dp, route, sizeof(route));
|
|
delete view_as<Handle>(dp);
|
|
DiscordSendRequest(request, route);
|
|
}
|
|
|
|
stock bool RenameJsonObject(Handle hJson, char[] key, char[] toKey) {
|
|
Handle hObject = json_object_get(hJson, key);
|
|
if(hObject != null) {
|
|
json_object_set_new(hJson, toKey, hObject);
|
|
json_object_del(hJson, key);
|
|
return true;
|
|
}
|
|
return false;
|
|
} |