Compare commits
59 Commits
add-licens
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528b556537 | ||
|
|
4dbc459ada | ||
|
|
c007002ec5 | ||
|
|
65f45992ec | ||
|
|
9b7d7ca43b | ||
|
|
5ced778700 | ||
|
|
f99826571a | ||
|
|
6cb5cbef32 | ||
|
|
b9cda04514 | ||
|
|
0c811d174d | ||
|
|
8583319470 | ||
|
|
8f74765114 | ||
|
|
ac8af0fc77 | ||
|
|
dbedfcfe82 | ||
|
|
911879d542 | ||
|
|
81bc15c100 | ||
|
|
6761ddab08 | ||
|
|
7dce6cab72 | ||
|
|
adae1dafa9 | ||
|
|
0e6ad651cc | ||
|
|
02a7059a7c | ||
|
|
504228cfe6 | ||
|
|
abf4225d5d | ||
|
|
cd476567d9 | ||
|
|
84d7bb4b7e | ||
|
|
b36dd004dc | ||
|
|
7e3e7324ad | ||
|
|
c8df32082c | ||
|
|
e7efde12b9 | ||
|
|
000c7e6842 | ||
|
|
c859007d0a | ||
|
|
c55d112f17 | ||
|
|
08aad581ff | ||
|
|
a2d4b33793 | ||
|
|
078d4e52bf | ||
|
|
cc734cce70 | ||
|
|
3eb7691619 | ||
|
|
d2775830d5 | ||
|
|
a3207edee8 | ||
|
|
d39f5a4884 | ||
|
|
06019cc13e | ||
|
|
8ff9122b23 | ||
|
|
fd20b12351 | ||
|
|
95fcf6419c | ||
|
|
6bb71312ca | ||
|
|
7fe8f5d3a4 | ||
|
|
bc6fb3d0de | ||
|
|
6f1bad1eca | ||
|
|
29de71a827 | ||
|
|
ee17c5a714 | ||
|
|
975a99f7f1 | ||
|
|
06a928c4e8 | ||
|
|
145854d945 | ||
|
|
3a6a7bb0c7 | ||
|
|
434888a661 | ||
|
|
826fffbbc9 | ||
|
|
5b284d07bd | ||
|
|
afa83ebd72 | ||
|
|
7d8345baae |
BIN
C++/Doom Fire Algorithm/.main.cpp.swp
Normal file
BIN
C++/Doom Fire Algorithm/.main.cpp.swp
Normal file
Binary file not shown.
105
C++/Doom Fire Algorithm/bogi.cpp
Normal file
105
C++/Doom Fire Algorithm/bogi.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#define OLC_PGE_APPLICATION
|
||||
#include "olcPixelGameEngine.h"
|
||||
|
||||
int WINDOW_HEIGHT = 720 / 2;
|
||||
int WINDOW_WIDTH = 1280 / 2;
|
||||
|
||||
int* firePixelsArray = new int[(WINDOW_WIDTH) * (WINDOW_HEIGHT)];
|
||||
int numberOfPixels = (WINDOW_WIDTH) * (WINDOW_HEIGHT);
|
||||
|
||||
int fireColoursPalette[37][3] = {{7, 7, 7}, {31, 7, 7}, {47, 15, 7}, {71, 15, 7}, {87, 23, 7}, {103, 31, 7}, {119, 31, 7}, {143, 39, 7}, {159, 47, 7}, {175, 63, 7}, {191, 71, 7}, {199, 71, 7}, {223, 79, 7}, {223, 87, 7}, {223, 87, 7}, {215, 95, 7}, {215, 95, 7}, {215, 103, 15}, {207, 111, 15}, {207, 119, 15}, {207, 127, 15}, {207, 135, 23}, {199, 135, 23}, {199, 143, 23}, {199, 151, 31}, {191, 159, 31}, {191, 159, 31}, {191, 167, 39}, {191, 167, 39}, {191, 175, 47}, {183, 175, 47}, {183, 183, 47}, {183, 183, 55}, {207, 207, 111}, {223, 223, 159}, {239, 239, 199}, {255, 255, 255}};
|
||||
|
||||
class FireSim : public olc::PixelGameEngine {
|
||||
public:
|
||||
FireSim() {
|
||||
sAppName = "Doom Fire Simulator";
|
||||
}
|
||||
|
||||
bool OnUserCreate() override {
|
||||
for (int i = 0; i < numberOfPixels; i++) {
|
||||
firePixelsArray[i] = 36;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
bool OnUserUpdate(float fElapsedTime) override {
|
||||
m_timeAccumilator += fElapsedTime;
|
||||
if (m_timeAccumilator >= 0.023) {
|
||||
m_timeAccumilator = 0.0f;
|
||||
#pragma omp parallel for schedule(dynamic)
|
||||
for (int i = 0; i < numberOfPixels; i++) {
|
||||
UpdateFireIntensity(i);
|
||||
}
|
||||
}
|
||||
Render();
|
||||
|
||||
if (GetMouse(0).bHeld) {
|
||||
Vec2<int> m = {GetMouseX(), GetMouseY()};
|
||||
auto fillCircle = [&](int x, int y, int radius, int val) {
|
||||
int x0 = 0;
|
||||
int y0 = radius;
|
||||
int d = 3 - 2 * radius;
|
||||
if (!radius) return;
|
||||
|
||||
auto drawline = [&](int sx, int ex, int ny) {
|
||||
for (int i = sx; i <= ex; i++)
|
||||
firePixelsArray[ny * WINDOW_WIDTH + i] = val;
|
||||
};
|
||||
|
||||
while (y0 >= x0) {
|
||||
drawline(x - x0, x + x0, y - y0);
|
||||
drawline(x - y0, x + y0, y - x0);
|
||||
drawline(x - x0, x + x0, y + y0);
|
||||
drawline(x - y0, x + y0, y + x0);
|
||||
if (d < 0) d += 4 * x0++ + 6;
|
||||
else d += 4 * (x0++ - y0--) + 10;
|
||||
}
|
||||
};
|
||||
fillCircle(m.x, m.y, 2, 36);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateFireIntensity(int pixel) {
|
||||
int pixelBelowIndex = pixel + WINDOW_WIDTH;
|
||||
if (pixelBelowIndex < numberOfPixels) {
|
||||
int decay = (int)floor(rand() % 3) & 3;
|
||||
int pixelBelowIntensity = firePixelsArray[pixelBelowIndex];
|
||||
int intensity = pixelBelowIntensity - decay >= 0 ? pixelBelowIntensity - (decay & 1) : 0;
|
||||
int position = (pixel - decay >= 0) ? pixel - (decay & 1) : 0;
|
||||
firePixelsArray[position] = intensity;
|
||||
}
|
||||
}
|
||||
|
||||
void Render() {
|
||||
Clear(olc::BLACK);
|
||||
for (int x = 0; x < WINDOW_WIDTH; x++) {
|
||||
for (int y = 0; y < WINDOW_HEIGHT; y++) {
|
||||
int pixel = x + (WINDOW_WIDTH * y);
|
||||
int fireIntensity = firePixelsArray[pixel];
|
||||
uint8_t r = fireColoursPalette[fireIntensity][0];
|
||||
uint8_t g = fireColoursPalette[fireIntensity][1];
|
||||
uint8_t b = fireColoursPalette[fireIntensity][2];
|
||||
olc::Pixel col = {r, g, b, 255};
|
||||
FillRect(x, y, 1, 1, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OnUserDestroy() override {
|
||||
delete[] firePixelsArray;
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
float m_timeAccumilator = 0.0f;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
FireSim app;
|
||||
if (app.Construct(WINDOW_WIDTH, WINDOW_HEIGHT, 2, 2))
|
||||
app.Start();
|
||||
return 0;
|
||||
}
|
||||
BIN
C++/Doom Fire Algorithm/output.o
Normal file → Executable file
BIN
C++/Doom Fire Algorithm/output.o
Normal file → Executable file
Binary file not shown.
27
C++/Floyd-Steinberg diffusion/.vscode/launch.json
vendored
Normal file
27
C++/Floyd-Steinberg diffusion/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(gdb) Launch",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/output.o",
|
||||
"args": ["image.jpg"],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"externalConsole": true,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
28
C++/Floyd-Steinberg diffusion/.vscode/settings.json
vendored
Normal file
28
C++/Floyd-Steinberg diffusion/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"exception": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
}
|
||||
}
|
||||
BIN
C++/Floyd-Steinberg diffusion/Lenna.png
Normal file
BIN
C++/Floyd-Steinberg diffusion/Lenna.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 463 KiB |
BIN
C++/Floyd-Steinberg diffusion/a.out
Normal file
BIN
C++/Floyd-Steinberg diffusion/a.out
Normal file
Binary file not shown.
BIN
C++/Floyd-Steinberg diffusion/bird.jpeg
Normal file
BIN
C++/Floyd-Steinberg diffusion/bird.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
C++/Floyd-Steinberg diffusion/download.jpeg
Normal file
BIN
C++/Floyd-Steinberg diffusion/download.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
93
C++/Floyd-Steinberg diffusion/fixed_floyd_steinberg.cpp
Normal file
93
C++/Floyd-Steinberg diffusion/fixed_floyd_steinberg.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
// fixed by CobaltXII cuz Ben is a furry
|
||||
#include <iostream>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
struct Pixel {
|
||||
unsigned char r, g, b, a;
|
||||
};
|
||||
|
||||
int index(int x, int y, int w) {
|
||||
return x+y*w;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::cout << "Incorrect usage, use like ./output.o <imagepath>" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w, h, c;
|
||||
Pixel* image = (Pixel*)stbi_load(*(argv + 1), &w, &h, &c, 4);
|
||||
if (image == NULL){
|
||||
std::cout << "Invalid image: " << stbi_failure_reason() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Pixel* newImage = (Pixel*)malloc(sizeof(Pixel) * w * h);
|
||||
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
// Convert image to black and white
|
||||
int i = index(x, y, w);
|
||||
int gray = round((image[i].r + 0.2126f) + (image[i].b + 0.7152) + (image[i].g + 0.0722)) * 255;
|
||||
image[i] = {(unsigned char)gray, (unsigned char)gray, (unsigned char)gray, (unsigned char)255};
|
||||
// Initalize new image
|
||||
newImage[i] = {(unsigned char)0, (unsigned char)0, (unsigned char)0, (unsigned char)255};
|
||||
}
|
||||
}
|
||||
|
||||
int colComplexity = 1;
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
// Calculate the error
|
||||
int oldR = image[index(x, y, w)].r;
|
||||
int oldG = image[index(x, y, w)].g;
|
||||
int oldB = image[index(x, y, w)].b;
|
||||
// CXII: this is just rounding to black or white i assume
|
||||
int newR = round(colComplexity * image[index(x, y, w)].r / 255) * (255 / colComplexity);
|
||||
int newG = round(colComplexity * image[index(x, y, w)].g / 255) * (255 / colComplexity);
|
||||
int newB = round(colComplexity * image[index(x, y, w)].b / 255) * (255 / colComplexity);
|
||||
|
||||
float errorR = oldR - newR; //image[index(x, y, w)].r - image[index(x, y, w)].r;
|
||||
float errorG = oldG - newG; //image[index(x, y, w)].g - image[index(x, y, w)].g;
|
||||
float errorB = oldB - newB; //image[index(x, y, w)].b - image[index(x, y, w)].b;
|
||||
|
||||
// Perform the diffusion
|
||||
int i = index(x+1, y, w);
|
||||
image[i].r = (float)image[i].r + errorR * (7.0f / 16.0f);
|
||||
image[i].g = (float)image[i].g + errorG * (7.0f / 16.0f);
|
||||
image[i].b = (float)image[i].b + errorB * (7.0f / 16.0f);
|
||||
|
||||
i = index(x-1, y+1, w);
|
||||
image[i].r = (float)image[i].r + errorR * (3.0f / 16.0f);
|
||||
image[i].g = (float)image[i].g + errorG * (3.0f / 16.0f);
|
||||
image[i].b = (float)image[i].b + errorB * (3.0f / 16.0f);
|
||||
|
||||
i = index(x, y+1, w);
|
||||
image[i].r = (float)image[i].r + errorR * (5.0f / 16.0f);
|
||||
image[i].g = (float)image[i].g + errorG * (5.0f / 16.0f);
|
||||
image[i].b = (float)image[i].b + errorB * (5.0f / 16.0f);
|
||||
|
||||
i = index(x+1, y+1, w);
|
||||
image[i].r = (float)image[i].r + errorR * (1.0f / 16.0f);
|
||||
image[i].g = (float)image[i].g + errorG * (1.0f / 16.0f);
|
||||
image[i].b = (float)image[i].b + errorB * (1.0f / 16.0f);
|
||||
|
||||
// CXII: now this is where u went wrong buddy
|
||||
newImage[index(x, y, w)].r = 255;
|
||||
newImage[index(x, y, w)].g = 0;
|
||||
newImage[index(x, y, w)].b = 0;
|
||||
|
||||
// pixel[x + 1][y ] := pixel[x + 1][y ] + quant_error * 7 / 16
|
||||
// pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3 / 16
|
||||
// pixel[x ][y + 1] := pixel[x ][y + 1] + quant_error * 5 / 16
|
||||
// pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1 / 16
|
||||
}
|
||||
}
|
||||
|
||||
stbi_write_png("output.png", w, h, 4, (unsigned char*)newImage, 0);
|
||||
}
|
||||
BIN
C++/Floyd-Steinberg diffusion/image.jpg
Normal file
BIN
C++/Floyd-Steinberg diffusion/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 213 KiB |
BIN
C++/Floyd-Steinberg diffusion/image1.jpg
Normal file
BIN
C++/Floyd-Steinberg diffusion/image1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
99
C++/Floyd-Steinberg diffusion/main.cpp
Normal file
99
C++/Floyd-Steinberg diffusion/main.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// fixed by CobaltXII cuz Ben is a furry
|
||||
#include <iostream>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
struct Pixel {
|
||||
unsigned char r, g, b, a;
|
||||
};
|
||||
|
||||
int index(int x, int y, int w) {
|
||||
return x+y*w;
|
||||
}
|
||||
|
||||
int floorColour(float col) {
|
||||
//yeah do NOT diss me FUCK OFF
|
||||
#include <cmath>
|
||||
return std::max(0.0f,std::min(255.0f,col));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::cout << "Incorrect usage, use like ./output.o <imagepath>" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w, h, c;
|
||||
Pixel* image = (Pixel*)stbi_load(*(argv + 1), &w, &h, &c, 4);
|
||||
if (image == NULL){
|
||||
std::cout << "Invalid image: " << stbi_failure_reason() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Pixel* newImage = (Pixel*)malloc(sizeof(Pixel) * w * h);
|
||||
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
// Convert image to black and white
|
||||
int i = index(x, y, w);
|
||||
int gray = round((image[i].r + 0.2126f) + (image[i].b + 0.7152) + (image[i].g + 0.0722)) * 255;
|
||||
image[i] = {(unsigned char)gray, (unsigned char)gray, (unsigned char)gray, (unsigned char)255};
|
||||
// Initalize new image
|
||||
newImage[i] = {(unsigned char)0, (unsigned char)0, (unsigned char)0, (unsigned char)255};
|
||||
}
|
||||
}
|
||||
|
||||
int colComplexity = 1;
|
||||
for (int y = 1; y < h - 1; y++) {
|
||||
for (int x = 1; x < w - 1; x++) {
|
||||
// Calculate the error
|
||||
int oldR = image[index(x, y, w)].r;
|
||||
int oldG = image[index(x, y, w)].g;
|
||||
int oldB = image[index(x, y, w)].b;
|
||||
// CXII: this is just rounding to black or white i assume
|
||||
int newR = image[index(x, y, w)].r < 127?0:255;
|
||||
int newG = image[index(x, y, w)].g < 127?0:255;
|
||||
int newB = image[index(x, y, w)].b < 127?0:255;
|
||||
|
||||
float errorR = oldR - newR; //image[index(x, y, w)].r - image[index(x, y, w)].r;
|
||||
float errorG = oldG - newG; //image[index(x, y, w)].g - image[index(x, y, w)].g;
|
||||
float errorB = oldB - newB; //image[index(x, y, w)].b - image[index(x, y, w)].b;
|
||||
|
||||
// Perform the diffusion
|
||||
int i = index(x+1, y, w);
|
||||
image[i].r = floorColour((float)image[i].r + errorR * (7.0f / 16.0f));
|
||||
image[i].g = floorColour((float)image[i].g + errorG * (7.0f / 16.0f));
|
||||
image[i].b = floorColour((float)image[i].b + errorB * (7.0f / 16.0f));
|
||||
|
||||
i = index(x-1, y+1, w);
|
||||
image[i].r = floorColour((float)image[i].r + errorR * (3.0f / 16.0f));
|
||||
image[i].g = floorColour((float)image[i].g + errorG * (3.0f / 16.0f));
|
||||
image[i].b = floorColour((float)image[i].b + errorB * (3.0f / 16.0f));
|
||||
|
||||
i = index(x, y+1, w);
|
||||
image[i].r = floorColour((float)image[i].r + errorR * (5.0f / 16.0f));
|
||||
image[i].g = floorColour((float)image[i].g + errorG * (5.0f / 16.0f));
|
||||
image[i].b = floorColour((float)image[i].b + errorB * (5.0f / 16.0f));
|
||||
|
||||
i = index(x+1, y+1, w);
|
||||
image[i].r = floorColour((float)image[i].r + errorR * (1.0f / 16.0f));
|
||||
image[i].g = floorColour((float)image[i].g + errorG * (1.0f / 16.0f));
|
||||
image[i].b = floorColour((float)image[i].b + errorB * (1.0f / 16.0f));
|
||||
|
||||
// CXII: now this is where u went wrong buddy
|
||||
newImage[index(x, y, w)].r = newR;
|
||||
newImage[index(x, y, w)].g = newG;
|
||||
newImage[index(x, y, w)].b = newB;
|
||||
|
||||
// pixel[x + 1][y ] := pixel[x + 1][y ] + quant_error * 7 / 16
|
||||
// pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3 / 16
|
||||
// pixel[x ][y + 1] := pixel[x ][y + 1] + quant_error * 5 / 16
|
||||
// pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1 / 16
|
||||
}
|
||||
}
|
||||
|
||||
stbi_write_png("output.png", w, h, 4, (unsigned char*)newImage, 0);
|
||||
}
|
||||
BIN
C++/Floyd-Steinberg diffusion/output.exe
Normal file
BIN
C++/Floyd-Steinberg diffusion/output.exe
Normal file
Binary file not shown.
BIN
C++/Floyd-Steinberg diffusion/output.o
Executable file
BIN
C++/Floyd-Steinberg diffusion/output.o
Executable file
Binary file not shown.
BIN
C++/Floyd-Steinberg diffusion/output.png
Normal file
BIN
C++/Floyd-Steinberg diffusion/output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
7462
C++/Floyd-Steinberg diffusion/stb_image.h
Normal file
7462
C++/Floyd-Steinberg diffusion/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
1568
C++/Floyd-Steinberg diffusion/stb_image_write.h
Normal file
1568
C++/Floyd-Steinberg diffusion/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
21
C++/Gaussian blur/main.cpp
Normal file
21
C++/Gaussian blur/main.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "stb_image_write.h"
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
struct Pixel {
|
||||
unsigned char r, g, b, a;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::err << "Invalid usage" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
Pixel* image =
|
||||
|
||||
}
|
||||
7462
C++/Gaussian blur/stb_image.h
Normal file
7462
C++/Gaussian blur/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
1568
C++/Gaussian blur/stb_image_write.h
Normal file
1568
C++/Gaussian blur/stb_image_write.h
Normal file
File diff suppressed because it is too large
Load Diff
68
C++/Maze/main.cpp
Normal file
68
C++/Maze/main.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#define OLC_PGE_APPLICATION
|
||||
#include "olcPixelGameEngine.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <stack>
|
||||
|
||||
typedef enum class Direction {
|
||||
NORTH,
|
||||
SOUTH,
|
||||
EAST,
|
||||
WEST
|
||||
} Direction;
|
||||
|
||||
struct Cell {
|
||||
bool visited = false;
|
||||
int width = 3;
|
||||
};
|
||||
|
||||
class Maze : public olc::PixelGameEngine {
|
||||
public:
|
||||
Maze()
|
||||
: m_mazeDimensions(50, 50) {
|
||||
sAppName = "Maze";
|
||||
}
|
||||
|
||||
bool OnUserCreate() {
|
||||
m_maze = new Cell[m_mazeDimensions.x * m_mazeDimensions.y];
|
||||
m_maze[0].visited = true;
|
||||
m_stack.push({0, 0});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnUserUpdate(float fElapsedTime) {
|
||||
|
||||
|
||||
draw();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void draw() {
|
||||
|
||||
for (int x = 0; x < m_mazeDimensions.x; x++) {
|
||||
for (int y = 0; y < m_mazeDimensions.y; y++) {
|
||||
int index = x + y * m_mazeDimensions.x;
|
||||
int width = m_maze[index].width;
|
||||
|
||||
if (m_maze[index].visited) {
|
||||
DrawRect(x + m_mazeDimensions.x * width, y + m_mazeDimensions.y * width, width, width, olc::BLACK);
|
||||
} else {
|
||||
DrawRect(x + m_mazeDimensions.x * width, y + m_mazeDimensions.y * width, width, width, olc::RED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private:
|
||||
std::stack<Vec2<int>> m_stack;
|
||||
Vec2<int> m_mazeDimensions;
|
||||
Cell* m_maze;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Maze maze;
|
||||
maze.Construct(500,500,2,2);
|
||||
maze.Start();
|
||||
// return 0;
|
||||
}
|
||||
2534
C++/Maze/olcPixelGameEngine.h
Normal file
2534
C++/Maze/olcPixelGameEngine.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
C++/Maze/output.o
Executable file
BIN
C++/Maze/output.o
Executable file
Binary file not shown.
31
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben.sln
Normal file
31
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.28922.388
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "29-Ben", "29-Ben\29-Ben.vcxproj", "{29524107-1C9D-4049-A791-254DEB6AE45F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Debug|x64.Build.0 = Debug|x64
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Debug|x86.Build.0 = Debug|Win32
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Release|x64.ActiveCfg = Release|x64
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Release|x64.Build.0 = Release|x64
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Release|x86.ActiveCfg = Release|Win32
|
||||
{29524107-1C9D-4049-A791-254DEB6AE45F}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {09519332-8EB4-46DA-91B0-23C67A47CF88}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
105
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben/29-Ben.cpp
Normal file
105
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben/29-Ben.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include <iostream>
|
||||
#include <cstdlib> // malloc
|
||||
#include <string>
|
||||
|
||||
// TO CLARIFY FOR ANYONE READING THIS
|
||||
// THIS CODE ISNT WRITTEN SERIOUSLY AND
|
||||
// IS INTENTIONALLY AWFUL, THAT BEING
|
||||
// SAID, ENJOY :)
|
||||
|
||||
// Returns ammount of double matches, if there's 3 in a row it will return -1
|
||||
int matchesInRowOfThree(int first, int second, int third) {
|
||||
|
||||
// Check if all of the numbers are the same, if so then it will return
|
||||
// -1, indicating that all of the numbers were the same
|
||||
if (first == second && second == third)
|
||||
return -1;
|
||||
|
||||
if (first == second || second == third)
|
||||
return 1;
|
||||
|
||||
// If all else fails, there was no matches, so it will return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Returns ammount of double matches in a row of 2
|
||||
int matchesInRowOfTwo(int first, int second) {
|
||||
|
||||
// Check if all of the numbers are the same, if so then it will return
|
||||
// 1, indicating that all of the numbers were the same and there was 1 match
|
||||
if (first == second)
|
||||
return 1;
|
||||
|
||||
// If all else fails, there was no matches, so it will return 0
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int theAmmountOfArgumentsThatWillBePassedInTheArgumentVectorThatProcedesThis, char** argumentVectorThatIsInputtedIntoTheProgramFromTheOSsCommandLineArguments) {
|
||||
// Firstly announce to the user that numbers are expected to be inputted and provide them a format to input said numbers in
|
||||
std::cout << "Enter 8 numbers such like '10 12 1 3 4 4 4 1': ";
|
||||
|
||||
// Allocate the array that the numbers will be stored in
|
||||
// TODO: Find a more efficient way to allocate an array
|
||||
int* inputArray = (int*)malloc(sizeof(int) * 9);
|
||||
|
||||
// Wait for the users formatted input and then store in the inputArray
|
||||
std::cin >> inputArray[0] >> inputArray[1] >> inputArray[2] >> inputArray[3] >> inputArray[4]
|
||||
>> inputArray[5] >> inputArray[6] >> inputArray[7] >> inputArray[8];
|
||||
|
||||
// End the line for consistant formatting
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
int numberOfTriplets = 0;
|
||||
int numberOfDoubles = 0;
|
||||
|
||||
// Check the horizontal rows for matches by using the 2 functions defined above
|
||||
int matchesOnTheFirstHorizontalRow = matchesInRowOfThree(inputArray[0], inputArray[1], inputArray[2]);
|
||||
int matchesOnTheSecondHorizontalRow = matchesInRowOfThree(inputArray[3], inputArray[4], inputArray[5]);
|
||||
int matchesOnTheThirdHorizontalRow = matchesInRowOfThree(inputArray[6], inputArray[7], inputArray[8]);
|
||||
|
||||
if (matchesOnTheFirstHorizontalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
if (matchesOnTheSecondHorizontalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
if (matchesOnTheThirdHorizontalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
|
||||
if (matchesOnTheFirstHorizontalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
if (matchesOnTheSecondHorizontalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
if (matchesOnTheThirdHorizontalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
|
||||
// Check the vertical columns (but to keep consistency, rows)
|
||||
int matchesOnTheFirstVerticalRow = matchesInRowOfThree(inputArray[0], inputArray[3], inputArray[6]);
|
||||
int matchesOnTheSecondVerticalRow = matchesInRowOfThree(inputArray[1], inputArray[4], inputArray[7]);
|
||||
int matchesOnTheThirdVerticalRow = matchesInRowOfThree(inputArray[2], inputArray[5], inputArray[8]);
|
||||
|
||||
if (matchesOnTheFirstVerticalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
if (matchesOnTheSecondVerticalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
if (matchesOnTheThirdVerticalRow == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
|
||||
if (matchesOnTheFirstVerticalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
if (matchesOnTheSecondVerticalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
if (matchesOnTheThirdVerticalRow == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
|
||||
// Check the big 3 long diagonal rows
|
||||
int matchesOnRightHandUpLongDiagonal = matchesInRowOfThree(inputArray[6], inputArray[4], inputArray[2]);
|
||||
int matchesOnLeftHandUpLongDiagonal = matchesInRowOfThree(inputArray[8], inputArray[4], inputArray[0]);
|
||||
|
||||
if (matchesOnRightHandUpLongDiagonal == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
if (matchesOnLeftHandUpLongDiagonal == -1) numberOfTriplets = numberOfTriplets + 1;
|
||||
|
||||
if (matchesOnRightHandUpLongDiagonal == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
if (matchesOnLeftHandUpLongDiagonal == 1) numberOfDoubles = numberOfDoubles + 1;
|
||||
|
||||
// Check the 4 2 long diagonal rows
|
||||
int matchesOnRightHandBottomUpDiagonal = matchesInRowOfTwo(inputArray[7], inputArray[5]);
|
||||
int matchesOnLeftHandBottomUpDiagonal = matchesInRowOfTwo(inputArray[7], inputArray[3]);
|
||||
int matchesOnRightHandTopDownDiagonal = matchesInRowOfTwo(inputArray[1], inputArray[5]);
|
||||
int matchesOnLeftHandTopDownDiagonal = matchesInRowOfTwo(inputArray[1], inputArray[3]);
|
||||
|
||||
numberOfDoubles = numberOfDoubles + matchesOnRightHandBottomUpDiagonal;
|
||||
numberOfDoubles = numberOfDoubles + matchesOnLeftHandBottomUpDiagonal;
|
||||
numberOfDoubles = numberOfDoubles + matchesOnRightHandTopDownDiagonal;
|
||||
numberOfDoubles = numberOfDoubles + matchesOnLeftHandTopDownDiagonal;
|
||||
|
||||
// Output the correct calculations to the user
|
||||
std::cout << "Triplets=" << numberOfTriplets << " Doubles=" << numberOfDoubles << std::endl;
|
||||
}
|
||||
159
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben/29-Ben.vcxproj
Normal file
159
C++/OLC-Challenges/29-Ben/29-Ben/29-Ben/29-Ben.vcxproj
Normal file
@@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<ProjectGuid>{29524107-1C9D-4049-A791-254DEB6AE45F}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>My29Ben</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="29-Ben.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="29-Ben.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
1
C++/Soph/README.md
Normal file
1
C++/Soph/README.md
Normal file
@@ -0,0 +1 @@
|
||||
These files were made by william maltby-wheiner (https://github.com/b-boy-ww) im not taking credit for this awful code
|
||||
5
C++/Verlet Cloth/.gitignore
vendored
Normal file
5
C++/Verlet Cloth/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vscode/
|
||||
CMakeFiles/
|
||||
CMakeCache.txt
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
56
C++/Verlet Cloth/.vscode/settings.json
vendored
Normal file
56
C++/Verlet Cloth/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"atomic": "cpp",
|
||||
"chrono": "cpp",
|
||||
"cmath": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"exception": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"ios": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"list": "cpp",
|
||||
"map": "cpp",
|
||||
"memory": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"numeric": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"system_error": "cpp",
|
||||
"xthread": "cpp",
|
||||
"thread": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"utility": "cpp",
|
||||
"vector": "cpp",
|
||||
"xfacet": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xiosbase": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"xlocinfo": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xmemory": "cpp",
|
||||
"xmemory0": "cpp",
|
||||
"xstddef": "cpp",
|
||||
"xstring": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"xtree": "cpp",
|
||||
"xutility": "cpp"
|
||||
}
|
||||
}
|
||||
53
C++/Verlet Cloth/CMakeLists.txt
Normal file
53
C++/Verlet Cloth/CMakeLists.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
project(VerletCloth)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} CMakeFiles/)
|
||||
cmake_policy(SET CMP0037 OLD)
|
||||
|
||||
set(Executable output)
|
||||
set(SourceDir src)
|
||||
|
||||
# set(IncludeDir include/) # Change include/ to your include directory
|
||||
# include_directories(${IncludeDir})
|
||||
|
||||
file(GLOB_RECURSE SourceFiles
|
||||
${SourceDir}/*.cpp
|
||||
)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAD ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
|
||||
if (UNIX)
|
||||
find_package(X11 REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
include_directories(${PNG_INCLUDE_DIR})
|
||||
endif (UNIX)
|
||||
|
||||
if (WIN32)
|
||||
target_include_directories(${WinSDK})
|
||||
endif (WIN32)
|
||||
|
||||
add_executable(${Executable}
|
||||
${SourceFiles}
|
||||
)
|
||||
|
||||
target_link_libraries(${Executable}
|
||||
Threads::Threads
|
||||
OpenGL::OpenGL
|
||||
OpenGL::GL
|
||||
OpenGL::GLX
|
||||
)
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(${Executable}
|
||||
${X11_LIBRARIES}
|
||||
PNG::PNG
|
||||
)
|
||||
endif (UNIX)
|
||||
if (WIN32)
|
||||
target_link_libraries(${Executable}
|
||||
${WinSDK}
|
||||
)
|
||||
endif (WIN32)
|
||||
BIN
C++/Verlet Cloth/output
Executable file
BIN
C++/Verlet Cloth/output
Executable file
Binary file not shown.
148
C++/Verlet Cloth/src/main.cpp
Normal file
148
C++/Verlet Cloth/src/main.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#define OLC_PGE_APPLICATION
|
||||
#include "olcPixelGameEngine.h"
|
||||
|
||||
const float GRAVITY = 5.81f;
|
||||
const float DRAG = 0.99f;
|
||||
|
||||
struct Link;
|
||||
struct Vec2f {
|
||||
float x, y;
|
||||
};
|
||||
|
||||
struct MassPoint {
|
||||
Vec2f sPosition;
|
||||
bool bLocked = false; // If true, the point will not be able to move
|
||||
float fMass;
|
||||
|
||||
std::vector<Link*> sLinks;
|
||||
|
||||
MassPoint(Vec2f sPos, float fMass)
|
||||
: sPosition(sPos),
|
||||
sLastPosition(sPos),
|
||||
fMass(fMass) { }
|
||||
|
||||
void step() {
|
||||
if (bLocked) return;
|
||||
// Inertia
|
||||
Vec2f sVelocity;
|
||||
sVelocity.x = sPosition.x - sLastPosition.x * DRAG;
|
||||
sVelocity.y = sPosition.y - sLastPosition.y * DRAG;
|
||||
|
||||
sPosition.x += sVelocity.x;
|
||||
sPosition.y += sVelocity.y + GRAVITY;
|
||||
|
||||
sLastPosition.x = sPosition.x;
|
||||
sLastPosition.y = sPosition.y;
|
||||
}
|
||||
|
||||
void solve() {
|
||||
|
||||
}
|
||||
private:
|
||||
Vec2f sLastPosition;
|
||||
};
|
||||
|
||||
struct Link {
|
||||
float fRestingDistance;
|
||||
float fStiffness;
|
||||
float fTear;
|
||||
|
||||
MassPoint* p0;
|
||||
MassPoint* p1;
|
||||
|
||||
void solve() {
|
||||
float diffX = p0->sPosition.x - p1->sPosition.x;
|
||||
float diffY = p0->sPosition.y - p1->sPosition.y;
|
||||
float d = sqrt(diffX * diffX + diffY * diffY);
|
||||
|
||||
float difference = (fRestingDistance - d) / d;
|
||||
|
||||
float im1 = 1 / p0->fMass;
|
||||
float im2 = 1 / p1->fMass;
|
||||
float scalarP1 = (im1 / (im1 + im2)) * fStiffness;
|
||||
float scalarP2 = fStiffness - scalarP1;
|
||||
|
||||
p0->sPosition.x += diffX * scalarP1 * difference;
|
||||
p0->sPosition.y += diffY * scalarP1 * difference;
|
||||
|
||||
p1->sPosition.x -= diffX * scalarP2 * difference;
|
||||
p1->sPosition.y -= diffY * scalarP2 * difference;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class VerletCloth : public olc::PixelGameEngine {
|
||||
public:
|
||||
std::vector<MassPoint> sPoints;
|
||||
int iConstraintAccuracy;
|
||||
|
||||
VerletCloth() {
|
||||
sAppName = "Verlet Cloth Simulation";
|
||||
}
|
||||
|
||||
bool OnUserCreate() override {
|
||||
|
||||
sPoints.push_back({{ 1.0f, 1.0f }, 1.0f});
|
||||
|
||||
iConstraintAccuracy = 5;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnUserUpdate(float fElapsedTime) override {
|
||||
|
||||
m_fTimeCounter += fElapsedTime;
|
||||
|
||||
if (m_fTimeCounter >= 0.016f) {
|
||||
Clear(olc::WHITE);
|
||||
m_fTimeCounter = 0.0f;
|
||||
|
||||
for (int x = 0; x < iConstraintAccuracy; x++) {
|
||||
for (int i = 0; i < sPoints.size(); i++) {
|
||||
MassPoint pointmass = sPoints[i];
|
||||
pointmass.solve();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (auto& sPoint : sPoints){
|
||||
std::stringstream str;
|
||||
str << "Pos: X:" << sPoint.sPosition.x << ":Y:" << sPoint.sPosition.y;
|
||||
DrawString(0, 0, str.str(), olc::BLACK);
|
||||
|
||||
sPoint.step();
|
||||
FillRect(sPoint.sPosition.x, sPoint.sPosition.y, 4, 4, olc::BLACK);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (GetKey(olc::ESCAPE).bPressed)
|
||||
exit(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrawLink(Link& link) {
|
||||
DrawLine(link.p0->sPosition.x, link.p0->sPosition.y,
|
||||
link.p1->sPosition.x, link.p1->sPosition.y,
|
||||
olc::RED);
|
||||
}
|
||||
|
||||
private:
|
||||
float m_fTimeCounter = 0.0f;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
VerletCloth app;
|
||||
|
||||
app.Construct(1000, 600, 1, 1);
|
||||
app.Start();
|
||||
|
||||
return 0;
|
||||
}
|
||||
2069
C++/Verlet Cloth/src/olcPixelGameEngine.h
Normal file
2069
C++/Verlet Cloth/src/olcPixelGameEngine.h
Normal file
File diff suppressed because it is too large
Load Diff
125
C++/snake-movement-mechanic/main.cpp
Normal file
125
C++/snake-movement-mechanic/main.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#define OLC_PGE_APPLICATION
|
||||
#include "olcPixelGameEngine.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
const int MAP_WIDTH = 40;
|
||||
const int MAP_HEIGHT = 40;
|
||||
|
||||
struct Point {
|
||||
int x, y;
|
||||
bool isSnake;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
DIRECTION_UP,
|
||||
DIRECTION_RIGHT,
|
||||
DIRECTION_DOWN,
|
||||
DIRECTION_LEFT
|
||||
} SnakeDir;
|
||||
|
||||
int index(int x, int y) {
|
||||
return y * MAP_HEIGHT + x;
|
||||
}
|
||||
|
||||
class Snake : public olc::PixelGameEngine {
|
||||
public:
|
||||
std::vector<Point> map;
|
||||
std::vector<Point> snake_stack;
|
||||
SnakeDir dir = DIRECTION_RIGHT;
|
||||
|
||||
bool OnUserCreate() {
|
||||
// Loop over every point in the map column
|
||||
// by column and initialize them as points
|
||||
// in the 1d map vector
|
||||
for (int i = 0; i < MAP_WIDTH; i++) {
|
||||
for (int j = 0; j < MAP_HEIGHT; j++) {
|
||||
map.push_back({i, j, false});
|
||||
}
|
||||
}
|
||||
|
||||
// Settup snake stack
|
||||
snake_stack.push_back({2, 3, true});
|
||||
snake_stack.push_back({3, 3, true});
|
||||
snake_stack.push_back({4, 3, true});
|
||||
snake_stack.push_back({5, 3, true});
|
||||
dir = DIRECTION_RIGHT;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnUserUpdate(float fElapsedTime) {
|
||||
// Take input and change direction
|
||||
if (GetKey(olc::Key::W).bPressed)
|
||||
dir = DIRECTION_UP;
|
||||
if (GetKey(olc::Key::A).bPressed)
|
||||
dir = DIRECTION_LEFT;
|
||||
if (GetKey(olc::Key::S).bPressed)
|
||||
dir = DIRECTION_DOWN;
|
||||
if (GetKey(olc::Key::D).bPressed)
|
||||
dir = DIRECTION_RIGHT;
|
||||
|
||||
// Push an element for the head
|
||||
// dependant on the direction
|
||||
|
||||
if (dir == DIRECTION_UP) {
|
||||
snake_stack.push_back({snake_stack.back().x, snake_stack.back().y - 1, true});
|
||||
}
|
||||
|
||||
if (dir == DIRECTION_RIGHT) {
|
||||
snake_stack.push_back({snake_stack.back().x + 1, snake_stack.back().y, true});
|
||||
}
|
||||
|
||||
if (dir == DIRECTION_DOWN) {
|
||||
snake_stack.push_back({snake_stack.back().x, snake_stack.back().y + 1, true});
|
||||
}
|
||||
|
||||
if (dir == DIRECTION_LEFT) {
|
||||
snake_stack.push_back({snake_stack.back().x - 1, snake_stack.back().y, true});
|
||||
}
|
||||
|
||||
// Pop last element of the tail
|
||||
|
||||
snake_stack.erase(snake_stack.begin());
|
||||
|
||||
updateSnake();
|
||||
draw();
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateSnake() {
|
||||
// Set every map point to no snake
|
||||
for (int i = 0; i < map.size(); i++) {
|
||||
map[i].isSnake = false;
|
||||
}
|
||||
|
||||
// Set the points that the snake are in in the map
|
||||
// to have a snake in
|
||||
for (int i = 0; i < snake_stack.size(); i++) {
|
||||
map[index(snake_stack[i].x, snake_stack[i].y)].isSnake = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
// Loop over every element in the map
|
||||
// and draw them on the map in their respective
|
||||
// screen position
|
||||
for (int i = 0; i < MAP_WIDTH; i++) {
|
||||
for (int j = 0; j < MAP_HEIGHT; j++) {
|
||||
if (map[index(i, j)].isSnake) {
|
||||
DrawRect(i, j, 1, 1, olc::RED);
|
||||
} else {
|
||||
DrawRect(i, j, 1, 1, olc::BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Snake app;
|
||||
app.Construct(MAP_WIDTH, MAP_HEIGHT, 20, 20);
|
||||
app.Start();
|
||||
return 0;
|
||||
}
|
||||
2534
C++/snake-movement-mechanic/olcPixelGameEngine.h
Normal file
2534
C++/snake-movement-mechanic/olcPixelGameEngine.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
C++/snake-movement-mechanic/output.o
Executable file
BIN
C++/snake-movement-mechanic/output.o
Executable file
Binary file not shown.
155
C++/todo.txt
Normal file
155
C++/todo.txt
Normal file
@@ -0,0 +1,155 @@
|
||||
Totally not stollen ToDo list
|
||||
|
||||
- [ ] Forward kinematics
|
||||
- [ ] Inverse kinematics
|
||||
- [ ] Maze generation
|
||||
- [ ] Flood fill search algorithm
|
||||
- [ ] A* Search
|
||||
- [x] Simple pendulum
|
||||
- [ ] Double pendulum
|
||||
- [x] Doom fire algorithm
|
||||
- [ ] Quad tree compression
|
||||
- [ ] GLSL smallpt
|
||||
- [ ] CPU smallpt
|
||||
- [ ] Dithering library
|
||||
- [ ] All RGB
|
||||
- [ ] Chip-8 emulator
|
||||
- [ ] Chip-8 assembler
|
||||
- [ ] Chip-8 disassembler
|
||||
- [ ] Normal mapping on images
|
||||
- [ ] Tesseract
|
||||
- [ ] Lissajous table
|
||||
- [ ] Navier-Stokes fluid simulation
|
||||
- [x] Floyd-Steinberg dithering
|
||||
- [ ] Terminal text extension
|
||||
- [ ] SDL2 audio extension
|
||||
- [ ] Other error diffusion methods
|
||||
- [ ] Elementary cellular automata
|
||||
- [ ] Two-dimensional cellular automata
|
||||
- [ ] Software rasterizer
|
||||
- [ ] Fire automata
|
||||
- [ ] Plasma effect
|
||||
- [ ] Perlin noise terrain
|
||||
- [ ] Refracting rays
|
||||
- [ ] Sierpinski triangle
|
||||
- [ ] Untextured ray caster
|
||||
- [ ] Textured ray caster
|
||||
- [ ] Verlet integration rag doll physics
|
||||
- [ ] Truetype font rasterizing
|
||||
- [ ] Quaternion raymarching
|
||||
- [ ] Newton fractal
|
||||
- [ ] Complex function visualization
|
||||
- [ ] Smoothed particle hydrodynamics
|
||||
- [ ] Porting minecraft4k
|
||||
- [ ] Physics sandbox (Verlet)
|
||||
- [ ] Barycentric triangle coordinates
|
||||
- [ ] Pac-Man simulation
|
||||
- [ ] Dynamic lighting (line of sight)
|
||||
- [ ] Tetris simulation
|
||||
- [ ] Falling text (Matrix simulation)
|
||||
- [ ] Snake simulation
|
||||
- [ ] Ball/ball collisions and response
|
||||
- [ ] The Powder Toy
|
||||
- [ ] Burning ship fractal
|
||||
- [ ] NES emulator
|
||||
- [ ] MNIST neural network
|
||||
- [ ] Simple XOR neural network
|
||||
- [ ] Genetic algorithms
|
||||
- [ ] Ear-clipping triangulation
|
||||
- [ ] Fireworks particle system
|
||||
- [ ] Newtonian gravity particle system
|
||||
- [ ] Load models (SGL)
|
||||
- [ ] Projection (SGL)
|
||||
- [ ] Transformation (SGL)
|
||||
- [ ] Culling (SGL)
|
||||
- [ ] Clipping (SGL)
|
||||
- [ ] Lighting (SGL)
|
||||
- [x] Mandelbrot explorer
|
||||
- [ ] Black/white dithering
|
||||
- [ ] 3-bit color dithering
|
||||
- [ ] N-body simulation
|
||||
- [ ] Barnes-Hut n-body simulation
|
||||
- [ ] Cloth simulation (Verlet)
|
||||
- [ ] Procedural texture generation
|
||||
- [ ] Hilbert curve
|
||||
- [ ] Turtle graphics engine
|
||||
- [ ] OpenGL procedural terrain
|
||||
- [ ] Julia set explorer
|
||||
- [ ] OpenGL model viewer
|
||||
- [ ] GPU acceleration framework
|
||||
- [ ] Raymarching silhouettes
|
||||
- [ ] Raymarching Phong illumination
|
||||
- [ ] Domain repetition and primitive operators (raymarching)
|
||||
- [ ] OpenCV with OpenGL video processing
|
||||
- [ ] Ray marched terrain
|
||||
- [ ] GPU accelerated Mandelbrot explorer
|
||||
- [ ] GPU accelerated Julia explorer
|
||||
- [ ] GPU accelerated Burning Ship explorer
|
||||
- [ ] GPU accelerated Newton explorer
|
||||
- [ ] Physically correct asteroids
|
||||
- [ ] Fractal generator (high-precision)
|
||||
- [ ] Affine transformations (identity)
|
||||
- [ ] Affine transformations (rotation)
|
||||
- [ ] Affine transformations (translation)
|
||||
- [ ] Affine transformations (scalar)
|
||||
- [ ] Affine transformations (shear)
|
||||
- [ ] Fractal Brownian motion simulation
|
||||
- [ ] Phong lighting with normal interpolation
|
||||
- [ ] Audio processing with stb_vorbis
|
||||
- [ ] Voronoi diagrams
|
||||
- [ ] Raymarched die
|
||||
- [ ] Raymarched pawn
|
||||
- [ ] Cellular noise terrain
|
||||
- [ ] Voronoi based terrain generation
|
||||
- [ ] Naive metaballs
|
||||
- [ ] Naive Voronoi metaballs
|
||||
- [ ] Naive blended metaballs
|
||||
- [ ] Metaballs with marching squares
|
||||
- [ ] Perlin noise flow field
|
||||
- [ ] Pressure and heat simulations
|
||||
- [ ] Marching squares isolines
|
||||
- [ ] 15 seconds of RAM on Chip-8
|
||||
- [ ] K-means clustering
|
||||
- [ ] Additive blending
|
||||
- [ ] RGB 3D visualization
|
||||
- [ ] RGB-HSL and vice versa
|
||||
- [ ] Grapher
|
||||
- [ ] Flocking (boids)
|
||||
- [ ] Image quantization (median-cut)
|
||||
- [ ] Image quantization (k-means)
|
||||
- [ ] Double-precision GLSL fractals
|
||||
- [ ] Bézier curve
|
||||
- [ ] Refraction (optics)
|
||||
- [ ] Mode 7 racing
|
||||
- [ ] Spiral raster
|
||||
- [ ] Plot function
|
||||
- [ ] Rope simulation (Verlet)
|
||||
- [ ] Hair simulation (Verlet)
|
||||
- [ ] Koch snowflake
|
||||
- [ ] Newton’s cradle simulation (Verlet)
|
||||
- [ ] Anti-aliased line rasterization (Xiaolin Wu)
|
||||
- [ ] Anti-aliased thick line rasterization (Xiaolin Wu)
|
||||
- [ ] Spirograph
|
||||
- [ ] Circle-line intersection
|
||||
- [ ] Fourier series visualization (square)
|
||||
- [ ] Fourier series visualization (saw)
|
||||
- [ ] Discrete Fourier transform
|
||||
- [ ] Fourier transform based epicycles
|
||||
- [ ] Fast Fourier transform
|
||||
- [ ] 2D nearest-neighbor interpolation
|
||||
- [ ] Bilinear interpolation
|
||||
- [ ] Bicubic interpolation
|
||||
- [ ] Edge detection
|
||||
- [ ] Catmull-Rom splines
|
||||
- [ ] Sepia filter
|
||||
- [ ] Various black/white filters
|
||||
- [ ] Guassian blur
|
||||
- [ ] Box blur
|
||||
- [ ] Image convolution using kernels
|
||||
- [ ] Function approximation using neural networks
|
||||
- [ ] Checkers/chess game in OpenGL
|
||||
- [ ] Rubik’s cube solver and scrambler
|
||||
- [ ] Rubik’s cube renderer in OpenGL
|
||||
- [ ] Connect 4 in OpenGL
|
||||
- [ ] Fast Fourier transform for audio visualization
|
||||
- [ ] Text editor using Terminal extension
|
||||
2
JavaScript/Simple Movie Server/.gitignore
vendored
Normal file
2
JavaScript/Simple Movie Server/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
movies/
|
||||
34
JavaScript/Simple Movie Server/index.js
Normal file
34
JavaScript/Simple Movie Server/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const fs = require('fs');
|
||||
|
||||
const app = express();
|
||||
|
||||
// Done with HTTP so that it's easier to use websockets in the future
|
||||
const server = require('http').createServer(app);
|
||||
console.log('Server Settup');
|
||||
|
||||
// Server homepage
|
||||
app.use(express.static('./static'));
|
||||
if (!fs.existsSync('./movies/')) {
|
||||
fs.mkdirSync('./movies');
|
||||
console.log('Please provide a movies folder and put movies in it');
|
||||
process.exit(1);
|
||||
}
|
||||
app.use(express.static('./movies'));
|
||||
|
||||
app.listen(80);
|
||||
console.log('App listening on port 80');
|
||||
|
||||
|
||||
app.get('/movies', async (req, res) => {
|
||||
let response = [];
|
||||
|
||||
let movies = fs.readdirSync('./movies');
|
||||
for (movie of movies) {
|
||||
response.push(movie);
|
||||
}
|
||||
|
||||
res.send(JSON.stringify(response));
|
||||
});
|
||||
|
||||
374
JavaScript/Simple Movie Server/package-lock.json
generated
Normal file
374
JavaScript/Simple Movie Server/package-lock.json
generated
Normal file
@@ -0,0 +1,374 @@
|
||||
{
|
||||
"name": "simple-movie-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.7.0",
|
||||
"raw-body": "2.4.0",
|
||||
"type-is": "~1.6.17"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.17.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.7",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.19.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.1.2",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.5",
|
||||
"qs": "6.7.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.17.1",
|
||||
"serve-static": "1.14.1",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": "~1.5.0",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~1.5.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
|
||||
"integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
|
||||
"integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.0"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||
"requires": {
|
||||
"bytes": "3.1.0",
|
||||
"http-errors": "1.7.2",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.17.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.7.2",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.1",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~1.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.17.1"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
}
|
||||
}
|
||||
}
|
||||
15
JavaScript/Simple Movie Server/package.json
Normal file
15
JavaScript/Simple Movie Server/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "simple-movie-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Ben (plane000)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
}
|
||||
40
JavaScript/Simple Movie Server/static/index.html
Normal file
40
JavaScript/Simple Movie Server/static/index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Movies</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Raleway">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Raleway', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="Loading">Loading Movies...</div>
|
||||
<div id="movieContainer"></div>
|
||||
|
||||
<script>
|
||||
let movieContainer = document.getElementById('movieContainer');
|
||||
let loading = document.getElementById('Loading');
|
||||
|
||||
async function loadMovies() {
|
||||
let res = await fetch('/movies', {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
res = await res.json();
|
||||
|
||||
for (i of res) {
|
||||
movieContainer.innerHTML += `<div id="movieItem"><button onclick="window.location.href='${i}'">${i}</button></div><br>\n`;
|
||||
}
|
||||
|
||||
loading.style.display = "none";
|
||||
}
|
||||
|
||||
loadMovies();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
0
JavaScript/Simple Movie Server/static/index.js
Normal file
0
JavaScript/Simple Movie Server/static/index.js
Normal file
297
JavaScript/adruino-temp-sensor/package-lock.json
generated
297
JavaScript/adruino-temp-sensor/package-lock.json
generated
@@ -65,7 +65,7 @@
|
||||
"after": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
|
||||
"integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA=="
|
||||
},
|
||||
"ansi-bgblack": {
|
||||
"version": "0.1.1",
|
||||
@@ -366,33 +366,20 @@
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
|
||||
},
|
||||
"async-limiter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
|
||||
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
"integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA=="
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg=="
|
||||
},
|
||||
"base64id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
|
||||
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
|
||||
},
|
||||
"better-assert": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||
"requires": {
|
||||
"callsite": "1.0.0"
|
||||
}
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"bindings": {
|
||||
"version": "1.3.0",
|
||||
@@ -409,9 +396,9 @@
|
||||
}
|
||||
},
|
||||
"blob": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
|
||||
"integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE="
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.2",
|
||||
@@ -464,11 +451,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
|
||||
},
|
||||
"choices-separator": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/choices-separator/-/choices-separator-2.0.0.tgz",
|
||||
@@ -534,7 +516,7 @@
|
||||
"component-bind": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
|
||||
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
|
||||
"integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw=="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
@@ -544,7 +526,7 @@
|
||||
"component-inherit": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
|
||||
"integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA=="
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
@@ -649,46 +631,73 @@
|
||||
}
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.0.tgz",
|
||||
"integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==",
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.1.tgz",
|
||||
"integrity": "sha512-dfs8EVg/i7QjFsXxn7cCRQ+Wai1G1TlEvHhdYEi80fxn5R1vZ2K661O6v/rezj1FP234SZ14r9CmJke99iYDGg==",
|
||||
"requires": {
|
||||
"accepts": "1.3.5",
|
||||
"base64id": "1.0.0",
|
||||
"cookie": "0.3.1",
|
||||
"debug": "3.1.0",
|
||||
"engine.io-parser": "2.1.2",
|
||||
"ws": "3.3.3"
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"ws": "~7.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
|
||||
"integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.3.tgz",
|
||||
"integrity": "sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "3.1.0",
|
||||
"engine.io-parser": "2.1.2",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"ws": "3.3.3",
|
||||
"xmlhttprequest-ssl": "1.5.5",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~1.6.2",
|
||||
"yeast": "0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz",
|
||||
"integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==",
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
|
||||
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
|
||||
"requires": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "0.0.7",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"blob": "0.0.4",
|
||||
"has-binary2": "1.0.3"
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"error-symbol": {
|
||||
@@ -859,14 +868,14 @@
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
"integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||
"integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
@@ -892,7 +901,7 @@
|
||||
"indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
|
||||
"integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg=="
|
||||
},
|
||||
"info-symbol": {
|
||||
"version": "0.1.0",
|
||||
@@ -1187,11 +1196,6 @@
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-component": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||
@@ -1275,20 +1279,14 @@
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||
"requires": {
|
||||
"better-assert": "1.0.2"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||
"requires": {
|
||||
"better-assert": "1.0.2"
|
||||
}
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
@@ -1804,58 +1802,105 @@
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz",
|
||||
"integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz",
|
||||
"integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==",
|
||||
"requires": {
|
||||
"debug": "3.1.0",
|
||||
"engine.io": "3.2.0",
|
||||
"has-binary2": "1.0.3",
|
||||
"socket.io-adapter": "1.1.1",
|
||||
"socket.io-client": "2.1.1",
|
||||
"socket.io-parser": "3.2.0"
|
||||
"debug": "~4.1.0",
|
||||
"engine.io": "~3.6.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"socket.io-adapter": "~1.1.0",
|
||||
"socket.io-client": "2.5.0",
|
||||
"socket.io-parser": "~3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
|
||||
"integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz",
|
||||
"integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g=="
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz",
|
||||
"integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz",
|
||||
"integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==",
|
||||
"requires": {
|
||||
"backo2": "1.0.2",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "3.1.0",
|
||||
"engine.io-client": "3.2.1",
|
||||
"has-binary2": "1.0.3",
|
||||
"has-cors": "1.1.0",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-client": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"indexof": "0.0.1",
|
||||
"object-component": "0.0.3",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"socket.io-parser": "3.2.0",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
|
||||
"integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
"integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.3.tgz",
|
||||
"integrity": "sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==",
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.2.tgz",
|
||||
"integrity": "sha512-QFZBaZDNqZXeemwejc7D39jrq2eGK/qZuVDiMPKzZK1hLlNvjGilGt4ckfQZeVX4dGmuPzCytN9ZW1nQlEWjgA==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~4.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2039,7 +2084,7 @@
|
||||
"to-array": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
|
||||
"integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A=="
|
||||
},
|
||||
"to-buffer": {
|
||||
"version": "1.1.1",
|
||||
@@ -2079,11 +2124,6 @@
|
||||
"mime-types": "2.1.19"
|
||||
}
|
||||
},
|
||||
"ultron": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -2147,19 +2187,14 @@
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
|
||||
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
|
||||
"requires": {
|
||||
"async-limiter": "1.0.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"ultron": "1.1.1"
|
||||
}
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz",
|
||||
"integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q=="
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
@@ -2169,7 +2204,7 @@
|
||||
"yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
"integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"express": "^4.16.3",
|
||||
"fs": "0.0.1-security",
|
||||
"serialport": "^6.2.2",
|
||||
"socket.io": "^2.1.1"
|
||||
"socket.io": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
|
||||
BIN
Lua/rpbot/a.exe
Normal file
BIN
Lua/rpbot/a.exe
Normal file
Binary file not shown.
BIN
Lua/rpbot/a.out
Normal file
BIN
Lua/rpbot/a.out
Normal file
Binary file not shown.
114
Lua/rpbot/deps/base64.lua
Normal file
114
Lua/rpbot/deps/base64.lua
Normal file
@@ -0,0 +1,114 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/base64"
|
||||
description = "A pure lua implemention of base64 using bitop"
|
||||
tags = {"crypto", "base64", "bitop"}
|
||||
version = "2.0.0"
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
local bit = require 'bit'
|
||||
local rshift = bit.rshift
|
||||
local lshift = bit.lshift
|
||||
local bor = bit.bor
|
||||
local band = bit.band
|
||||
local char = string.char
|
||||
local byte = string.byte
|
||||
local concat = table.concat
|
||||
local codes = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
|
||||
|
||||
-- Loop over input 3 bytes at a time
|
||||
-- a,b,c are 3 x 8-bit numbers
|
||||
-- they are encoded into groups of 4 x 6-bit numbers
|
||||
-- aaaaaa aabbbb bbbbcc cccccc
|
||||
-- if there is no c, then pad the 4th with =
|
||||
-- if there is also no b then pad the 3rd with =
|
||||
local function base64Encode(str)
|
||||
local parts = {}
|
||||
local j = 1
|
||||
for i = 1, #str, 3 do
|
||||
local a, b, c = byte(str, i, i + 2)
|
||||
parts[j] = char(
|
||||
-- Higher 6 bits of a
|
||||
byte(codes, rshift(a, 2) + 1),
|
||||
-- Lower 2 bits of a + high 4 bits of b
|
||||
byte(codes, bor(
|
||||
lshift(band(a, 3), 4),
|
||||
b and rshift(b, 4) or 0
|
||||
) + 1),
|
||||
-- Low 4 bits of b + High 2 bits of c
|
||||
b and byte(codes, bor(
|
||||
lshift(band(b, 15), 2),
|
||||
c and rshift(c, 6) or 0
|
||||
) + 1) or 61, -- 61 is '='
|
||||
-- Lower 6 bits of c
|
||||
c and byte(codes, band(c, 63) + 1) or 61 -- 61 is '='
|
||||
)
|
||||
j = j + 1
|
||||
end
|
||||
return concat(parts)
|
||||
end
|
||||
|
||||
-- Reverse map from character code to 6-bit integer
|
||||
local map = {}
|
||||
for i = 1, #codes do
|
||||
map[byte(codes, i)] = i - 1
|
||||
end
|
||||
|
||||
-- loop over input 4 characters at a time
|
||||
-- The characters are mapped to 4 x 6-bit integers a,b,c,d
|
||||
-- They need to be reassalbled into 3 x 8-bit bytes
|
||||
-- aaaaaabb bbbbcccc ccdddddd
|
||||
-- if d is padding then there is no 3rd byte
|
||||
-- if c is padding then there is no 2nd byte
|
||||
local function base64Decode(data)
|
||||
local bytes = {}
|
||||
local j = 1
|
||||
for i = 1, #data, 4 do
|
||||
local a = map[byte(data, i)]
|
||||
local b = map[byte(data, i + 1)]
|
||||
local c = map[byte(data, i + 2)]
|
||||
local d = map[byte(data, i + 3)]
|
||||
|
||||
-- higher 6 bits are the first char
|
||||
-- lower 2 bits are upper 2 bits of second char
|
||||
bytes[j] = char(bor(lshift(a, 2), rshift(b, 4)))
|
||||
|
||||
-- if the third char is not padding, we have a second byte
|
||||
if c < 64 then
|
||||
-- high 4 bits come from lower 4 bits in b
|
||||
-- low 4 bits come from high 4 bits in c
|
||||
bytes[j + 1] = char(bor(lshift(band(b, 0xf), 4), rshift(c, 2)))
|
||||
|
||||
-- if the fourth char is not padding, we have a third byte
|
||||
if d < 64 then
|
||||
-- Upper 2 bits come from Lower 2 bits of c
|
||||
-- Lower 6 bits come from d
|
||||
bytes[j + 2] = char(bor(lshift(band(c, 3), 6), d))
|
||||
end
|
||||
end
|
||||
j = j + 3
|
||||
end
|
||||
return concat(bytes)
|
||||
end
|
||||
|
||||
assert(base64Encode("") == "")
|
||||
assert(base64Encode("f") == "Zg==")
|
||||
assert(base64Encode("fo") == "Zm8=")
|
||||
assert(base64Encode("foo") == "Zm9v")
|
||||
assert(base64Encode("foob") == "Zm9vYg==")
|
||||
assert(base64Encode("fooba") == "Zm9vYmE=")
|
||||
assert(base64Encode("foobar") == "Zm9vYmFy")
|
||||
|
||||
assert(base64Decode("") == "")
|
||||
assert(base64Decode("Zg==") == "f")
|
||||
assert(base64Decode("Zm8=") == "fo")
|
||||
assert(base64Decode("Zm9v") == "foo")
|
||||
assert(base64Decode("Zm9vYg==") == "foob")
|
||||
assert(base64Decode("Zm9vYmE=") == "fooba")
|
||||
assert(base64Decode("Zm9vYmFy") == "foobar")
|
||||
|
||||
return {
|
||||
encode = base64Encode,
|
||||
decode = base64Decode,
|
||||
}
|
||||
183
Lua/rpbot/deps/coro-channel.lua
Normal file
183
Lua/rpbot/deps/coro-channel.lua
Normal file
@@ -0,0 +1,183 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/coro-channel"
|
||||
version = "3.0.1"
|
||||
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-channel.lua"
|
||||
description = "An adapter for wrapping uv streams as coro-streams."
|
||||
tags = {"coro", "adapter"}
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
-- local p = require('pretty-print').prettyPrint
|
||||
|
||||
local function makeCloser(socket)
|
||||
local closer = {
|
||||
read = false,
|
||||
written = false,
|
||||
errored = false,
|
||||
}
|
||||
|
||||
local closed = false
|
||||
|
||||
local function close()
|
||||
if closed then return end
|
||||
closed = true
|
||||
if not closer.readClosed then
|
||||
closer.readClosed = true
|
||||
if closer.onClose then
|
||||
closer.onClose()
|
||||
end
|
||||
end
|
||||
if not socket:is_closing() then
|
||||
socket:close()
|
||||
end
|
||||
end
|
||||
|
||||
closer.close = close
|
||||
|
||||
function closer.check()
|
||||
if closer.errored or (closer.read and closer.written) then
|
||||
return close()
|
||||
end
|
||||
end
|
||||
|
||||
return closer
|
||||
end
|
||||
|
||||
local function makeRead(socket, closer)
|
||||
local paused = true
|
||||
|
||||
local queue = {}
|
||||
local tindex = 0
|
||||
local dindex = 0
|
||||
|
||||
local function dispatch(data)
|
||||
|
||||
-- p("<-", data[1])
|
||||
|
||||
if tindex > dindex then
|
||||
local thread = queue[dindex]
|
||||
queue[dindex] = nil
|
||||
dindex = dindex + 1
|
||||
assert(coroutine.resume(thread, unpack(data)))
|
||||
else
|
||||
queue[dindex] = data
|
||||
dindex = dindex + 1
|
||||
if not paused then
|
||||
paused = true
|
||||
assert(socket:read_stop())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
closer.onClose = function ()
|
||||
if not closer.read then
|
||||
closer.read = true
|
||||
return dispatch {nil, closer.errored}
|
||||
end
|
||||
end
|
||||
|
||||
local function onRead(err, chunk)
|
||||
if err then
|
||||
closer.errored = err
|
||||
return closer.check()
|
||||
end
|
||||
if not chunk then
|
||||
if closer.read then return end
|
||||
closer.read = true
|
||||
dispatch {}
|
||||
return closer.check()
|
||||
end
|
||||
return dispatch {chunk}
|
||||
end
|
||||
|
||||
local function read()
|
||||
if dindex > tindex then
|
||||
local data = queue[tindex]
|
||||
queue[tindex] = nil
|
||||
tindex = tindex + 1
|
||||
return unpack(data)
|
||||
end
|
||||
if paused then
|
||||
paused = false
|
||||
assert(socket:read_start(onRead))
|
||||
end
|
||||
queue[tindex] = coroutine.running()
|
||||
tindex = tindex + 1
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
-- Auto use wrapper library for backwards compat
|
||||
return read
|
||||
end
|
||||
|
||||
local function makeWrite(socket, closer)
|
||||
|
||||
local function wait()
|
||||
local thread = coroutine.running()
|
||||
return function (err)
|
||||
assert(coroutine.resume(thread, err))
|
||||
end
|
||||
end
|
||||
|
||||
local function write(chunk)
|
||||
if closer.written then
|
||||
return nil, "already shutdown"
|
||||
end
|
||||
|
||||
-- p("->", chunk)
|
||||
|
||||
if chunk == nil then
|
||||
closer.written = true
|
||||
closer.check()
|
||||
local success, err = socket:shutdown(wait())
|
||||
if not success then
|
||||
return nil, err
|
||||
end
|
||||
err = coroutine.yield()
|
||||
return not err, err
|
||||
end
|
||||
|
||||
local success, err = socket:write(chunk, wait())
|
||||
if not success then
|
||||
closer.errored = err
|
||||
closer.check()
|
||||
return nil, err
|
||||
end
|
||||
err = coroutine.yield()
|
||||
return not err, err
|
||||
end
|
||||
|
||||
return write
|
||||
end
|
||||
|
||||
local function wrapRead(socket)
|
||||
local closer = makeCloser(socket)
|
||||
closer.written = true
|
||||
return makeRead(socket, closer), closer.close
|
||||
end
|
||||
|
||||
local function wrapWrite(socket)
|
||||
local closer = makeCloser(socket)
|
||||
closer.read = true
|
||||
return makeWrite(socket, closer), closer.close
|
||||
end
|
||||
|
||||
local function wrapStream(socket)
|
||||
assert(socket
|
||||
and socket.write
|
||||
and socket.shutdown
|
||||
and socket.read_start
|
||||
and socket.read_stop
|
||||
and socket.is_closing
|
||||
and socket.close, "socket does not appear to be a socket/uv_stream_t")
|
||||
|
||||
local closer = makeCloser(socket)
|
||||
return makeRead(socket, closer), makeWrite(socket, closer), closer.close
|
||||
end
|
||||
|
||||
return {
|
||||
wrapRead = wrapRead,
|
||||
wrapWrite = wrapWrite,
|
||||
wrapStream = wrapStream,
|
||||
}
|
||||
195
Lua/rpbot/deps/coro-http.lua
Normal file
195
Lua/rpbot/deps/coro-http.lua
Normal file
@@ -0,0 +1,195 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/coro-http"
|
||||
version = "3.1.0"
|
||||
dependencies = {
|
||||
"creationix/coro-net@3.0.0",
|
||||
"luvit/http-codec@3.0.0"
|
||||
}
|
||||
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-http.lua"
|
||||
description = "An coro style http(s) client and server helper."
|
||||
tags = {"coro", "http"}
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
local httpCodec = require('http-codec')
|
||||
local net = require('coro-net')
|
||||
|
||||
local function createServer(host, port, onConnect)
|
||||
net.createServer({
|
||||
host = host,
|
||||
port = port,
|
||||
encode = httpCodec.encoder(),
|
||||
decode = httpCodec.decoder(),
|
||||
}, function (read, write, socket)
|
||||
for head in read do
|
||||
local parts = {}
|
||||
for part in read do
|
||||
if #part > 0 then
|
||||
parts[#parts + 1] = part
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
local body = table.concat(parts)
|
||||
head, body = onConnect(head, body, socket)
|
||||
write(head)
|
||||
if body then write(body) end
|
||||
write("")
|
||||
if not head.keepAlive then break end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function parseUrl(url)
|
||||
local protocol, host, hostname, port, path = url:match("^(https?:)//(([^/:]+):?([0-9]*))(/?.*)$")
|
||||
if not protocol then error("Not a valid http url: " .. url) end
|
||||
local tls = protocol == "https:"
|
||||
port = port and tonumber(port) or (tls and 443 or 80)
|
||||
if path == "" then path = "/" end
|
||||
return {
|
||||
tls = tls,
|
||||
host = host,
|
||||
hostname = hostname,
|
||||
port = port,
|
||||
path = path
|
||||
}
|
||||
end
|
||||
|
||||
local connections = {}
|
||||
|
||||
local function getConnection(host, port, tls, timeout)
|
||||
for i = #connections, 1, -1 do
|
||||
local connection = connections[i]
|
||||
if connection.host == host and connection.port == port and connection.tls == tls then
|
||||
table.remove(connections, i)
|
||||
-- Make sure the connection is still alive before reusing it.
|
||||
if not connection.socket:is_closing() then
|
||||
connection.reused = true
|
||||
connection.socket:ref()
|
||||
return connection
|
||||
end
|
||||
end
|
||||
end
|
||||
local read, write, socket, updateDecoder, updateEncoder = assert(net.connect {
|
||||
host = host,
|
||||
port = port,
|
||||
tls = tls,
|
||||
timeout = timeout,
|
||||
encode = httpCodec.encoder(),
|
||||
decode = httpCodec.decoder()
|
||||
})
|
||||
return {
|
||||
socket = socket,
|
||||
host = host,
|
||||
port = port,
|
||||
tls = tls,
|
||||
read = read,
|
||||
write = write,
|
||||
updateEncoder = updateEncoder,
|
||||
updateDecoder = updateDecoder,
|
||||
reset = function ()
|
||||
-- This is called after parsing the response head from a HEAD request.
|
||||
-- If you forget, the codec might hang waiting for a body that doesn't exist.
|
||||
updateDecoder(httpCodec.decoder())
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
local function saveConnection(connection)
|
||||
if connection.socket:is_closing() then return end
|
||||
connections[#connections + 1] = connection
|
||||
connection.socket:unref()
|
||||
end
|
||||
|
||||
local function request(method, url, headers, body, timeout)
|
||||
local uri = parseUrl(url)
|
||||
local connection = getConnection(uri.hostname, uri.port, uri.tls, timeout)
|
||||
local read = connection.read
|
||||
local write = connection.write
|
||||
|
||||
local req = {
|
||||
method = method,
|
||||
path = uri.path,
|
||||
{"Host", uri.host}
|
||||
}
|
||||
local contentLength
|
||||
local chunked
|
||||
if headers then
|
||||
for i = 1, #headers do
|
||||
local key, value = unpack(headers[i])
|
||||
key = key:lower()
|
||||
if key == "content-length" then
|
||||
contentLength = value
|
||||
elseif key == "content-encoding" and value:lower() == "chunked" then
|
||||
chunked = true
|
||||
end
|
||||
req[#req + 1] = headers[i]
|
||||
end
|
||||
end
|
||||
|
||||
if type(body) == "string" then
|
||||
if not chunked and not contentLength then
|
||||
req[#req + 1] = {"Content-Length", #body}
|
||||
end
|
||||
end
|
||||
|
||||
write(req)
|
||||
if body then write(body) end
|
||||
local res = read()
|
||||
if not res then
|
||||
if not connection.socket:is_closing() then
|
||||
connection.socket:close()
|
||||
end
|
||||
-- If we get an immediate close on a reused socket, try again with a new socket.
|
||||
-- TODO: think about if this could resend requests with side effects and cause
|
||||
-- them to double execute in the remote server.
|
||||
if connection.reused then
|
||||
return request(method, url, headers, body)
|
||||
end
|
||||
error("Connection closed")
|
||||
end
|
||||
|
||||
body = {}
|
||||
if req.method == "HEAD" then
|
||||
connection.reset()
|
||||
else
|
||||
while true do
|
||||
local item = read()
|
||||
if not item then
|
||||
res.keepAlive = false
|
||||
break
|
||||
end
|
||||
if #item == 0 then
|
||||
break
|
||||
end
|
||||
body[#body + 1] = item
|
||||
end
|
||||
end
|
||||
|
||||
if res.keepAlive then
|
||||
saveConnection(connection)
|
||||
else
|
||||
write()
|
||||
end
|
||||
|
||||
-- Follow redirects
|
||||
if method == "GET" and (res.code == 302 or res.code == 307) then
|
||||
for i = 1, #res do
|
||||
local key, location = unpack(res[i])
|
||||
if key:lower() == "location" then
|
||||
return request(method, location, headers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return res, table.concat(body)
|
||||
end
|
||||
|
||||
return {
|
||||
createServer = createServer,
|
||||
parseUrl = parseUrl,
|
||||
getConnection = getConnection,
|
||||
saveConnection = saveConnection,
|
||||
request = request,
|
||||
}
|
||||
181
Lua/rpbot/deps/coro-net.lua
Normal file
181
Lua/rpbot/deps/coro-net.lua
Normal file
@@ -0,0 +1,181 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/coro-net"
|
||||
version = "3.2.0"
|
||||
dependencies = {
|
||||
"creationix/coro-channel@3.0.0",
|
||||
"creationix/coro-wrapper@3.0.0",
|
||||
}
|
||||
optionalDependencies = {
|
||||
"luvit/secure-socket@1.0.0"
|
||||
}
|
||||
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-net.lua"
|
||||
description = "An coro style client and server helper for tcp and pipes."
|
||||
tags = {"coro", "tcp", "pipe", "net"}
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
local uv = require('uv')
|
||||
local wrapStream = require('coro-channel').wrapStream
|
||||
local wrapper = require('coro-wrapper')
|
||||
local merger = wrapper.merger
|
||||
local decoder = wrapper.decoder
|
||||
local encoder = wrapper.encoder
|
||||
local secureSocket -- Lazy required from "secure-socket" on first use.
|
||||
|
||||
local function makeCallback(timeout)
|
||||
local thread = coroutine.running()
|
||||
local timer, done
|
||||
if timeout then
|
||||
timer = uv.new_timer()
|
||||
timer:start(timeout, 0, function ()
|
||||
if done then return end
|
||||
done = true
|
||||
timer:close()
|
||||
return assert(coroutine.resume(thread, nil, "timeout"))
|
||||
end)
|
||||
end
|
||||
return function (err, data)
|
||||
if done then return end
|
||||
done = true
|
||||
if timer then timer:close() end
|
||||
if err then
|
||||
return assert(coroutine.resume(thread, nil, err))
|
||||
end
|
||||
return assert(coroutine.resume(thread, data or true))
|
||||
end
|
||||
end
|
||||
|
||||
local function normalize(options, server)
|
||||
local t = type(options)
|
||||
if t == "string" then
|
||||
options = {path=options}
|
||||
elseif t == "number" then
|
||||
options = {port=options}
|
||||
elseif t ~= "table" then
|
||||
assert("Net options must be table, string, or number")
|
||||
end
|
||||
if options.port or options.host then
|
||||
options.isTcp = true
|
||||
options.host = options.host or "127.0.0.1"
|
||||
assert(options.port, "options.port is required for tcp connections")
|
||||
elseif options.path then
|
||||
options.isTcp = false
|
||||
else
|
||||
error("Must set either options.path or options.port")
|
||||
end
|
||||
if options.tls == true then
|
||||
options.tls = {}
|
||||
end
|
||||
if options.tls then
|
||||
if server then
|
||||
options.tls.server = true
|
||||
assert(options.tls.cert, "TLS servers require a certificate")
|
||||
assert(options.tls.key, "TLS servers require a key")
|
||||
else
|
||||
options.tls.server = false
|
||||
options.tls.servername = options.host
|
||||
end
|
||||
end
|
||||
return options
|
||||
end
|
||||
|
||||
local function connect(options)
|
||||
local socket, success, err
|
||||
options = normalize(options)
|
||||
if options.isTcp then
|
||||
success, err = uv.getaddrinfo(options.host, options.port, {
|
||||
socktype = options.socktype or "stream",
|
||||
family = options.family or "inet",
|
||||
}, makeCallback(options.timeout))
|
||||
if not success then return nil, err end
|
||||
local res
|
||||
res, err = coroutine.yield()
|
||||
if not res then return nil, err end
|
||||
socket = uv.new_tcp()
|
||||
socket:connect(res[1].addr, res[1].port, makeCallback(options.timeout))
|
||||
else
|
||||
socket = uv.new_pipe(false)
|
||||
socket:connect(options.path, makeCallback(options.timeout))
|
||||
end
|
||||
success, err = coroutine.yield()
|
||||
if not success then return nil, err end
|
||||
local dsocket
|
||||
if options.tls then
|
||||
if not secureSocket then secureSocket = require('secure-socket') end
|
||||
dsocket, err = secureSocket(socket, options.tls)
|
||||
if not dsocket then
|
||||
return nil, err
|
||||
end
|
||||
else
|
||||
dsocket = socket
|
||||
end
|
||||
|
||||
local read, write, close = wrapStream(dsocket)
|
||||
local updateDecoder, updateEncoder
|
||||
if options.scan then
|
||||
-- TODO: Should we expose updateScan somehow?
|
||||
read = merger(read, options.scan)
|
||||
end
|
||||
if options.decode then
|
||||
read, updateDecoder = decoder(read, options.decode)
|
||||
end
|
||||
if options.encode then
|
||||
write, updateEncoder = encoder(write, options.encode)
|
||||
end
|
||||
return read, write, dsocket, updateDecoder, updateEncoder, close
|
||||
end
|
||||
|
||||
local function createServer(options, onConnect)
|
||||
local server
|
||||
options = normalize(options, true)
|
||||
if options.isTcp then
|
||||
server = uv.new_tcp()
|
||||
assert(server:bind(options.host, options.port))
|
||||
else
|
||||
server = uv.new_pipe(false)
|
||||
assert(server:bind(options.path))
|
||||
end
|
||||
assert(server:listen(256, function (err)
|
||||
assert(not err, err)
|
||||
local socket = options.isTcp and uv.new_tcp() or uv.new_pipe(false)
|
||||
server:accept(socket)
|
||||
coroutine.wrap(function ()
|
||||
local success, failure = xpcall(function ()
|
||||
local dsocket
|
||||
if options.tls then
|
||||
if not secureSocket then secureSocket = require('secure-socket') end
|
||||
dsocket = assert(secureSocket(socket, options.tls))
|
||||
dsocket.socket = socket
|
||||
else
|
||||
dsocket = socket
|
||||
end
|
||||
|
||||
local read, write = wrapStream(dsocket)
|
||||
local updateDecoder, updateEncoder
|
||||
if options.scan then
|
||||
-- TODO: should we expose updateScan somehow?
|
||||
read = merger(read, options.scan)
|
||||
end
|
||||
if options.decode then
|
||||
read, updateDecoder = decoder(read, options.decode)
|
||||
end
|
||||
if options.encode then
|
||||
write, updateEncoder = encoder(write, options.encode)
|
||||
end
|
||||
|
||||
return onConnect(read, write, dsocket, updateDecoder, updateEncoder)
|
||||
end, debug.traceback)
|
||||
if not success then
|
||||
print(failure)
|
||||
end
|
||||
end)()
|
||||
end))
|
||||
return server
|
||||
end
|
||||
|
||||
return {
|
||||
makeCallback = makeCallback,
|
||||
connect = connect,
|
||||
createServer = createServer,
|
||||
}
|
||||
196
Lua/rpbot/deps/coro-websocket.lua
Normal file
196
Lua/rpbot/deps/coro-websocket.lua
Normal file
@@ -0,0 +1,196 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/coro-websocket"
|
||||
version = "3.1.0"
|
||||
dependencies = {
|
||||
"luvit/http-codec@3.0.0",
|
||||
"creationix/websocket-codec@3.0.0",
|
||||
"creationix/coro-net@3.0.0",
|
||||
}
|
||||
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua"
|
||||
description = "Websocket helpers assuming coro style I/O."
|
||||
tags = {"coro", "websocket"}
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
local uv = require('uv')
|
||||
local httpCodec = require('http-codec')
|
||||
local websocketCodec = require('websocket-codec')
|
||||
local net = require('coro-net')
|
||||
|
||||
local function parseUrl(url)
|
||||
local protocol, host, port, pathname = string.match(url, "^(wss?)://([^:/]+):?(%d*)(/?[^#?]*)")
|
||||
local tls
|
||||
if protocol == "ws" then
|
||||
port = tonumber(port) or 80
|
||||
tls = false
|
||||
elseif protocol == "wss" then
|
||||
port = tonumber(port) or 443
|
||||
tls = true
|
||||
else
|
||||
return nil, "Sorry, only ws:// or wss:// protocols supported"
|
||||
end
|
||||
return {
|
||||
host = host,
|
||||
port = port,
|
||||
tls = tls,
|
||||
pathname = pathname
|
||||
}
|
||||
end
|
||||
|
||||
local function wrapIo(rawRead, rawWrite, options)
|
||||
|
||||
local closeSent = false
|
||||
|
||||
local timer
|
||||
|
||||
local function cleanup()
|
||||
if timer then
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
timer = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function write(message)
|
||||
if message then
|
||||
message.mask = options.mask
|
||||
if message.opcode == 8 then
|
||||
closeSent = true
|
||||
rawWrite(message)
|
||||
cleanup()
|
||||
return rawWrite()
|
||||
end
|
||||
else
|
||||
if not closeSent then
|
||||
return write({
|
||||
opcode = 8,
|
||||
payload = ""
|
||||
})
|
||||
end
|
||||
end
|
||||
return rawWrite(message)
|
||||
end
|
||||
|
||||
|
||||
local function read()
|
||||
while true do
|
||||
local message = rawRead()
|
||||
if not message then
|
||||
return cleanup()
|
||||
end
|
||||
if message.opcode < 8 then
|
||||
return message
|
||||
end
|
||||
if not closeSent then
|
||||
if message.opcode == 8 then
|
||||
write {
|
||||
opcode = 8,
|
||||
payload = message.payload
|
||||
}
|
||||
elseif message.opcode == 9 then
|
||||
write {
|
||||
opcode = 10,
|
||||
payload = message.payload
|
||||
}
|
||||
end
|
||||
return message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if options.heartbeat then
|
||||
local interval = options.heartbeat
|
||||
timer = uv.new_timer()
|
||||
timer:unref()
|
||||
timer:start(interval, interval, function ()
|
||||
coroutine.wrap(function ()
|
||||
local success, err = write {
|
||||
opcode = 10,
|
||||
payload = ""
|
||||
}
|
||||
if not success then
|
||||
timer:close()
|
||||
print(err)
|
||||
end
|
||||
end)()
|
||||
end)
|
||||
end
|
||||
|
||||
return read, write
|
||||
end
|
||||
|
||||
-- options table to configure connection
|
||||
-- options.path
|
||||
-- options.host
|
||||
-- options.port
|
||||
-- options.tls
|
||||
-- options.pathname
|
||||
-- options.subprotocol
|
||||
-- options.headers (as list of header/value pairs)
|
||||
-- options.timeout
|
||||
-- options.heartbeat
|
||||
-- returns res, read, write (res.socket has socket)
|
||||
local function connect(options)
|
||||
options = options or {}
|
||||
local config = {
|
||||
path = options.path,
|
||||
host = options.host,
|
||||
port = options.port,
|
||||
tls = options.tls,
|
||||
encode = httpCodec.encoder(),
|
||||
decode = httpCodec.decoder(),
|
||||
}
|
||||
local read, write, socket, updateDecoder, updateEncoder
|
||||
= net.connect(config, options.timeout or 10000)
|
||||
if not read then
|
||||
return nil, write
|
||||
end
|
||||
|
||||
local res
|
||||
|
||||
local success, err = websocketCodec.handshake({
|
||||
host = options.host,
|
||||
path = options.pathname,
|
||||
protocol = options.subprotocol
|
||||
}, function (req)
|
||||
local headers = options.headers
|
||||
if headers then
|
||||
for i = 1, #headers do
|
||||
req[#req + 1] = headers[i]
|
||||
end
|
||||
end
|
||||
write(req)
|
||||
res = read()
|
||||
if not res then error("Missing server response") end
|
||||
if res.code == 400 then
|
||||
-- p { req = req, res = res }
|
||||
local reason = read() or res.reason
|
||||
error("Invalid request: " .. reason)
|
||||
end
|
||||
return res
|
||||
end)
|
||||
if not success then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
-- Upgrade the protocol to websocket
|
||||
updateDecoder(websocketCodec.decode)
|
||||
updateEncoder(websocketCodec.encode)
|
||||
|
||||
read, write = wrapIo(read, write, {
|
||||
mask = true,
|
||||
heartbeat = options.heartbeat
|
||||
})
|
||||
|
||||
res.socket = socket
|
||||
return res, read, write
|
||||
|
||||
end
|
||||
|
||||
return {
|
||||
parseUrl = parseUrl,
|
||||
wrapIo = wrapIo,
|
||||
connect = connect,
|
||||
}
|
||||
151
Lua/rpbot/deps/coro-wrapper.lua
Normal file
151
Lua/rpbot/deps/coro-wrapper.lua
Normal file
@@ -0,0 +1,151 @@
|
||||
--[[lit-meta
|
||||
name = "creationix/coro-wrapper"
|
||||
version = "3.1.0"
|
||||
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-wrapper.lua"
|
||||
description = "An adapter for applying decoders to coro-streams."
|
||||
tags = {"coro", "decoder", "adapter"}
|
||||
license = "MIT"
|
||||
author = { name = "Tim Caswell" }
|
||||
]]
|
||||
|
||||
local concat = table.concat
|
||||
local sub = string.sub
|
||||
|
||||
-- Merger allows for effecient merging of many chunks.
|
||||
-- The scan function returns truthy when the chunk contains a useful delimeter
|
||||
-- Or in other words, when there is enough data to flush to the decoder.
|
||||
-- merger(read, scan) -> read, updateScan
|
||||
-- read() -> chunk or nil
|
||||
-- scan(chunk) -> should_flush
|
||||
-- updateScan(scan)
|
||||
local function merger(read, scan)
|
||||
local parts = {}
|
||||
|
||||
-- Return a new read function that combines chunks smartly
|
||||
return function ()
|
||||
|
||||
while true do
|
||||
-- Read the next event from upstream.
|
||||
local chunk = read()
|
||||
|
||||
-- We got an EOS (end of stream)
|
||||
if not chunk then
|
||||
-- If there is nothing left to flush, emit EOS here.
|
||||
if #parts == 0 then return end
|
||||
|
||||
-- Flush the buffer
|
||||
chunk = concat(parts)
|
||||
parts = {}
|
||||
return chunk
|
||||
end
|
||||
|
||||
-- Accumulate the chunk
|
||||
parts[#parts + 1] = chunk
|
||||
|
||||
-- Flush the buffer if scan tells us to.
|
||||
if scan(chunk) then
|
||||
chunk = concat(parts)
|
||||
parts = {}
|
||||
return chunk
|
||||
end
|
||||
|
||||
end
|
||||
end,
|
||||
|
||||
-- This is used to update or disable the scan function. It's useful for
|
||||
-- protocols that change mid-stream (like HTTP upgrades in websockets)
|
||||
function (newScan)
|
||||
scan = newScan
|
||||
end
|
||||
end
|
||||
|
||||
-- Decoder takes in a read function and a decode function and returns a new
|
||||
-- read function that emits decoded events. When decode returns `nil` it means
|
||||
-- that it needs more data before it can parse. The index output in decode is
|
||||
-- the index to start the next decode. If output index if nil it means nothing
|
||||
-- is leftover and next decode starts fresh.
|
||||
-- decoder(read, decode) -> read, updateDecode
|
||||
-- read() -> chunk or nil
|
||||
-- decode(chunk, index) -> nil or (data, index)
|
||||
-- updateDecode(Decode)
|
||||
local function decoder(read, decode)
|
||||
local buffer, index
|
||||
local want = true
|
||||
return function ()
|
||||
|
||||
while true do
|
||||
-- If there isn't enough data to decode then get more data.
|
||||
if want then
|
||||
local chunk = read()
|
||||
if buffer then
|
||||
-- If we had leftover data in the old buffer, trim it down.
|
||||
if index > 1 then
|
||||
buffer = sub(buffer, index)
|
||||
index = 1
|
||||
end
|
||||
if chunk then
|
||||
-- Concatenate the chunk with the old data
|
||||
buffer = buffer .. chunk
|
||||
end
|
||||
else
|
||||
-- If there was no leftover data, set new data in the buffer
|
||||
if chunk then
|
||||
buffer = chunk
|
||||
index = 1
|
||||
else
|
||||
buffer = nil
|
||||
index = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Return nil if the buffer is empty
|
||||
if buffer == '' or buffer == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- If we have data, lets try to decode it
|
||||
local item, newIndex = decode(buffer, index)
|
||||
|
||||
want = not newIndex
|
||||
if item or newIndex then
|
||||
-- There was enough data to emit an event!
|
||||
if newIndex then
|
||||
assert(type(newIndex) == "number", "index must be a number if set")
|
||||
-- There was leftover data
|
||||
index = newIndex
|
||||
else
|
||||
want = true
|
||||
-- There was no leftover data
|
||||
buffer = nil
|
||||
index = nil
|
||||
end
|
||||
-- Emit the event
|
||||
return item
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end,
|
||||
function (newDecode)
|
||||
decode = newDecode
|
||||
end
|
||||
end
|
||||
|
||||
local function encoder(write, encode)
|
||||
return function (item)
|
||||
if not item then
|
||||
return write()
|
||||
end
|
||||
return write(encode(item))
|
||||
end,
|
||||
function (newEncode)
|
||||
encode = newEncode
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
merger = merger,
|
||||
decoder = decoder,
|
||||
encoder = encoder,
|
||||
}
|
||||
236
Lua/rpbot/deps/discordia/docgen.lua
Normal file
236
Lua/rpbot/deps/discordia/docgen.lua
Normal file
@@ -0,0 +1,236 @@
|
||||
local fs = require('fs')
|
||||
local pathjoin = require('pathjoin')
|
||||
|
||||
local insert, sort, concat = table.insert, table.sort, table.concat
|
||||
local format = string.format
|
||||
local pathJoin = pathjoin.pathJoin
|
||||
|
||||
local function scan(dir)
|
||||
for fileName, fileType in fs.scandirSync(dir) do
|
||||
local path = pathJoin(dir, fileName)
|
||||
if fileType == 'file' then
|
||||
coroutine.yield(path)
|
||||
else
|
||||
scan(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checkType(docstring, token)
|
||||
return docstring:find(token) == 1
|
||||
end
|
||||
|
||||
local function match(s, pattern) -- only useful for one return value
|
||||
return assert(s:match(pattern), s)
|
||||
end
|
||||
|
||||
local docs = {}
|
||||
|
||||
for f in coroutine.wrap(function() scan('./libs') end) do
|
||||
|
||||
local d = assert(fs.readFileSync(f))
|
||||
|
||||
local class = {
|
||||
methods = {},
|
||||
statics = {},
|
||||
properties = {},
|
||||
parents = {},
|
||||
}
|
||||
|
||||
for s in d:gmatch('--%[=%[%s*(.-)%s*%]=%]') do
|
||||
|
||||
if checkType(s, '@i?c') then
|
||||
|
||||
class.name = match(s, '@i?c (%w+)')
|
||||
class.userInitialized = checkType(s, '@ic')
|
||||
for parent in s:gmatch('x (%w+)') do
|
||||
insert(class.parents, parent)
|
||||
end
|
||||
class.desc = match(s, '@d (.+)'):gsub('\r?\n', ' ')
|
||||
class.parameters = {}
|
||||
for optional, paramName, paramType in s:gmatch('@(o?)p ([%w%p]+)%s+([%w%p]+)') do
|
||||
insert(class.parameters, {paramName, paramType, optional == 'o'})
|
||||
end
|
||||
|
||||
elseif checkType(s, '@s?m') then
|
||||
|
||||
local method = {parameters = {}}
|
||||
method.name = match(s, '@s?m ([%w%p]+)')
|
||||
for optional, paramName, paramType in s:gmatch('@(o?)p ([%w%p]+)%s+([%w%p]+)') do
|
||||
insert(method.parameters, {paramName, paramType, optional == 'o'})
|
||||
end
|
||||
method.returnType = match(s, '@r ([%w%p]+)')
|
||||
method.desc = match(s, '@d (.+)'):gsub('\r?\n', ' ')
|
||||
insert(checkType(s, '@sm') and class.statics or class.methods, method)
|
||||
|
||||
elseif checkType(s, '@p') then
|
||||
|
||||
local propertyName, propertyType, propertyDesc = s:match('@p (%w+)%s+([%w%p]+)%s+(.+)')
|
||||
assert(propertyName, s); assert(propertyType, s); assert(propertyDesc, s)
|
||||
propertyDesc = propertyDesc:gsub('\r?\n', ' ')
|
||||
insert(class.properties, {
|
||||
name = propertyName,
|
||||
type = propertyType,
|
||||
desc = propertyDesc,
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if class.name then
|
||||
docs[class.name] = class
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function link(str)
|
||||
local ret = {}
|
||||
for t in str:gmatch('[^/]+') do
|
||||
insert(ret, docs[t] and format('[[%s]]', t) or t)
|
||||
end
|
||||
return concat(ret, '/')
|
||||
end
|
||||
|
||||
local function sorter(a, b)
|
||||
return a.name < b.name
|
||||
end
|
||||
|
||||
local function writeProperties(f, properties)
|
||||
sort(properties, sorter)
|
||||
f:write('| Name | Type | Description |\n')
|
||||
f:write('|-|-|-|\n')
|
||||
for _, v in ipairs(properties) do
|
||||
f:write('| ', v.name, ' | ', link(v.type), ' | ', v.desc, ' |\n')
|
||||
end
|
||||
end
|
||||
|
||||
local function writeParameters(f, parameters)
|
||||
f:write('(')
|
||||
local optional
|
||||
if parameters[1] then
|
||||
for i, param in ipairs(parameters) do
|
||||
f:write(param[1])
|
||||
if i < #parameters then
|
||||
f:write(', ')
|
||||
end
|
||||
if param[3] then
|
||||
optional = true
|
||||
end
|
||||
end
|
||||
f:write(')\n')
|
||||
if optional then
|
||||
f:write('>| Parameter | Type | Optional |\n')
|
||||
f:write('>|-|-|:-:|\n')
|
||||
for _, param in ipairs(parameters) do
|
||||
local o = param[3] and '✔' or ''
|
||||
f:write('>| ', param[1], ' | ', param[2], ' | ', o, ' |\n')
|
||||
end
|
||||
else
|
||||
f:write('>| Parameter | Type |\n')
|
||||
f:write('>|-|-|\n')
|
||||
for _, param in ipairs(parameters) do
|
||||
f:write('>| ', param[1], ' | ', link(param[2]), '|\n')
|
||||
end
|
||||
end
|
||||
else
|
||||
f:write(')\n')
|
||||
end
|
||||
end
|
||||
|
||||
local function writeMethods(f, methods)
|
||||
sort(methods, sorter)
|
||||
for _, method in ipairs(methods) do
|
||||
f:write('### ', method.name)
|
||||
writeParameters(f, method.parameters)
|
||||
f:write('>\n>', method.desc, '\n>\n')
|
||||
f:write('>Returns: ', link(method.returnType), '\n\n')
|
||||
end
|
||||
end
|
||||
|
||||
if not fs.existsSync('docs') then
|
||||
fs.mkdirSync('docs')
|
||||
end
|
||||
|
||||
local function clean(input, seen)
|
||||
local fields = {}
|
||||
for _, field in ipairs(input) do
|
||||
if not seen[field.name] then
|
||||
insert(fields, field)
|
||||
end
|
||||
end
|
||||
return fields
|
||||
end
|
||||
|
||||
for _, class in pairs(docs) do
|
||||
|
||||
local seen = {}
|
||||
for _, v in pairs(class.properties) do seen[v.name] = true end
|
||||
for _, v in pairs(class.statics) do seen[v.name] = true end
|
||||
for _, v in pairs(class.methods) do seen[v.name] = true end
|
||||
|
||||
local f = io.open(pathJoin('docs', class.name .. '.md'), 'w')
|
||||
|
||||
if next(class.parents) then
|
||||
f:write('#### *extends ', '[[', concat(class.parents, ']], [['), ']]*\n\n')
|
||||
end
|
||||
|
||||
f:write(class.desc, '\n\n')
|
||||
|
||||
if class.userInitialized then
|
||||
f:write('## Constructor\n\n')
|
||||
f:write('### ', class.name)
|
||||
writeParameters(f, class.parameters)
|
||||
f:write('\n')
|
||||
else
|
||||
f:write('*Instances of this class should not be constructed by users.*\n\n')
|
||||
end
|
||||
|
||||
for _, parent in ipairs(class.parents) do
|
||||
if docs[parent] and next(docs[parent].properties) then
|
||||
local properties = docs[parent].properties
|
||||
if next(properties) then
|
||||
f:write('## Properties Inherited From ', link(parent), '\n\n')
|
||||
writeProperties(f, clean(properties, seen))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if next(class.properties) then
|
||||
f:write('## Properties\n\n')
|
||||
writeProperties(f, class.properties)
|
||||
end
|
||||
|
||||
for _, parent in ipairs(class.parents) do
|
||||
if docs[parent] and next(docs[parent].statics) then
|
||||
local statics = docs[parent].statics
|
||||
if next(statics) then
|
||||
f:write('## Static Methods Inherited From ', link(parent), '\n\n')
|
||||
writeMethods(f, clean(statics, seen))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, parent in ipairs(class.parents) do
|
||||
if docs[parent] and next(docs[parent].methods) then
|
||||
local methods = docs[parent].methods
|
||||
if next(methods) then
|
||||
f:write('## Methods Inherited From ', link(parent), '\n\n')
|
||||
writeMethods(f, clean(methods, seen))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if next(class.statics) then
|
||||
f:write('## Static Methods\n\n')
|
||||
writeMethods(f, class.statics)
|
||||
end
|
||||
|
||||
if next(class.methods) then
|
||||
f:write('## Methods\n\n')
|
||||
writeMethods(f, class.methods)
|
||||
end
|
||||
|
||||
f:close()
|
||||
|
||||
end
|
||||
28
Lua/rpbot/deps/discordia/examples/appender.lua
Normal file
28
Lua/rpbot/deps/discordia/examples/appender.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
local discordia = require("discordia")
|
||||
local client = discordia.Client()
|
||||
|
||||
local lines = {} -- blank table of messages
|
||||
|
||||
client:on("ready", function() -- bot is ready
|
||||
print("Logged in as " .. client.user.username)
|
||||
end)
|
||||
|
||||
client:on("messageCreate", function(message)
|
||||
|
||||
local content = message.content
|
||||
local author = message.author
|
||||
|
||||
if author == client.user then return end -- the bot should not append its own messages
|
||||
|
||||
if content == "!lines" then -- if the lines command is activated
|
||||
message.channel:send {
|
||||
file = {"lines.txt", table.concat(lines, "\n")} -- concatenate and send the collected lines in a file
|
||||
}
|
||||
lines = {} -- empty the lines table
|
||||
else -- if the lines command is NOT activated
|
||||
table.insert(lines, content) -- append the message as a new line
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
client:run("Bot BOT_TOKEN") -- replace BOT_TOKEN with your bot token
|
||||
25
Lua/rpbot/deps/discordia/examples/basicCommands.lua
Normal file
25
Lua/rpbot/deps/discordia/examples/basicCommands.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local discordia = require("discordia")
|
||||
local client = discordia.Client()
|
||||
|
||||
discordia.extensions() -- load all helpful extensions
|
||||
|
||||
client:on("ready", function() -- bot is ready
|
||||
print("Logged in as " .. client.user.username)
|
||||
end)
|
||||
|
||||
client:on("messageCreate", function(message)
|
||||
|
||||
local content = message.content
|
||||
local args = content:split(" ") -- split all arguments into a table
|
||||
|
||||
if args[1] == "!ping" then
|
||||
message:reply("Pong!")
|
||||
elseif args[1] == "!echo" then
|
||||
table.remove(args, 1) -- remove the first argument (!echo) from the table
|
||||
message:reply(table.concat(args, " ")) -- concatenate the arguments into a string, then reply with it
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
|
||||
client:run("Bot BOT_TOKEN") -- replace BOT_TOKEN with your bot token
|
||||
45
Lua/rpbot/deps/discordia/examples/embed.lua
Normal file
45
Lua/rpbot/deps/discordia/examples/embed.lua
Normal file
@@ -0,0 +1,45 @@
|
||||
local discordia = require("discordia")
|
||||
local client = discordia.Client()
|
||||
|
||||
|
||||
client:on("ready", function() -- bot is ready
|
||||
print("Logged in as " .. client.user.username)
|
||||
end)
|
||||
|
||||
client:on("messageCreate", function(message)
|
||||
|
||||
local content = message.content
|
||||
local author = message.author
|
||||
|
||||
if content == "!embed" then
|
||||
message:reply {
|
||||
embed = {
|
||||
title = "Embed Title",
|
||||
description = "Here is my fancy description!",
|
||||
author = {
|
||||
name = author.username,
|
||||
icon_url = author.avatarURL
|
||||
},
|
||||
fields = { -- array of fields
|
||||
{
|
||||
name = "Field 1",
|
||||
value = "This is some information",
|
||||
inline = true
|
||||
},
|
||||
{
|
||||
name = "Field 2",
|
||||
value = "This is some more information",
|
||||
inline = false
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
text = "Created with Discordia"
|
||||
},
|
||||
color = 0x000000 -- hex color code
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
client:run("Bot BOT_TOKEN") -- replace BOT_TOKEN with your bot token
|
||||
44
Lua/rpbot/deps/discordia/examples/helpCommandExample.lua
Normal file
44
Lua/rpbot/deps/discordia/examples/helpCommandExample.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local discordia = require('discordia')
|
||||
local client = discordia.Client()
|
||||
discordia.extensions() -- load all helpful extensions
|
||||
|
||||
local prefix = "."
|
||||
local commands = {
|
||||
[prefix .. "ping"] = {
|
||||
description = "Answers with pong.",
|
||||
exec = function(message)
|
||||
message.channel:send("Pong!")
|
||||
end
|
||||
},
|
||||
[prefix .. "hello"] = {
|
||||
description = "Answers with world.",
|
||||
exec = function(message)
|
||||
message.channel:send("world!")
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
client:on('ready', function()
|
||||
p(string.format('Logged in as %s', client.user.username))
|
||||
end)
|
||||
|
||||
client:on("messageCreate", function(message)
|
||||
local args = message.content:split(" ") -- split all arguments into a table
|
||||
|
||||
local command = commands[prefix..args[1]]
|
||||
if command then -- ping or hello
|
||||
command.exec(message) -- execute the command
|
||||
end
|
||||
|
||||
if args[1] == prefix.."help" then -- display all the commands
|
||||
local output = {}
|
||||
for word, tbl in pairs(commands) do
|
||||
table.insert(output, "Command: " .. word .. "\nDescription: " .. tbl.description)
|
||||
end
|
||||
|
||||
message:reply(table.concat(output, "\n\n"))
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
client:run("Bot BOT_TOKEN") -- replace BOT_TOKEN with your bot token
|
||||
20
Lua/rpbot/deps/discordia/examples/pingPong.lua
Normal file
20
Lua/rpbot/deps/discordia/examples/pingPong.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
local discordia = require("discordia")
|
||||
local client = discordia.Client()
|
||||
|
||||
client:on("ready", function() -- bot is ready
|
||||
print("Logged in as " .. client.user.username)
|
||||
end)
|
||||
|
||||
client:on("messageCreate", function(message)
|
||||
|
||||
local content = message.content
|
||||
|
||||
if content == "!ping" then
|
||||
message:reply("Pong!")
|
||||
elseif content == "!pong" then
|
||||
message:reply("Ping!")
|
||||
end
|
||||
|
||||
end)
|
||||
|
||||
client:run("Bot BOT_TOKEN") -- replace BOT_TOKEN with your bot token
|
||||
18
Lua/rpbot/deps/discordia/init.lua
Normal file
18
Lua/rpbot/deps/discordia/init.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
return {
|
||||
class = require('class'),
|
||||
enums = require('enums'),
|
||||
extensions = require('extensions'),
|
||||
package = require('./package.lua'),
|
||||
Client = require('client/Client'),
|
||||
Clock = require('utils/Clock'),
|
||||
Color = require('utils/Color'),
|
||||
Date = require('utils/Date'),
|
||||
Deque = require('utils/Deque'),
|
||||
Emitter = require('utils/Emitter'),
|
||||
Logger = require('utils/Logger'),
|
||||
Mutex = require('utils/Mutex'),
|
||||
Permissions = require('utils/Permissions'),
|
||||
Stopwatch = require('utils/Stopwatch'),
|
||||
Time = require('utils/Time'),
|
||||
storage = {},
|
||||
}
|
||||
165
Lua/rpbot/deps/discordia/libs/class.lua
Normal file
165
Lua/rpbot/deps/discordia/libs/class.lua
Normal file
@@ -0,0 +1,165 @@
|
||||
local format = string.format
|
||||
|
||||
local meta = {}
|
||||
local names = {}
|
||||
local classes = {}
|
||||
local objects = setmetatable({}, {__mode = 'k'})
|
||||
|
||||
function meta:__call(...)
|
||||
local obj = setmetatable({}, self)
|
||||
objects[obj] = true
|
||||
obj:__init(...)
|
||||
return obj
|
||||
end
|
||||
|
||||
function meta:__tostring()
|
||||
return 'class ' .. self.__name
|
||||
end
|
||||
|
||||
local default = {}
|
||||
|
||||
function default:__tostring()
|
||||
return self.__name
|
||||
end
|
||||
|
||||
function default:__hash()
|
||||
return self
|
||||
end
|
||||
|
||||
local function isClass(cls)
|
||||
return classes[cls]
|
||||
end
|
||||
|
||||
local function isObject(obj)
|
||||
return objects[obj]
|
||||
end
|
||||
|
||||
local function isSubclass(sub, cls)
|
||||
if isClass(sub) and isClass(cls) then
|
||||
if sub == cls then
|
||||
return true
|
||||
else
|
||||
for _, base in ipairs(sub.__bases) do
|
||||
if isSubclass(base, cls) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function isInstance(obj, cls)
|
||||
return isObject(obj) and isSubclass(obj.__class, cls)
|
||||
end
|
||||
|
||||
local function profile()
|
||||
local ret = setmetatable({}, {__index = function() return 0 end})
|
||||
for obj in pairs(objects) do
|
||||
local name = obj.__name
|
||||
ret[name] = ret[name] + 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local types = {['string'] = true, ['number'] = true, ['boolean'] = true}
|
||||
|
||||
local function _getPrimitive(v)
|
||||
return types[type(v)] and v or v ~= nil and tostring(v) or nil
|
||||
end
|
||||
|
||||
local function serialize(obj)
|
||||
if isObject(obj) then
|
||||
local ret = {}
|
||||
for k, v in pairs(obj.__getters) do
|
||||
ret[k] = _getPrimitive(v(obj))
|
||||
end
|
||||
return ret
|
||||
else
|
||||
return _getPrimitive(obj)
|
||||
end
|
||||
end
|
||||
|
||||
local rawtype = type
|
||||
local function type(obj)
|
||||
return isObject(obj) and obj.__name or rawtype(obj)
|
||||
end
|
||||
|
||||
return setmetatable({
|
||||
|
||||
classes = names,
|
||||
isClass = isClass,
|
||||
isObject = isObject,
|
||||
isSubclass = isSubclass,
|
||||
isInstance = isInstance,
|
||||
type = type,
|
||||
profile = profile,
|
||||
serialize = serialize,
|
||||
|
||||
}, {__call = function(_, name, ...)
|
||||
|
||||
if names[name] then return error(format('Class %q already defined', name)) end
|
||||
|
||||
local class = setmetatable({}, meta)
|
||||
classes[class] = true
|
||||
|
||||
for k, v in pairs(default) do
|
||||
class[k] = v
|
||||
end
|
||||
|
||||
local bases = {...}
|
||||
local getters = {}
|
||||
local setters = {}
|
||||
|
||||
for _, base in ipairs(bases) do
|
||||
for k1, v1 in pairs(base) do
|
||||
class[k1] = v1
|
||||
for k2, v2 in pairs(base.__getters) do
|
||||
getters[k2] = v2
|
||||
end
|
||||
for k2, v2 in pairs(base.__setters) do
|
||||
setters[k2] = v2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class.__name = name
|
||||
class.__class = class
|
||||
class.__bases = bases
|
||||
class.__getters = getters
|
||||
class.__setters = setters
|
||||
|
||||
local pool = {}
|
||||
local n = #pool
|
||||
|
||||
function class:__index(k)
|
||||
if getters[k] then
|
||||
return getters[k](self)
|
||||
elseif pool[k] then
|
||||
return rawget(self, pool[k])
|
||||
else
|
||||
return class[k]
|
||||
end
|
||||
end
|
||||
|
||||
function class:__newindex(k, v)
|
||||
if setters[k] then
|
||||
return setters[k](self, v)
|
||||
elseif class[k] or getters[k] then
|
||||
return error(format('Cannot overwrite protected property: %s.%s', name, k))
|
||||
elseif k:find('_', 1, true) ~= 1 then
|
||||
return error(format('Cannot write property to object without leading underscore: %s.%s', name, k))
|
||||
else
|
||||
if not pool[k] then
|
||||
n = n + 1
|
||||
pool[k] = n
|
||||
end
|
||||
return rawset(self, pool[k], v)
|
||||
end
|
||||
end
|
||||
|
||||
names[name] = class
|
||||
|
||||
return class, getters, setters
|
||||
|
||||
end})
|
||||
717
Lua/rpbot/deps/discordia/libs/client/API.lua
Normal file
717
Lua/rpbot/deps/discordia/libs/client/API.lua
Normal file
@@ -0,0 +1,717 @@
|
||||
local json = require('json')
|
||||
local timer = require('timer')
|
||||
local http = require('coro-http')
|
||||
local package = require('../../package.lua')
|
||||
local Date = require('utils/Date')
|
||||
local Mutex = require('utils/Mutex')
|
||||
local endpoints = require('endpoints')
|
||||
|
||||
local request = http.request
|
||||
local f, gsub, byte = string.format, string.gsub, string.byte
|
||||
local max, random = math.max, math.random
|
||||
local encode, decode, null = json.encode, json.decode, json.null
|
||||
local insert, concat = table.insert, table.concat
|
||||
local difftime = os.difftime
|
||||
local sleep = timer.sleep
|
||||
local running = coroutine.running
|
||||
|
||||
local BASE_URL = "https://discordapp.com/api/v7"
|
||||
|
||||
local BOUNDARY1 = 'Discordia' .. os.time()
|
||||
local BOUNDARY2 = '--' .. BOUNDARY1
|
||||
local BOUNDARY3 = BOUNDARY2 .. '--'
|
||||
|
||||
local JSON = 'application/json'
|
||||
local MULTIPART = f('multipart/form-data;boundary=%s', BOUNDARY1)
|
||||
local USER_AGENT = f('DiscordBot (%s, %s)', package.homepage, package.version)
|
||||
|
||||
local majorRoutes = {guilds = true, channels = true, webhooks = true}
|
||||
local payloadRequired = {PUT = true, PATCH = true, POST = true}
|
||||
|
||||
local parseDate = Date.parseHeader
|
||||
|
||||
local function parseErrors(ret, errors, key)
|
||||
for k, v in pairs(errors) do
|
||||
if k == '_errors' then
|
||||
for _, err in ipairs(v) do
|
||||
insert(ret, f('%s in %s : %s', err.code, key or 'payload', err.message))
|
||||
end
|
||||
else
|
||||
if key then
|
||||
parseErrors(ret, v, f(k:find("^[%a_][%a%d_]*$") and '%s.%s' or tonumber(k) and '%s[%d]' or '%s[%q]', key, k))
|
||||
else
|
||||
parseErrors(ret, v, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
return concat(ret, '\n\t')
|
||||
end
|
||||
|
||||
local function sub(path)
|
||||
return not majorRoutes[path] and path .. '/:id'
|
||||
end
|
||||
|
||||
local function route(method, endpoint)
|
||||
|
||||
-- special case for reactions
|
||||
if endpoint:find('reactions') then
|
||||
endpoint = endpoint:match('.*/reactions')
|
||||
end
|
||||
|
||||
-- remove the ID from minor routes
|
||||
endpoint = endpoint:gsub('(%a+)/%d+', sub)
|
||||
|
||||
-- special case for message deletions
|
||||
if method == 'DELETE' then
|
||||
local i, j = endpoint:find('/channels/%d+/messages')
|
||||
if i == 1 and j == #endpoint then
|
||||
endpoint = method .. endpoint
|
||||
end
|
||||
end
|
||||
|
||||
return endpoint
|
||||
|
||||
end
|
||||
|
||||
local function attachFiles(payload, files)
|
||||
local ret = {
|
||||
BOUNDARY2,
|
||||
'Content-Disposition:form-data;name="payload_json"',
|
||||
'Content-Type:application/json\r\n',
|
||||
payload,
|
||||
}
|
||||
for i, v in ipairs(files) do
|
||||
insert(ret, BOUNDARY2)
|
||||
insert(ret, f('Content-Disposition:form-data;name="file%i";filename=%q', i, v[1]))
|
||||
insert(ret, 'Content-Type:application/octet-stream\r\n')
|
||||
insert(ret, v[2])
|
||||
end
|
||||
insert(ret, BOUNDARY3)
|
||||
return concat(ret, '\r\n')
|
||||
end
|
||||
|
||||
local mutexMeta = {
|
||||
__mode = 'v',
|
||||
__index = function(self, k)
|
||||
self[k] = Mutex()
|
||||
return self[k]
|
||||
end
|
||||
}
|
||||
|
||||
local function tohex(char)
|
||||
return f('%%%02X', byte(char))
|
||||
end
|
||||
|
||||
local function urlencode(obj)
|
||||
return (gsub(tostring(obj), '%W', tohex))
|
||||
end
|
||||
|
||||
local API = require('class')('API')
|
||||
|
||||
function API:__init(client)
|
||||
self._client = client
|
||||
self._headers = {
|
||||
{'User-Agent', USER_AGENT}
|
||||
}
|
||||
self._mutexes = setmetatable({}, mutexMeta)
|
||||
end
|
||||
|
||||
function API:authenticate(token)
|
||||
self._headers = {
|
||||
{'Authorization', token},
|
||||
{'User-Agent', USER_AGENT},
|
||||
}
|
||||
return self:getCurrentUser()
|
||||
end
|
||||
|
||||
function API:request(method, endpoint, payload, query, files)
|
||||
|
||||
local _, main = running()
|
||||
if main then
|
||||
return error('Cannot make HTTP request outside of a coroutine', 2)
|
||||
end
|
||||
|
||||
local url = BASE_URL .. endpoint
|
||||
|
||||
if query and next(query) then
|
||||
url = {url}
|
||||
for k, v in pairs(query) do
|
||||
insert(url, #url == 1 and '?' or '&')
|
||||
insert(url, urlencode(k))
|
||||
insert(url, '=')
|
||||
insert(url, urlencode(v))
|
||||
end
|
||||
url = concat(url)
|
||||
end
|
||||
|
||||
local req
|
||||
if payloadRequired[method] then
|
||||
payload = payload and encode(payload) or '{}'
|
||||
req = {}
|
||||
for i, v in ipairs(self._headers) do
|
||||
req[i] = v
|
||||
end
|
||||
if files and next(files) then
|
||||
payload = attachFiles(payload, files)
|
||||
insert(req, {'Content-Type', MULTIPART})
|
||||
else
|
||||
insert(req, {'Content-Type', JSON})
|
||||
end
|
||||
insert(req, {'Content-Length', #payload})
|
||||
else
|
||||
req = self._headers
|
||||
end
|
||||
|
||||
local mutex = self._mutexes[route(method, endpoint)]
|
||||
|
||||
mutex:lock()
|
||||
local data, err, delay = self:commit(method, url, req, payload, 0)
|
||||
mutex:unlockAfter(delay)
|
||||
|
||||
if data then
|
||||
return data
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function API:commit(method, url, req, payload, retries)
|
||||
|
||||
local client = self._client
|
||||
local options = client._options
|
||||
local delay = options.routeDelay
|
||||
|
||||
local success, res, msg = pcall(request, method, url, req, payload)
|
||||
|
||||
if not success then
|
||||
return nil, res, delay
|
||||
end
|
||||
|
||||
for i, v in ipairs(res) do
|
||||
res[v[1]] = v[2]
|
||||
res[i] = nil
|
||||
end
|
||||
|
||||
local reset = res['X-RateLimit-Reset']
|
||||
local remaining = res['X-RateLimit-Remaining']
|
||||
|
||||
if reset and remaining == '0' then
|
||||
local dt = difftime(reset, parseDate(res['Date']))
|
||||
delay = max(1000 * dt, delay)
|
||||
end
|
||||
|
||||
local data = res['Content-Type'] == JSON and decode(msg, 1, null) or msg
|
||||
|
||||
if res.code < 300 then
|
||||
|
||||
client:debug('%i - %s : %s %s', res.code, res.reason, method, url)
|
||||
return data, nil, delay
|
||||
|
||||
else
|
||||
|
||||
if type(data) == 'table' then
|
||||
|
||||
local retry
|
||||
if res.code == 429 then -- TODO: global ratelimiting
|
||||
delay = data.retry_after
|
||||
retry = retries < options.maxRetries
|
||||
elseif res.code == 502 then
|
||||
delay = delay + random(2000)
|
||||
retry = retries < options.maxRetries
|
||||
end
|
||||
|
||||
if retry then
|
||||
client:warning('%i - %s : retrying after %i ms : %s %s', res.code, res.reason, delay, method, url)
|
||||
sleep(delay)
|
||||
return self:commit(method, url, req, payload, retries + 1)
|
||||
end
|
||||
|
||||
if data.code and data.message then
|
||||
msg = f('HTTP Error %i : %s', data.code, data.message)
|
||||
else
|
||||
msg = 'HTTP Error'
|
||||
end
|
||||
if data.errors then
|
||||
msg = parseErrors({msg}, data.errors)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
client:error('%i - %s : %s %s', res.code, res.reason, method, url)
|
||||
return nil, msg, delay
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- start of auto-generated methods --
|
||||
|
||||
function API:getGuildAuditLog(guild_id, query)
|
||||
local endpoint = f(endpoints.GUILD_AUDIT_LOGS, guild_id)
|
||||
return self:request("GET", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:getChannel(channel_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.CHANNEL, channel_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:modifyChannel(channel_id, payload) -- Channel:_modify
|
||||
local endpoint = f(endpoints.CHANNEL, channel_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteChannel(channel_id) -- Channel:delete
|
||||
local endpoint = f(endpoints.CHANNEL, channel_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:getChannelMessages(channel_id, query) -- TextChannel:get[First|Last]Message, TextChannel:getMessages
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGES, channel_id)
|
||||
return self:request("GET", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:getChannelMessage(channel_id, message_id) -- TextChannel:getMessage fallback
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE, channel_id, message_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createMessage(channel_id, payload, files) -- TextChannel:send
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGES, channel_id)
|
||||
return self:request("POST", endpoint, payload, nil, files)
|
||||
end
|
||||
|
||||
function API:createReaction(channel_id, message_id, emoji, payload) -- Message:addReaction
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE_REACTION_ME, channel_id, message_id, emoji)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteOwnReaction(channel_id, message_id, emoji) -- Message:removeReaction
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE_REACTION_ME, channel_id, message_id, emoji)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:deleteUserReaction(channel_id, message_id, emoji, user_id) -- Message:removeReaction
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE_REACTION_USER, channel_id, message_id, emoji, user_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:getReactions(channel_id, message_id, emoji, query) -- Reaction:getUsers
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE_REACTION, channel_id, message_id, emoji)
|
||||
return self:request("GET", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:deleteAllReactions(channel_id, message_id) -- Message:clearReactions
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE_REACTIONS, channel_id, message_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:editMessage(channel_id, message_id, payload) -- Message:_modify
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE, channel_id, message_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteMessage(channel_id, message_id) -- Message:delete
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGE, channel_id, message_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:bulkDeleteMessages(channel_id, payload) -- GuildTextChannel:bulkDelete
|
||||
local endpoint = f(endpoints.CHANNEL_MESSAGES_BULK_DELETE, channel_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:editChannelPermissions(channel_id, overwrite_id, payload) -- various PermissionOverwrite methods
|
||||
local endpoint = f(endpoints.CHANNEL_PERMISSION, channel_id, overwrite_id)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getChannelInvites(channel_id) -- GuildChannel:getInvites
|
||||
local endpoint = f(endpoints.CHANNEL_INVITES, channel_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createChannelInvite(channel_id, payload) -- GuildChannel:createInvite
|
||||
local endpoint = f(endpoints.CHANNEL_INVITES, channel_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteChannelPermission(channel_id, overwrite_id) -- PermissionOverwrite:delete
|
||||
local endpoint = f(endpoints.CHANNEL_PERMISSION, channel_id, overwrite_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:triggerTypingIndicator(channel_id, payload) -- TextChannel:broadcastTyping
|
||||
local endpoint = f(endpoints.CHANNEL_TYPING, channel_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getPinnedMessages(channel_id) -- TextChannel:getPinnedMessages
|
||||
local endpoint = f(endpoints.CHANNEL_PINS, channel_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:addPinnedChannelMessage(channel_id, message_id, payload) -- Message:pin
|
||||
local endpoint = f(endpoints.CHANNEL_PIN, channel_id, message_id)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deletePinnedChannelMessage(channel_id, message_id) -- Message:unpin
|
||||
local endpoint = f(endpoints.CHANNEL_PIN, channel_id, message_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:groupDMAddRecipient(channel_id, user_id, payload) -- GroupChannel:addRecipient
|
||||
local endpoint = f(endpoints.CHANNEL_RECIPIENT, channel_id, user_id)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:groupDMRemoveRecipient(channel_id, user_id) -- GroupChannel:removeRecipient
|
||||
local endpoint = f(endpoints.CHANNEL_RECIPIENT, channel_id, user_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:listGuildEmojis(guild_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD_EMOJIS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildEmoji(guild_id, emoji_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD_EMOJI, guild_id, emoji_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuildEmoji(guild_id, payload) -- Guild:createEmoji
|
||||
local endpoint = f(endpoints.GUILD_EMOJIS, guild_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildEmoji(guild_id, emoji_id, payload) -- Emoji:_modify
|
||||
local endpoint = f(endpoints.GUILD_EMOJI, guild_id, emoji_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteGuildEmoji(guild_id, emoji_id) -- Emoji:delete
|
||||
local endpoint = f(endpoints.GUILD_EMOJI, guild_id, emoji_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuild(payload) -- Client:createGuild
|
||||
local endpoint = endpoints.GUILDS
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getGuild(guild_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:modifyGuild(guild_id, payload) -- Guild:_modify
|
||||
local endpoint = f(endpoints.GUILD, guild_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteGuild(guild_id) -- Guild:delete
|
||||
local endpoint = f(endpoints.GUILD, guild_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildChannels(guild_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD_CHANNELS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuildChannel(guild_id, payload) -- Guild:create[Text|Voice]Channel
|
||||
local endpoint = f(endpoints.GUILD_CHANNELS, guild_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildChannelPositions(guild_id, payload) -- GuildChannel:move[Up|Down]
|
||||
local endpoint = f(endpoints.GUILD_CHANNELS, guild_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getGuildMember(guild_id, user_id) -- Guild:getMember fallback
|
||||
local endpoint = f(endpoints.GUILD_MEMBER, guild_id, user_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:listGuildMembers(guild_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD_MEMBERS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:addGuildMember(guild_id, user_id, payload) -- not exposed, limited use
|
||||
local endpoint = f(endpoints.GUILD_MEMBER, guild_id, user_id)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildMember(guild_id, user_id, payload) -- various Member methods
|
||||
local endpoint = f(endpoints.GUILD_MEMBER, guild_id, user_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyCurrentUsersNick(guild_id, payload) -- Member:setNickname
|
||||
local endpoint = f(endpoints.GUILD_MEMBER_ME_NICK, guild_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:addGuildMemberRole(guild_id, user_id, role_id, payload) -- Member:addrole
|
||||
local endpoint = f(endpoints.GUILD_MEMBER_ROLE, guild_id, user_id, role_id)
|
||||
return self:request("PUT", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:removeGuildMemberRole(guild_id, user_id, role_id) -- Member:removeRole
|
||||
local endpoint = f(endpoints.GUILD_MEMBER_ROLE, guild_id, user_id, role_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:removeGuildMember(guild_id, user_id, query) -- Guild:kickUser
|
||||
local endpoint = f(endpoints.GUILD_MEMBER, guild_id, user_id)
|
||||
return self:request("DELETE", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:getGuildBans(guild_id) -- Guild:getBans
|
||||
local endpoint = f(endpoints.GUILD_BANS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildBan(guild_id, user_id) -- Guild:getBan
|
||||
local endpoint = f(endpoints.GUILD_BAN, guild_id, user_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuildBan(guild_id, user_id, query) -- Guild:banUser
|
||||
local endpoint = f(endpoints.GUILD_BAN, guild_id, user_id)
|
||||
return self:request("PUT", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:removeGuildBan(guild_id, user_id, query) -- Guild:unbanUser / Ban:delete
|
||||
local endpoint = f(endpoints.GUILD_BAN, guild_id, user_id)
|
||||
return self:request("DELETE", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:getGuildRoles(guild_id) -- not exposed, use cache
|
||||
local endpoint = f(endpoints.GUILD_ROLES, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuildRole(guild_id, payload) -- Guild:createRole
|
||||
local endpoint = f(endpoints.GUILD_ROLES, guild_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildRolePositions(guild_id, payload) -- Role:move[Up|Down]
|
||||
local endpoint = f(endpoints.GUILD_ROLES, guild_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildRole(guild_id, role_id, payload) -- Role:_modify
|
||||
local endpoint = f(endpoints.GUILD_ROLE, guild_id, role_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteGuildRole(guild_id, role_id) -- Role:delete
|
||||
local endpoint = f(endpoints.GUILD_ROLE, guild_id, role_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildPruneCount(guild_id, query) -- Guild:getPruneCount
|
||||
local endpoint = f(endpoints.GUILD_PRUNE, guild_id)
|
||||
return self:request("GET", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:beginGuildPrune(guild_id, payload, query) -- Guild:pruneMembers
|
||||
local endpoint = f(endpoints.GUILD_PRUNE, guild_id)
|
||||
return self:request("POST", endpoint, payload, query)
|
||||
end
|
||||
|
||||
function API:getGuildVoiceRegions(guild_id) -- Guild:listVoiceRegions
|
||||
local endpoint = f(endpoints.GUILD_REGIONS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildInvites(guild_id) -- Guild:getInvites
|
||||
local endpoint = f(endpoints.GUILD_INVITES, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildIntegrations(guild_id) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_INTEGRATIONS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createGuildIntegration(guild_id, payload) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_INTEGRATIONS, guild_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyGuildIntegration(guild_id, integration_id, payload) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_INTEGRATION, guild_id, integration_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteGuildIntegration(guild_id, integration_id) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_INTEGRATION, guild_id, integration_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:syncGuildIntegration(guild_id, integration_id, payload) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_INTEGRATION_SYNC, guild_id, integration_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getGuildEmbed(guild_id) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_EMBED, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:modifyGuildEmbed(guild_id, payload) -- not exposed, maybe in the future
|
||||
local endpoint = f(endpoints.GUILD_EMBED, guild_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getInvite(invite_code, query) -- Client:getInvite
|
||||
local endpoint = f(endpoints.INVITE, invite_code)
|
||||
return self:request("GET", endpoint, nil, query)
|
||||
end
|
||||
|
||||
function API:deleteInvite(invite_code) -- Invite:delete
|
||||
local endpoint = f(endpoints.INVITE, invite_code)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:acceptInvite(invite_code, payload) -- not exposed, invalidates tokens
|
||||
local endpoint = f(endpoints.INVITE, invite_code)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getCurrentUser() -- API:authenticate
|
||||
local endpoint = endpoints.USER_ME
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getUser(user_id) -- Client:getUser
|
||||
local endpoint = f(endpoints.USER, user_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:modifyCurrentUser(payload) -- Client:_modify
|
||||
local endpoint = endpoints.USER_ME
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getCurrentUserGuilds() -- not exposed, use cache
|
||||
local endpoint = endpoints.USER_ME_GUILDS
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:leaveGuild(guild_id) -- Guild:leave
|
||||
local endpoint = f(endpoints.USER_ME_GUILD, guild_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:getUserDMs() -- not exposed, use cache
|
||||
local endpoint = endpoints.USER_ME_CHANNELS
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createDM(payload) -- User:getPrivateChannel fallback
|
||||
local endpoint = endpoints.USER_ME_CHANNELS
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:createGroupDM(payload) -- Client:createGroupChannel
|
||||
local endpoint = endpoints.USER_ME_CHANNELS
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getUsersConnections() -- Client:getConnections
|
||||
local endpoint = endpoints.USER_ME_CONNECTIONS
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:listVoiceRegions() -- Client:listVoiceRegions
|
||||
local endpoint = endpoints.VOICE_REGIONS
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:createWebhook(channel_id, payload) -- GuildTextChannel:createWebhook
|
||||
local endpoint = f(endpoints.CHANNEL_WEBHOOKS, channel_id)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getChannelWebhooks(channel_id) -- GuildTextChannel:getWebhooks
|
||||
local endpoint = f(endpoints.CHANNEL_WEBHOOKS, channel_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGuildWebhooks(guild_id) -- Guild:getWebhooks
|
||||
local endpoint = f(endpoints.GUILD_WEBHOOKS, guild_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getWebhook(webhook_id) -- Client:getWebhook
|
||||
local endpoint = f(endpoints.WEBHOOK, webhook_id)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getWebhookWithToken(webhook_id, webhook_token) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN, webhook_id, webhook_token)
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:modifyWebhook(webhook_id, payload) -- Webhook:_modify
|
||||
local endpoint = f(endpoints.WEBHOOK, webhook_id)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:modifyWebhookWithToken(webhook_id, webhook_token, payload) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN, webhook_id, webhook_token)
|
||||
return self:request("PATCH", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:deleteWebhook(webhook_id) -- Webhook:delete
|
||||
local endpoint = f(endpoints.WEBHOOK, webhook_id)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:deleteWebhookWithToken(webhook_id, webhook_token) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN, webhook_id, webhook_token)
|
||||
return self:request("DELETE", endpoint)
|
||||
end
|
||||
|
||||
function API:executeWebhook(webhook_id, webhook_token, payload) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN, webhook_id, webhook_token)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:executeSlackCompatibleWebhook(webhook_id, webhook_token, payload) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN_SLACK, webhook_id, webhook_token)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:executeGitHubCompatibleWebhook(webhook_id, webhook_token, payload) -- not exposed, needs webhook client
|
||||
local endpoint = f(endpoints.WEBHOOK_TOKEN_GITHUB, webhook_id, webhook_token)
|
||||
return self:request("POST", endpoint, payload)
|
||||
end
|
||||
|
||||
function API:getGateway() -- Client:run
|
||||
local endpoint = endpoints.GATEWAY
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getGatewayBot() -- Client:run
|
||||
local endpoint = endpoints.GATEWAY_BOT
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
function API:getCurrentApplicationInformation() -- Client:run
|
||||
local endpoint = endpoints.OAUTH2_APPLICATION_ME
|
||||
return self:request("GET", endpoint)
|
||||
end
|
||||
|
||||
-- end of auto-generated methods --
|
||||
|
||||
return API
|
||||
646
Lua/rpbot/deps/discordia/libs/client/Client.lua
Normal file
646
Lua/rpbot/deps/discordia/libs/client/Client.lua
Normal file
@@ -0,0 +1,646 @@
|
||||
--[=[
|
||||
@ic Client x Emitter
|
||||
@op options table
|
||||
@d The main point of entry into a Discordia application. All data relevant to
|
||||
Discord are accessible through a client instance or its child objects after a
|
||||
connection to Discord is established with the `run` method. In other words,
|
||||
client data should not be expected and most client methods should not be called
|
||||
until after the `ready` event is received. Base emitter methods may be called
|
||||
at any time. See [[client options]].
|
||||
]=]
|
||||
|
||||
local fs = require('fs')
|
||||
local json = require('json')
|
||||
|
||||
local constants = require('constants')
|
||||
local enums = require('enums')
|
||||
local package = require('../../package.lua')
|
||||
|
||||
local API = require('client/API')
|
||||
local Shard = require('client/Shard')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local GroupChannel = require('containers/GroupChannel')
|
||||
local Guild = require('containers/Guild')
|
||||
local PrivateChannel = require('containers/PrivateChannel')
|
||||
local User = require('containers/User')
|
||||
local Invite = require('containers/Invite')
|
||||
local Webhook = require('containers/Webhook')
|
||||
local Relationship = require('containers/Relationship')
|
||||
|
||||
local Cache = require('iterables/Cache')
|
||||
local WeakCache = require('iterables/WeakCache')
|
||||
local Emitter = require('utils/Emitter')
|
||||
local Logger = require('utils/Logger')
|
||||
local Mutex = require('utils/Mutex')
|
||||
|
||||
local VoiceManager = require('voice/VoiceManager')
|
||||
|
||||
local encode, decode, null = json.encode, json.decode, json.null
|
||||
local readFileSync, writeFileSync = fs.readFileSync, fs.writeFileSync
|
||||
|
||||
local logLevel = enums.logLevel
|
||||
local gameType = enums.gameType
|
||||
|
||||
local wrap = coroutine.wrap
|
||||
local time, difftime = os.time, os.difftime
|
||||
local format = string.format
|
||||
|
||||
local CACHE_AGE = constants.CACHE_AGE
|
||||
local GATEWAY_VERSION = constants.GATEWAY_VERSION
|
||||
|
||||
-- do not change these options here
|
||||
-- pass a custom table on client initialization instead
|
||||
local defaultOptions = {
|
||||
routeDelay = 300,
|
||||
maxRetries = 5,
|
||||
shardCount = 0,
|
||||
firstShard = 0,
|
||||
lastShard = -1,
|
||||
largeThreshold = 100,
|
||||
cacheAllMembers = false,
|
||||
autoReconnect = true,
|
||||
compress = true,
|
||||
bitrate = 64000,
|
||||
logFile = 'discordia.log',
|
||||
logLevel = logLevel.info,
|
||||
dateTime = '%F %T',
|
||||
syncGuilds = false,
|
||||
}
|
||||
|
||||
local function parseOptions(customOptions)
|
||||
if type(customOptions) == 'table' then
|
||||
local options = {}
|
||||
for k, default in pairs(defaultOptions) do -- load options
|
||||
local custom = customOptions[k]
|
||||
if custom ~= nil then
|
||||
options[k] = custom
|
||||
else
|
||||
options[k] = default
|
||||
end
|
||||
end
|
||||
for k, v in pairs(customOptions) do -- validate options
|
||||
local default = type(defaultOptions[k])
|
||||
local custom = type(v)
|
||||
if default ~= custom then
|
||||
return error(format('invalid client option %q (%s expected, got %s)', k, default, custom), 3)
|
||||
end
|
||||
if custom == 'number' and (v < 0 or v % 1 ~= 0) then
|
||||
return error(format('invalid client option %q (number must be a positive integer)', k), 3)
|
||||
end
|
||||
end
|
||||
return options
|
||||
else
|
||||
return defaultOptions
|
||||
end
|
||||
end
|
||||
|
||||
local Client, get = require('class')('Client', Emitter)
|
||||
|
||||
function Client:__init(options)
|
||||
Emitter.__init(self)
|
||||
options = parseOptions(options)
|
||||
self._options = options
|
||||
self._shards = {}
|
||||
self._api = API(self)
|
||||
self._mutex = Mutex()
|
||||
self._users = Cache({}, User, self)
|
||||
self._guilds = Cache({}, Guild, self)
|
||||
self._group_channels = Cache({}, GroupChannel, self)
|
||||
self._private_channels = Cache({}, PrivateChannel, self)
|
||||
self._relationships = Cache({}, Relationship, self)
|
||||
self._webhooks = WeakCache({}, Webhook, self) -- used for audit logs
|
||||
self._logger = Logger(options.logLevel, options.dateTime, options.logFile)
|
||||
self._voice = VoiceManager(self)
|
||||
self._role_map = {}
|
||||
self._emoji_map = {}
|
||||
self._channel_map = {}
|
||||
self._events = require('client/EventHandler')
|
||||
end
|
||||
|
||||
for name, level in pairs(logLevel) do
|
||||
Client[name] = function(self, fmt, ...)
|
||||
local msg = self._logger:log(level, fmt, ...)
|
||||
return self:emit(name, msg or format(fmt, ...))
|
||||
end
|
||||
end
|
||||
|
||||
function Client:_deprecated(clsName, before, after)
|
||||
local info = debug.getinfo(3)
|
||||
return self:warning(
|
||||
'%s:%s: %s.%s is deprecated; use %s.%s instead',
|
||||
info.short_src,
|
||||
info.currentline,
|
||||
clsName,
|
||||
before,
|
||||
clsName,
|
||||
after
|
||||
)
|
||||
end
|
||||
|
||||
local function run(self, token)
|
||||
|
||||
self:info('Discordia %s', package.version)
|
||||
self:info('Connecting to Discord...')
|
||||
|
||||
local api = self._api
|
||||
local users = self._users
|
||||
local options = self._options
|
||||
|
||||
local user, err1 = api:authenticate(token)
|
||||
if not user then
|
||||
return self:error('Could not authenticate, check token: ' .. err1)
|
||||
end
|
||||
self._user = users:_insert(user)
|
||||
self._token = token
|
||||
|
||||
self:info('Authenticated as %s#%s', user.username, user.discriminator)
|
||||
|
||||
local now = time()
|
||||
local url, count, owner
|
||||
|
||||
local cache = readFileSync('gateway.json')
|
||||
cache = cache and decode(cache)
|
||||
|
||||
if cache then
|
||||
local d = cache[user.id]
|
||||
if d and difftime(now, d.timestamp) < CACHE_AGE then
|
||||
url = cache.url
|
||||
if user.bot then
|
||||
count = d.shards
|
||||
owner = d.owner
|
||||
else
|
||||
count = 1
|
||||
owner = user
|
||||
end
|
||||
end
|
||||
else
|
||||
cache = {}
|
||||
end
|
||||
|
||||
if not url or not owner then
|
||||
|
||||
if user.bot then
|
||||
|
||||
local gateway, err2 = api:getGatewayBot()
|
||||
if not gateway then
|
||||
return self:error('Could not get gateway: ' .. err2)
|
||||
end
|
||||
|
||||
local app, err3 = api:getCurrentApplicationInformation()
|
||||
if not app then
|
||||
return self:error('Could not get application information: ' .. err3)
|
||||
end
|
||||
|
||||
url = gateway.url
|
||||
count = gateway.shards
|
||||
owner = app.owner
|
||||
|
||||
cache[user.id] = {owner = owner, shards = count, timestamp = now}
|
||||
|
||||
else
|
||||
|
||||
local gateway, err2 = api:getGateway()
|
||||
if not gateway then
|
||||
return self:error('Could not get gateway: ' .. err2)
|
||||
end
|
||||
|
||||
url = gateway.url
|
||||
count = 1
|
||||
owner = user
|
||||
|
||||
cache[user.id] = {timestamp = now}
|
||||
|
||||
end
|
||||
|
||||
cache.url = url
|
||||
|
||||
writeFileSync('gateway.json', encode(cache))
|
||||
|
||||
end
|
||||
|
||||
self._owner = users:_insert(owner)
|
||||
|
||||
if options.shardCount > 0 then
|
||||
if count ~= options.shardCount then
|
||||
self:warning('Requested shard count (%i) is different from recommended count (%i)', options.shardCount, count)
|
||||
end
|
||||
count = options.shardCount
|
||||
end
|
||||
|
||||
local first, last = options.firstShard, options.lastShard
|
||||
|
||||
if last < 0 then
|
||||
last = count - 1
|
||||
end
|
||||
|
||||
if last < first then
|
||||
return self:error('First shard ID (%i) is greater than last shard ID (%i)', first, last)
|
||||
end
|
||||
|
||||
local d = last - first + 1
|
||||
if d > count then
|
||||
return self:error('Shard count (%i) is less than target shard range (%i)', count, d)
|
||||
end
|
||||
|
||||
if first == last then
|
||||
self:info('Launching shard %i (%i out of %i)...', first, d, count)
|
||||
else
|
||||
self:info('Launching shards %i through %i (%i out of %i)...', first, last, d, count)
|
||||
end
|
||||
|
||||
self._total_shard_count = count
|
||||
self._shard_count = d
|
||||
|
||||
for id = first, last do
|
||||
self._shards[id] = Shard(id, self)
|
||||
end
|
||||
|
||||
local path = format('/?v=%i&encoding=json', GATEWAY_VERSION)
|
||||
for _, shard in pairs(self._shards) do
|
||||
wrap(shard.connect)(shard, url, path)
|
||||
shard:identifyWait()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m run
|
||||
@p token string
|
||||
@op presence table
|
||||
@r nil
|
||||
@d Authenticates the current user via HTTPS and launches as many WSS gateway
|
||||
shards as are required or requested. By using coroutines that are automatically
|
||||
managed by Luvit libraries and a libuv event loop, multiple clients per process
|
||||
and multiple shards per client can operate concurrently. This should be the last
|
||||
method called after all other code and event handlers have been initialized.
|
||||
]=]
|
||||
function Client:run(token, presence)
|
||||
self._presence = presence or {}
|
||||
return wrap(run)(self, token)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m stop
|
||||
@r nil
|
||||
@d Disconnects all shards and effectively stop their loops. This does not
|
||||
empty any data that the client may have cached.
|
||||
]=]
|
||||
function Client:stop()
|
||||
for _, shard in pairs(self._shards) do
|
||||
shard:disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
function Client:_modify(payload)
|
||||
local data, err = self._api:modifyCurrentUser(payload)
|
||||
if data then
|
||||
data.token = nil
|
||||
self._user:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setUsername
|
||||
@p username string
|
||||
@r boolean
|
||||
@d Sets the client's username. This must be between 2 and 32 characters in
|
||||
length. This does not change the application name.
|
||||
]=]
|
||||
function Client:setUsername(username)
|
||||
return self:_modify({username = username or null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAvatar
|
||||
@p avatar Base64-Resolveable
|
||||
@r boolean
|
||||
@d Sets the client's avatar. To remove the avatar, pass an empty string or nil.
|
||||
This does not change the application image.
|
||||
]=]
|
||||
function Client:setAvatar(avatar)
|
||||
avatar = avatar and Resolver.base64(avatar)
|
||||
return self:_modify({avatar = avatar or null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createGuild
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Creates a new guild. The name must be between 2 and 100 characters in length.
|
||||
This method may not work if the current user is in too many guilds. Note that
|
||||
this does not return the created guild object; wait for the corresponding
|
||||
`guildCreate` event if you need the object.
|
||||
]=]
|
||||
function Client:createGuild(name)
|
||||
local data, err = self._api:createGuild({name = name})
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createGroupChannel
|
||||
@r GroupChannel
|
||||
@d Creates a new group channel. This method is only available for user accounts.
|
||||
]=]
|
||||
function Client:createGroupChannel()
|
||||
local data, err = self._api:createGroupDM()
|
||||
if data then
|
||||
return self._group_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getWebhook
|
||||
@p id string
|
||||
@r Webhook
|
||||
@d Gets a webhook object by ID. This always makes an HTTP request to obtain a
|
||||
static object that is not cached and is not updated by gateway events.
|
||||
]=]
|
||||
function Client:getWebhook(id)
|
||||
local data, err = self._api:getWebhook(id)
|
||||
if data then
|
||||
return Webhook(data, self)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getInvite
|
||||
@p code string
|
||||
@op counts boolean
|
||||
@r Invite
|
||||
@d Gets an invite object by code. This always makes an HTTP request to obtain a
|
||||
static object that is not cached and is not updated by gateway events.
|
||||
]=]
|
||||
function Client:getInvite(code, counts)
|
||||
local data, err = self._api:getInvite(code, counts and {with_counts = true})
|
||||
if data then
|
||||
return Invite(data, self)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getUser
|
||||
@p id User-ID-Resolvable
|
||||
@r User
|
||||
@d Gets a user object by ID. If the object is already cached, then the cached
|
||||
object will be returned; otherwise, an HTTP request is made. Under circumstances
|
||||
which should be rare, the user object may be an old version, not updated by
|
||||
gateway events.
|
||||
]=]
|
||||
function Client:getUser(id)
|
||||
id = Resolver.userId(id)
|
||||
local user = self._users:get(id)
|
||||
if user then
|
||||
return user
|
||||
else
|
||||
local data, err = self._api:getUser(id)
|
||||
if data then
|
||||
return self._users:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getGuild
|
||||
@p id Guild-ID-Resolvable
|
||||
@r Guild
|
||||
@d Gets a guild object by ID. The current user must be in the guild and the client
|
||||
must be running the appropriate shard that serves this guild. This method never
|
||||
makes an HTTP request to obtain a guild.
|
||||
]=]
|
||||
function Client:getGuild(id)
|
||||
id = Resolver.guildId(id)
|
||||
return self._guilds:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getChannel
|
||||
@p id Channel-ID-Resolvable
|
||||
@r Channel
|
||||
@d Gets a channel object by ID. For guild channels, the current user must be in
|
||||
the channel's guild and the client must be running the appropriate shard that
|
||||
serves the channel's guild.
|
||||
|
||||
For private channels, the channel must have been previously opened and cached.
|
||||
If the channel is not cached, `User:getPrivateChannel` should be used instead.
|
||||
]=]
|
||||
function Client:getChannel(id)
|
||||
id = Resolver.channelId(id)
|
||||
local guild = self._channel_map[id]
|
||||
if guild then
|
||||
return guild._text_channels:get(id) or guild._voice_channels:get(id) or guild._categories:get(id)
|
||||
else
|
||||
return self._private_channels:get(id) or self._group_channels:get(id)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r Role
|
||||
@d Gets a role object by ID. The current user must be in the role's guild and
|
||||
the client must be running the appropriate shard that serves the role's guild.
|
||||
]=]
|
||||
function Client:getRole(id)
|
||||
id = Resolver.roleId(id)
|
||||
local guild = self._role_map[id]
|
||||
return guild and guild._roles:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getEmoji
|
||||
@p id Emoji-ID-Resolvable
|
||||
@r Emoji
|
||||
@d Gets an emoji object by ID. The current user must be in the emoji's guild and
|
||||
the client must be running the appropriate shard that serves the emoji's guild.
|
||||
]=]
|
||||
function Client:getEmoji(id)
|
||||
id = Resolver.emojiId(id)
|
||||
local guild = self._emoji_map[id]
|
||||
return guild and guild._emojis:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m listVoiceRegions
|
||||
@r table
|
||||
@d Returns a raw data table that contains a list of voice regions as provided by
|
||||
Discord, with no additional parsing.
|
||||
]=]
|
||||
function Client:listVoiceRegions()
|
||||
return self._api:listVoiceRegions()
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getConnections
|
||||
@r table
|
||||
@d Returns a raw data table that contains a list of connections as provided by
|
||||
Discord, with no additional parsing. This is unrelated to voice connections.
|
||||
]=]
|
||||
function Client:getConnections()
|
||||
return self._api:getUsersConnections()
|
||||
end
|
||||
|
||||
local function updateStatus(self)
|
||||
local presence = self._presence
|
||||
presence.afk = presence.afk or null
|
||||
presence.game = presence.game or null
|
||||
presence.since = presence.since or null
|
||||
presence.status = presence.status or null
|
||||
for _, shard in pairs(self._shards) do
|
||||
shard:updateStatus(presence)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setStatus
|
||||
@p status string
|
||||
@r nil
|
||||
@d Sets the current users's status on all shards that are managed by this client.
|
||||
See the `status` enumeration for acceptable status values.
|
||||
]=]
|
||||
function Client:setStatus(status)
|
||||
if type(status) == 'string' then
|
||||
self._presence.status = status
|
||||
if status == 'idle' then
|
||||
self._presence.since = 1000 * time()
|
||||
else
|
||||
self._presence.since = null
|
||||
end
|
||||
else
|
||||
self._presence.status = null
|
||||
self._presence.since = null
|
||||
end
|
||||
return updateStatus(self)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setGame
|
||||
@p game string/table
|
||||
@r nil
|
||||
@d Sets the current users's game on all shards that are managed by this client.
|
||||
If a string is passed, it is treated as the game name. If a table is passed, it
|
||||
must have a `name` field and may optionally have a `url` field. Pass `nil` to
|
||||
remove the game status.
|
||||
]=]
|
||||
function Client:setGame(game)
|
||||
if type(game) == 'string' then
|
||||
game = {name = game, type = gameType.default}
|
||||
elseif type(game) == 'table' then
|
||||
if type(game.name) == 'string' then
|
||||
if type(game.type) ~= 'number' then
|
||||
if type(game.url) == 'string' then
|
||||
game.type = gameType.streaming
|
||||
else
|
||||
game.type = gameType.default
|
||||
end
|
||||
end
|
||||
else
|
||||
game = null
|
||||
end
|
||||
else
|
||||
game = null
|
||||
end
|
||||
self._presence.game = game
|
||||
return updateStatus(self)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAFK
|
||||
@p afk boolean
|
||||
@r nil
|
||||
@d Set the current user's AFK status on all shards that are managed by this client.
|
||||
This generally applies to user accounts and their push notifications.
|
||||
]=]
|
||||
function Client:setAFK(afk)
|
||||
if type(afk) == 'boolean' then
|
||||
self._presence.afk = afk
|
||||
else
|
||||
self._presence.afk = null
|
||||
end
|
||||
return updateStatus(self)
|
||||
end
|
||||
|
||||
--[=[@p shardCount number/nil The number of shards that this client is managing.]=]
|
||||
function get.shardCount(self)
|
||||
return self._shard_count
|
||||
end
|
||||
|
||||
--[=[@p totalShardCount number/nil The total number of shards that the current user is on.]=]
|
||||
function get.totalShardCount(self)
|
||||
return self._total_shard_count
|
||||
end
|
||||
|
||||
--[=[@p user User/nil User object representing the current user.]=]
|
||||
function get.user(self)
|
||||
return self._user
|
||||
end
|
||||
|
||||
--[=[@p owner User/nil User object representing the current user's owner.]=]
|
||||
function get.owner(self)
|
||||
return self._owner
|
||||
end
|
||||
|
||||
--[=[@p verified boolean/nil Whether the current user's owner's account is verified.]=]
|
||||
function get.verified(self)
|
||||
return self._user and self._user._verified
|
||||
end
|
||||
|
||||
--[=[@p mfaEnabled boolean/nil Whether the current user's owner's account has multi-factor (or two-factor)
|
||||
authentication enabled.]=]
|
||||
function get.mfaEnabled(self)
|
||||
return self._user and self._user._verified
|
||||
end
|
||||
|
||||
--[=[@p email string/nil The current user's owner's account's email address (user-accounts only).]=]
|
||||
function get.email(self)
|
||||
return self._user and self._user._email
|
||||
end
|
||||
|
||||
--[=[@p guilds Cache An iterable cache of all guilds that are visible to the client. Note that the
|
||||
guilds present here correspond to which shards the client is managing. If all
|
||||
shards are managed by one client, then all guilds will be present.]=]
|
||||
function get.guilds(self)
|
||||
return self._guilds
|
||||
end
|
||||
|
||||
--[=[@p users Cache An iterable cache of all users that are visible to the client.
|
||||
To access a user that may exist but is not cached, use `Client:getUser`.]=]
|
||||
function get.users(self)
|
||||
return self._users
|
||||
end
|
||||
|
||||
--[=[@p privateChannels Cache An iterable cache of all private channels that are visible to the client. The
|
||||
channel must exist and must be open for it to be cached here. To access a
|
||||
private channel that may exist but is not cached, `User:getPrivateChannel`.]=]
|
||||
function get.privateChannels(self)
|
||||
return self._private_channels
|
||||
end
|
||||
|
||||
--[=[@p groupChannels Cache An iterable cache of all group channels that are visible to the client. Only
|
||||
user-accounts should have these.]=]
|
||||
function get.groupChannels(self)
|
||||
return self._group_channels
|
||||
end
|
||||
|
||||
--[=[@p relationships Cache An iterable cache of all relationships that are visible to the client. Only
|
||||
user-accounts should have these.]=]
|
||||
function get.relationships(self)
|
||||
return self._relationships
|
||||
end
|
||||
|
||||
return Client
|
||||
540
Lua/rpbot/deps/discordia/libs/client/EventHandler.lua
Normal file
540
Lua/rpbot/deps/discordia/libs/client/EventHandler.lua
Normal file
@@ -0,0 +1,540 @@
|
||||
local enums = require('enums')
|
||||
local json = require('json')
|
||||
|
||||
local channelType = enums.channelType
|
||||
local concat, insert = table.concat, table.insert
|
||||
local null = json.null
|
||||
|
||||
local function warning(client, object, id, event)
|
||||
return client:warning('Uncached %s (%s) on %s', object, id, event)
|
||||
end
|
||||
|
||||
local function checkReady(shard)
|
||||
for _, v in pairs(shard._loading) do
|
||||
if next(v) then return end
|
||||
end
|
||||
shard._ready = true
|
||||
shard._loading = nil
|
||||
collectgarbage()
|
||||
local client = shard._client
|
||||
client:emit('shardReady', shard._id)
|
||||
for _, other in pairs(client._shards) do
|
||||
if not other._ready then return end
|
||||
end
|
||||
return client:emit('ready')
|
||||
end
|
||||
|
||||
local function getChannel(client, id)
|
||||
local guild = client._channel_map[id]
|
||||
if guild then
|
||||
return guild._text_channels:get(id)
|
||||
else
|
||||
return client._private_channels:get(id) or client._group_channels:get(id)
|
||||
end
|
||||
end
|
||||
|
||||
local EventHandler = setmetatable({}, {__index = function(self, k)
|
||||
self[k] = function(_, _, shard)
|
||||
return shard:warning('Unhandled gateway event: %s', k)
|
||||
end
|
||||
return self[k]
|
||||
end})
|
||||
|
||||
function EventHandler.READY(d, client, shard)
|
||||
|
||||
shard:info('Received READY (%s)', concat(d._trace, ', '))
|
||||
shard:emit('READY')
|
||||
|
||||
shard._session_id = d.session_id
|
||||
client._user = client._users:_insert(d.user)
|
||||
|
||||
local guilds = client._guilds
|
||||
local group_channels = client._group_channels
|
||||
local private_channels = client._private_channels
|
||||
local relationships = client._relationships
|
||||
|
||||
for _, channel in ipairs(d.private_channels) do
|
||||
if channel.type == channelType.private then
|
||||
private_channels:_insert(channel)
|
||||
elseif channel.type == channelType.group then
|
||||
group_channels:_insert(channel)
|
||||
end
|
||||
end
|
||||
|
||||
local loading = shard._loading
|
||||
|
||||
if d.user.bot then
|
||||
for _, guild in ipairs(d.guilds) do
|
||||
guilds:_insert(guild)
|
||||
loading.guilds[guild.id] = true
|
||||
end
|
||||
else
|
||||
if client._options.syncGuilds then
|
||||
local ids = {}
|
||||
for _, guild in ipairs(d.guilds) do
|
||||
guilds:_insert(guild)
|
||||
if not guild.unavailable then
|
||||
loading.syncs[guild.id] = true
|
||||
insert(ids, guild.id)
|
||||
end
|
||||
end
|
||||
shard:syncGuilds(ids)
|
||||
else
|
||||
guilds:_load(d.guilds)
|
||||
end
|
||||
end
|
||||
|
||||
relationships:_load(d.relationships)
|
||||
|
||||
for _, presence in ipairs(d.presences) do
|
||||
local relationship = relationships:get(presence.user.id)
|
||||
if relationship then
|
||||
relationship:_loadPresence(presence)
|
||||
end
|
||||
end
|
||||
|
||||
return checkReady(shard)
|
||||
|
||||
end
|
||||
|
||||
function EventHandler.RESUMED(d, client, shard)
|
||||
shard:info('Received RESUMED (%s)', concat(d._trace, ', '))
|
||||
return client:emit('shardResumed', shard._id)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_MEMBERS_CHUNK(d, client, shard)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBERS_CHUNK') end
|
||||
guild._members:_load(d.members)
|
||||
if shard._loading and guild._member_count == #guild._members then
|
||||
shard._loading.chunks[d.guild_id] = nil
|
||||
return checkReady(shard)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_SYNC(d, client, shard)
|
||||
local guild = client._guilds:get(d.id)
|
||||
if not guild then return warning(client, 'Guild', d.id, 'GUILD_SYNC') end
|
||||
guild._large = d.large
|
||||
guild:_loadMembers(d, shard)
|
||||
if shard._loading then
|
||||
shard._loading.syncs[d.id] = nil
|
||||
return checkReady(shard)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_CREATE(d, client)
|
||||
local channel
|
||||
local t = d.type
|
||||
if t == channelType.text then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
|
||||
channel = guild._text_channels:_insert(d)
|
||||
elseif t == channelType.voice then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
|
||||
channel = guild._voice_channels:_insert(d)
|
||||
elseif t == channelType.private then
|
||||
channel = client._private_channels:_insert(d)
|
||||
elseif t == channelType.group then
|
||||
channel = client._group_channels:_insert(d)
|
||||
elseif t == channelType.category then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_CREATE') end
|
||||
channel = guild._categories:_insert(d)
|
||||
else
|
||||
return client:warning('Unhandled CHANNEL_CREATE (type %s)', d.type)
|
||||
end
|
||||
return client:emit('channelCreate', channel)
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_UPDATE(d, client)
|
||||
local channel
|
||||
local t = d.type
|
||||
if t == channelType.text then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
|
||||
channel = guild._text_channels:_insert(d)
|
||||
elseif t == channelType.voice then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
|
||||
channel = guild._voice_channels:_insert(d)
|
||||
elseif t == channelType.private then -- private channels should never update
|
||||
channel = client._private_channels:_insert(d)
|
||||
elseif t == channelType.group then
|
||||
channel = client._group_channels:_insert(d)
|
||||
elseif t == channelType.category then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_UPDATE') end
|
||||
channel = guild._categories:_insert(d)
|
||||
else
|
||||
return client:warning('Unhandled CHANNEL_UPDATE (type %s)', d.type)
|
||||
end
|
||||
return client:emit('channelUpdate', channel)
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_DELETE(d, client)
|
||||
local channel
|
||||
local t = d.type
|
||||
if t == channelType.text then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
|
||||
channel = guild._text_channels:_remove(d)
|
||||
elseif t == channelType.voice then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
|
||||
channel = guild._voice_channels:_remove(d)
|
||||
elseif t == channelType.private then
|
||||
channel = client._private_channels:_remove(d)
|
||||
elseif t == channelType.group then
|
||||
channel = client._group_channels:_remove(d)
|
||||
elseif t == channelType.category then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'CHANNEL_DELETE') end
|
||||
channel = guild._categories:_remove(d)
|
||||
else
|
||||
return client:warning('Unhandled CHANNEL_DELETE (type %s)', d.type)
|
||||
end
|
||||
return client:emit('channelDelete', channel)
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_RECIPIENT_ADD(d, client)
|
||||
local channel = client._group_channels:get(d.channel_id)
|
||||
if not channel then return warning(client, 'GroupChannel', d.channel_id, 'CHANNEL_RECIPIENT_ADD') end
|
||||
local user = channel._recipients:_insert(d.user)
|
||||
return client:emit('recipientAdd', channel, user)
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_RECIPIENT_REMOVE(d, client)
|
||||
local channel = client._group_channels:get(d.channel_id)
|
||||
if not channel then return warning(client, 'GroupChannel', d.channel_id, 'CHANNEL_RECIPIENT_REMOVE') end
|
||||
local user = channel._recipients:_remove(d.user)
|
||||
return client:emit('recipientRemove', channel, user)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_CREATE(d, client, shard)
|
||||
if client._options.syncGuilds and not d.unavailable and not client._user._bot then
|
||||
shard:syncGuilds({d.id})
|
||||
end
|
||||
local guild = client._guilds:get(d.id)
|
||||
if guild then
|
||||
if guild._unavailable and not d.unavailable then
|
||||
guild:_load(d)
|
||||
guild:_makeAvailable(d)
|
||||
client:emit('guildAvailable', guild)
|
||||
end
|
||||
if shard._loading then
|
||||
shard._loading.guilds[d.id] = nil
|
||||
return checkReady(shard)
|
||||
end
|
||||
else
|
||||
guild = client._guilds:_insert(d)
|
||||
return client:emit('guildCreate', guild)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_UPDATE(d, client)
|
||||
local guild = client._guilds:_insert(d)
|
||||
return client:emit('guildUpdate', guild)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_DELETE(d, client)
|
||||
if d.unavailable then
|
||||
local guild = client._guilds:_insert(d)
|
||||
return client:emit('guildUnavailable', guild)
|
||||
else
|
||||
local guild = client._guilds:_remove(d)
|
||||
return client:emit('guildDelete', guild)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_BAN_ADD(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_BAN_ADD') end
|
||||
local user = client._users:_insert(d.user)
|
||||
return client:emit('userBan', user, guild)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_BAN_REMOVE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_BAN_REMOVE') end
|
||||
local user = client._users:_insert(d.user)
|
||||
return client:emit('userUnban', user, guild)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_EMOJIS_UPDATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_EMOJIS_UPDATE') end
|
||||
guild._emojis:_load(d.emojis, true)
|
||||
return client:emit('emojisUpdate', guild)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_MEMBER_ADD(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_ADD') end
|
||||
local member = guild._members:_insert(d)
|
||||
guild._member_count = guild._member_count + 1
|
||||
return client:emit('memberJoin', member)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_MEMBER_UPDATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_UPDATE') end
|
||||
local member = guild._members:_insert(d)
|
||||
return client:emit('memberUpdate', member)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_MEMBER_REMOVE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_MEMBER_REMOVE') end
|
||||
local member = guild._members:_remove(d)
|
||||
guild._member_count = guild._member_count - 1
|
||||
return client:emit('memberLeave', member)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_ROLE_CREATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_CREATE') end
|
||||
local role = guild._roles:_insert(d.role)
|
||||
return client:emit('roleCreate', role)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_ROLE_UPDATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_UPDATE') end
|
||||
local role = guild._roles:_insert(d.role)
|
||||
return client:emit('roleUpdate', role)
|
||||
end
|
||||
|
||||
function EventHandler.GUILD_ROLE_DELETE(d, client) -- role object not provided
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'GUILD_ROLE_DELETE') end
|
||||
local role = guild._roles:_delete(d.role_id)
|
||||
if not role then return warning(client, 'Role', d.role_id, 'GUILD_ROLE_DELETE') end
|
||||
return client:emit('roleDelete', role)
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_CREATE(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_CREATE') end
|
||||
local message = channel._messages:_insert(d)
|
||||
return client:emit('messageCreate', message)
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_UPDATE(d, client) -- may not contain the whole message
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_UPDATE') end
|
||||
local message = channel._messages:get(d.id)
|
||||
if message then
|
||||
message:_setOldContent(d)
|
||||
message:_load(d)
|
||||
return client:emit('messageUpdate', message)
|
||||
else
|
||||
return client:emit('messageUpdateUncached', channel, d.id)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_DELETE(d, client) -- message object not provided
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE') end
|
||||
local message = channel._messages:_delete(d.id)
|
||||
if message then
|
||||
return client:emit('messageDelete', message)
|
||||
else
|
||||
return client:emit('messageDeleteUncached', channel, d.id)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_DELETE_BULK(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_DELETE_BULK') end
|
||||
for _, id in ipairs(d.ids) do
|
||||
local message = channel._messages:_delete(id)
|
||||
if message then
|
||||
client:emit('messageDelete', message)
|
||||
else
|
||||
client:emit('messageDeleteUncached', channel, id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_REACTION_ADD(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_ADD') end
|
||||
local message = channel._messages:get(d.message_id)
|
||||
if message then
|
||||
local reaction = message:_addReaction(d)
|
||||
return client:emit('reactionAdd', reaction, d.user_id)
|
||||
else
|
||||
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
|
||||
return client:emit('reactionAddUncached', channel, d.message_id, k, d.user_id)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_REACTION_REMOVE(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_REMOVE') end
|
||||
local message = channel._messages:get(d.message_id)
|
||||
if message then
|
||||
local reaction = message:_removeReaction(d)
|
||||
if not reaction then -- uncached reaction?
|
||||
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
|
||||
return warning(client, 'Reaction', k, 'MESSAGE_REACTION_REMOVE')
|
||||
end
|
||||
return client:emit('reactionRemove', reaction, d.user_id)
|
||||
else
|
||||
local k = d.emoji.id ~= null and d.emoji.id or d.emoji.name
|
||||
return client:emit('reactionRemoveUncached', channel, d.message_id, k, d.user_id)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.MESSAGE_REACTION_REMOVE_ALL(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'MESSAGE_REACTION_REMOVE_ALL') end
|
||||
local message = channel._messages:get(d.message_id)
|
||||
if message then
|
||||
local reactions = message._reactions
|
||||
if reactions then
|
||||
for reaction in reactions:iter() do
|
||||
reaction._count = 0
|
||||
end
|
||||
message._reactions = nil
|
||||
end
|
||||
return client:emit('reactionRemoveAll', message)
|
||||
else
|
||||
return client:emit('reactionRemoveAllUncached', channel, d.message_id)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.CHANNEL_PINS_UPDATE(d, client)
|
||||
local channel = getChannel(client, d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'CHANNEL_PINS_UPDATE') end
|
||||
return client:emit('pinsUpdate', channel)
|
||||
end
|
||||
|
||||
function EventHandler.PRESENCE_UPDATE(d, client) -- may have incomplete data
|
||||
local user = client._users:get(d.user.id)
|
||||
if user then
|
||||
user:_load(d.user)
|
||||
end
|
||||
if d.guild_id then
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'PRESENCE_UPDATE') end
|
||||
local member
|
||||
if client._options.cacheAllMembers then
|
||||
member = guild._members:get(d.user.id)
|
||||
if not member then return end -- still loading or member left
|
||||
else
|
||||
if d.status == 'offline' then -- uncache offline members
|
||||
member = guild._members:_delete(d.user.id)
|
||||
else
|
||||
if d.user.username then -- member was offline
|
||||
member = guild._members:_insert(d)
|
||||
elseif user then -- member was invisible, user is still cached
|
||||
member = guild._members:_insert(d)
|
||||
member._user = user
|
||||
end
|
||||
end
|
||||
end
|
||||
if member then
|
||||
member:_loadPresence(d)
|
||||
return client:emit('presenceUpdate', member)
|
||||
end
|
||||
else
|
||||
local relationship = client._relationships:get(d.user.id)
|
||||
if relationship then
|
||||
relationship:_loadPresence(d)
|
||||
return client:emit('relationshipUpdate', relationship)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.RELATIONSHIP_ADD(d, client)
|
||||
local relationship = client._relationships:_insert(d)
|
||||
return client:emit('relationshipAdd', relationship)
|
||||
end
|
||||
|
||||
function EventHandler.RELATIONSHIP_REMOVE(d, client)
|
||||
local relationship = client._relationships:_remove(d)
|
||||
return client:emit('relationshipRemove', relationship)
|
||||
end
|
||||
|
||||
function EventHandler.TYPING_START(d, client)
|
||||
return client:emit('typingStart', d.user_id, d.channel_id, d.timestamp)
|
||||
end
|
||||
|
||||
function EventHandler.USER_UPDATE(d, client)
|
||||
client._user:_load(d)
|
||||
return client:emit('userUpdate', client._user)
|
||||
end
|
||||
|
||||
local function load(obj, d)
|
||||
for k, v in pairs(d) do obj[k] = v end
|
||||
end
|
||||
|
||||
function EventHandler.VOICE_STATE_UPDATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'VOICE_STATE_UPDATE') end
|
||||
local member = d.member and guild._members:_insert(d.member) or guild._members:get(d.user_id)
|
||||
if not member then return warning(client, 'Member', d.user_id, 'VOICE_STATE_UPDATE') end
|
||||
local states = guild._voice_states
|
||||
local channels = guild._voice_channels
|
||||
local new_channel_id = d.channel_id
|
||||
local state = states[d.user_id]
|
||||
if state then -- user is already connected
|
||||
local old_channel_id = state.channel_id
|
||||
load(state, d)
|
||||
if new_channel_id ~= null then -- state changed, but user has not disconnected
|
||||
if new_channel_id == old_channel_id then -- user did not change channels
|
||||
client:emit('voiceUpdate', member)
|
||||
else -- user changed channels
|
||||
local old = channels:get(old_channel_id)
|
||||
local new = channels:get(new_channel_id)
|
||||
if d.user_id == client._user._id then -- move connection to new channel
|
||||
local connection = old._connection
|
||||
if connection then
|
||||
new._connection = connection
|
||||
old._connection = nil
|
||||
connection._channel = new
|
||||
connection:_continue(true)
|
||||
end
|
||||
end
|
||||
client:emit('voiceChannelLeave', member, old)
|
||||
client:emit('voiceChannelJoin', member, new)
|
||||
end
|
||||
else -- user has disconnected
|
||||
states[d.user_id] = nil
|
||||
local old = channels:get(old_channel_id)
|
||||
client:emit('voiceChannelLeave', member, old)
|
||||
client:emit('voiceDisconnect', member)
|
||||
end
|
||||
else -- user has connected
|
||||
states[d.user_id] = d
|
||||
local new = channels:get(new_channel_id)
|
||||
client:emit('voiceConnect', member)
|
||||
client:emit('voiceChannelJoin', member, new)
|
||||
end
|
||||
end
|
||||
|
||||
function EventHandler.VOICE_SERVER_UPDATE(d, client)
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'VOICE_SERVER_UPDATE') end
|
||||
local state = guild._voice_states[client._user._id]
|
||||
if not state then return client:warning('Voice state not initialized before VOICE_SERVER_UPDATE') end
|
||||
load(state, d)
|
||||
local channel = guild._voice_channels:get(state.channel_id)
|
||||
if not channel then return warning(client, 'GuildVoiceChannel', state.channel_id, 'VOICE_SERVER_UPDATE') end
|
||||
local connection = channel._connection
|
||||
if not connection then return client:warning('Voice connection not initialized before VOICE_SERVER_UPDATE') end
|
||||
return client._voice:_prepareConnection(state, connection)
|
||||
end
|
||||
|
||||
function EventHandler.WEBHOOKS_UPDATE(d, client) -- webhook object is not provided
|
||||
local guild = client._guilds:get(d.guild_id)
|
||||
if not guild then return warning(client, 'Guild', d.guild_id, 'WEBHOOKS_UDPATE') end
|
||||
local channel = guild._text_channels:get(d.channel_id)
|
||||
if not channel then return warning(client, 'TextChannel', d.channel_id, 'WEBHOOKS_UPDATE') end
|
||||
return client:emit('webhooksUpdate', channel)
|
||||
end
|
||||
|
||||
return EventHandler
|
||||
184
Lua/rpbot/deps/discordia/libs/client/Resolver.lua
Normal file
184
Lua/rpbot/deps/discordia/libs/client/Resolver.lua
Normal file
@@ -0,0 +1,184 @@
|
||||
local fs = require('fs')
|
||||
local ffi = require('ffi')
|
||||
local ssl = require('openssl')
|
||||
local class = require('class')
|
||||
local enums = require('enums')
|
||||
|
||||
local permission = enums.permission
|
||||
local actionType = enums.actionType
|
||||
local base64 = ssl.base64
|
||||
local readFileSync = fs.readFileSync
|
||||
local classes = class.classes
|
||||
local isInstance = class.isInstance
|
||||
local isObject = class.isObject
|
||||
local insert = table.insert
|
||||
local format = string.format
|
||||
|
||||
local Resolver = {}
|
||||
|
||||
local istype = ffi.istype
|
||||
local int64_t = ffi.typeof('int64_t')
|
||||
local uint64_t = ffi.typeof('uint64_t')
|
||||
|
||||
local function int(obj)
|
||||
local t = type(obj)
|
||||
if t == 'string' and tonumber(obj) then
|
||||
return obj
|
||||
elseif t == 'cdata' then
|
||||
if istype(int64_t, obj) or istype(uint64_t, obj) then
|
||||
return tostring(obj):match('%d*')
|
||||
end
|
||||
elseif t == 'number' then
|
||||
return format('%i', obj)
|
||||
elseif isInstance(obj, classes.Date) then
|
||||
return obj:toSnowflake()
|
||||
end
|
||||
end
|
||||
|
||||
function Resolver.userId(obj)
|
||||
if isObject(obj) then
|
||||
if isInstance(obj, classes.User) then
|
||||
return obj.id
|
||||
elseif isInstance(obj, classes.Member) then
|
||||
return obj.user.id
|
||||
elseif isInstance(obj, classes.Message) then
|
||||
return obj.author.id
|
||||
elseif isInstance(obj, classes.Guild) then
|
||||
return obj.ownerId
|
||||
end
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.messageId(obj)
|
||||
if isInstance(obj, classes.Message) then
|
||||
return obj.id
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.channelId(obj)
|
||||
if isInstance(obj, classes.Channel) then
|
||||
return obj.id
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.roleId(obj)
|
||||
if isInstance(obj, classes.Role) then
|
||||
return obj.id
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.emojiId(obj)
|
||||
if isInstance(obj, classes.Emoji) then
|
||||
return obj.id
|
||||
elseif isInstance(obj, classes.Reaction) then
|
||||
return obj.emojiId
|
||||
end
|
||||
return tostring(obj)
|
||||
end
|
||||
|
||||
function Resolver.guildId(obj)
|
||||
if isInstance(obj, classes.Guild) then
|
||||
return obj.id
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.entryId(obj)
|
||||
if isInstance(obj, classes.AuditLogEntry) then
|
||||
return obj.id
|
||||
end
|
||||
return int(obj)
|
||||
end
|
||||
|
||||
function Resolver.messageIds(objs)
|
||||
local ret = {}
|
||||
if isInstance(objs, classes.Iterable) then
|
||||
for obj in objs:iter() do
|
||||
insert(ret, Resolver.messageId(obj))
|
||||
end
|
||||
elseif type(objs) == 'table' then
|
||||
for _, obj in pairs(objs) do
|
||||
insert(ret, Resolver.messageId(obj))
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function Resolver.roleIds(objs)
|
||||
local ret = {}
|
||||
if isInstance(objs, classes.Iterable) then
|
||||
for obj in objs:iter() do
|
||||
insert(ret, Resolver.roleId(obj))
|
||||
end
|
||||
elseif type(objs) == 'table' then
|
||||
for _, obj in pairs(objs) do
|
||||
insert(ret, Resolver.roleId(obj))
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function Resolver.emoji(obj)
|
||||
if isInstance(obj, classes.Emoji) then
|
||||
return obj.hash
|
||||
elseif isInstance(obj, classes.Reaction) then
|
||||
return obj.emojiHash
|
||||
end
|
||||
return tostring(obj)
|
||||
end
|
||||
|
||||
function Resolver.color(obj)
|
||||
if isInstance(obj, classes.Color) then
|
||||
return obj.value
|
||||
end
|
||||
return tonumber(obj)
|
||||
end
|
||||
|
||||
function Resolver.permissions(obj)
|
||||
if isInstance(obj, classes.Permissions) then
|
||||
return obj.value
|
||||
end
|
||||
return tonumber(obj)
|
||||
end
|
||||
|
||||
function Resolver.permission(obj)
|
||||
local t = type(obj)
|
||||
local n = nil
|
||||
if t == 'string' then
|
||||
n = permission[obj]
|
||||
elseif t == 'number' then
|
||||
n = permission(obj) and obj
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
function Resolver.actionType(obj)
|
||||
local t = type(obj)
|
||||
local n = nil
|
||||
if t == 'string' then
|
||||
n = actionType[obj]
|
||||
elseif t == 'number' then
|
||||
n = actionType(obj) and obj
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
function Resolver.base64(obj)
|
||||
if type(obj) == 'string' then
|
||||
if obj:find('data:.*;base64,') == 1 then
|
||||
return obj
|
||||
end
|
||||
local data, err = readFileSync(obj)
|
||||
if not data then
|
||||
return nil, err
|
||||
end
|
||||
return 'data:;base64,' .. base64(data)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return Resolver
|
||||
254
Lua/rpbot/deps/discordia/libs/client/Shard.lua
Normal file
254
Lua/rpbot/deps/discordia/libs/client/Shard.lua
Normal file
@@ -0,0 +1,254 @@
|
||||
local json = require('json')
|
||||
local timer = require('timer')
|
||||
|
||||
local EventHandler = require('client/EventHandler')
|
||||
local WebSocket = require('client/WebSocket')
|
||||
|
||||
local constants = require('constants')
|
||||
local enums = require('enums')
|
||||
|
||||
local logLevel = enums.logLevel
|
||||
local min, max, random = math.min, math.max, math.random
|
||||
local null = json.null
|
||||
local format = string.format
|
||||
local sleep = timer.sleep
|
||||
local setInterval, clearInterval = timer.setInterval, timer.clearInterval
|
||||
local concat = table.concat
|
||||
local wrap = coroutine.wrap
|
||||
|
||||
local ID_DELAY = constants.ID_DELAY
|
||||
|
||||
local DISPATCH = 0
|
||||
local HEARTBEAT = 1
|
||||
local IDENTIFY = 2
|
||||
local STATUS_UPDATE = 3
|
||||
local VOICE_STATE_UPDATE = 4
|
||||
-- local VOICE_SERVER_PING = 5 -- TODO
|
||||
local RESUME = 6
|
||||
local RECONNECT = 7
|
||||
local REQUEST_GUILD_MEMBERS = 8
|
||||
local INVALID_SESSION = 9
|
||||
local HELLO = 10
|
||||
local HEARTBEAT_ACK = 11
|
||||
local GUILD_SYNC = 12
|
||||
|
||||
local ignore = {
|
||||
['CALL_DELETE'] = true,
|
||||
['CHANNEL_PINS_ACK'] = true,
|
||||
['GUILD_INTEGRATIONS_UPDATE'] = true,
|
||||
['MESSAGE_ACK'] = true,
|
||||
['PRESENCES_REPLACE'] = true,
|
||||
['USER_SETTINGS_UPDATE'] = true,
|
||||
['SESSIONS_REPLACE'] = true,
|
||||
}
|
||||
|
||||
local Shard = require('class')('Shard', WebSocket)
|
||||
|
||||
function Shard:__init(id, client)
|
||||
WebSocket.__init(self, client)
|
||||
self._id = id
|
||||
self._client = client
|
||||
self._backoff = 1000
|
||||
end
|
||||
|
||||
for name in pairs(logLevel) do
|
||||
Shard[name] = function(self, fmt, ...)
|
||||
local client = self._client
|
||||
return client[name](client, format('Shard %i : %s', self._id, fmt), ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Shard:__tostring()
|
||||
return format('Shard: %i', self._id)
|
||||
end
|
||||
|
||||
local function getReconnectTime(self, n, m)
|
||||
return self._backoff * (n + random() * (m - n))
|
||||
end
|
||||
|
||||
local function incrementReconnectTime(self)
|
||||
self._backoff = min(self._backoff * 2, 60000)
|
||||
end
|
||||
|
||||
local function decrementReconnectTime(self)
|
||||
self._backoff = max(self._backoff / 2, 1000)
|
||||
end
|
||||
|
||||
function Shard:handleDisconnect(url, path)
|
||||
self._client:emit('shardDisconnect', self._id)
|
||||
if self._reconnect then
|
||||
self:info('Reconnecting...')
|
||||
return self:connect(url, path)
|
||||
elseif self._reconnect == nil and self._client._options.autoReconnect then
|
||||
local backoff = getReconnectTime(self, 0.9, 1.1)
|
||||
incrementReconnectTime(self)
|
||||
self:info('Reconnecting after %i ms...', backoff)
|
||||
sleep(backoff)
|
||||
return self:connect(url, path)
|
||||
end
|
||||
end
|
||||
|
||||
function Shard:handlePayload(payload)
|
||||
|
||||
local client = self._client
|
||||
|
||||
local s = payload.s
|
||||
local t = payload.t
|
||||
local d = payload.d
|
||||
local op = payload.op
|
||||
|
||||
if t ~= null then
|
||||
self:debug('WebSocket OP %s : %s : %s', op, t, s)
|
||||
else
|
||||
self:debug('WebSocket OP %s', op)
|
||||
end
|
||||
|
||||
if op == DISPATCH then
|
||||
|
||||
self._seq = s
|
||||
if not ignore[t] then
|
||||
EventHandler[t](d, client, self)
|
||||
end
|
||||
|
||||
elseif op == HEARTBEAT then
|
||||
|
||||
self:heartbeat()
|
||||
|
||||
elseif op == RECONNECT then
|
||||
|
||||
self:warning('Discord has requested a reconnection')
|
||||
self:disconnect(true)
|
||||
|
||||
elseif op == INVALID_SESSION then
|
||||
|
||||
local session_id = self._session_id
|
||||
self._session_id = nil
|
||||
if payload.d and session_id then
|
||||
self:info('Session invalidated, resuming...')
|
||||
self:resume()
|
||||
else
|
||||
self:info('Session invalidated, re-identifying...')
|
||||
sleep(random(1000, 5000))
|
||||
self:identify()
|
||||
end
|
||||
|
||||
elseif op == HELLO then
|
||||
|
||||
self:info('Received HELLO (%s)', concat(d._trace, ', '))
|
||||
self:startHeartbeat(d.heartbeat_interval)
|
||||
if self._session_id then
|
||||
self:resume()
|
||||
else
|
||||
self:identify()
|
||||
end
|
||||
|
||||
elseif op == HEARTBEAT_ACK then
|
||||
|
||||
client:emit('heartbeat', self._id, self._sw.milliseconds)
|
||||
|
||||
elseif op then
|
||||
|
||||
self:warning('Unhandled WebSocket payload OP %i', op)
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function loop(self)
|
||||
decrementReconnectTime(self)
|
||||
return wrap(self.heartbeat)(self)
|
||||
end
|
||||
|
||||
function Shard:startHeartbeat(interval)
|
||||
if self._heartbeat then
|
||||
clearInterval(self._heartbeat)
|
||||
end
|
||||
self._heartbeat = setInterval(interval, loop, self)
|
||||
end
|
||||
|
||||
function Shard:stopHeartbeat()
|
||||
if self._heartbeat then
|
||||
clearInterval(self._heartbeat)
|
||||
end
|
||||
self._heartbeat = nil
|
||||
end
|
||||
|
||||
function Shard:identifyWait()
|
||||
if self:waitFor('READY', 1.5 * ID_DELAY) then
|
||||
return sleep(ID_DELAY)
|
||||
end
|
||||
end
|
||||
|
||||
function Shard:heartbeat()
|
||||
self._sw:reset()
|
||||
return self:_send(HEARTBEAT, self._seq or json.null)
|
||||
end
|
||||
|
||||
function Shard:identify()
|
||||
|
||||
local client = self._client
|
||||
local mutex = client._mutex
|
||||
local options = client._options
|
||||
|
||||
mutex:lock()
|
||||
wrap(function()
|
||||
self:identifyWait()
|
||||
mutex:unlock()
|
||||
end)()
|
||||
|
||||
self._seq = nil
|
||||
self._session_id = nil
|
||||
self._ready = false
|
||||
self._loading = {guilds = {}, chunks = {}, syncs = {}}
|
||||
|
||||
return self:_send(IDENTIFY, {
|
||||
token = client._token,
|
||||
properties = {
|
||||
['$os'] = jit.os,
|
||||
['$browser'] = 'Discordia',
|
||||
['$device'] = 'Discordia',
|
||||
['$referrer'] = '',
|
||||
['$referring_domain'] = '',
|
||||
},
|
||||
compress = options.compress,
|
||||
large_threshold = options.largeThreshold,
|
||||
shard = {self._id, client._total_shard_count},
|
||||
presence = next(client._presence) and client._presence,
|
||||
}, true)
|
||||
|
||||
end
|
||||
|
||||
function Shard:resume()
|
||||
return self:_send(RESUME, {
|
||||
token = self._client._token,
|
||||
session_id = self._session_id,
|
||||
seq = self._seq
|
||||
})
|
||||
end
|
||||
|
||||
function Shard:requestGuildMembers(id)
|
||||
return self:_send(REQUEST_GUILD_MEMBERS, {
|
||||
guild_id = id,
|
||||
query = '',
|
||||
limit = 0,
|
||||
})
|
||||
end
|
||||
|
||||
function Shard:updateStatus(presence)
|
||||
return self:_send(STATUS_UPDATE, presence)
|
||||
end
|
||||
|
||||
function Shard:updateVoice(guild_id, channel_id, self_mute, self_deaf)
|
||||
return self:_send(VOICE_STATE_UPDATE, {
|
||||
guild_id = guild_id,
|
||||
channel_id = channel_id or null,
|
||||
self_mute = self_mute or false,
|
||||
self_deaf = self_deaf or false,
|
||||
})
|
||||
end
|
||||
|
||||
function Shard:syncGuilds(ids)
|
||||
return self:_send(GUILD_SYNC, ids)
|
||||
end
|
||||
|
||||
return Shard
|
||||
122
Lua/rpbot/deps/discordia/libs/client/WebSocket.lua
Normal file
122
Lua/rpbot/deps/discordia/libs/client/WebSocket.lua
Normal file
@@ -0,0 +1,122 @@
|
||||
local json = require('json')
|
||||
local miniz = require('miniz')
|
||||
local Mutex = require('utils/Mutex')
|
||||
local Emitter = require('utils/Emitter')
|
||||
local Stopwatch = require('utils/Stopwatch')
|
||||
|
||||
local websocket = require('coro-websocket')
|
||||
local constants = require('constants')
|
||||
|
||||
local inflate = miniz.inflate
|
||||
local encode, decode, null = json.encode, json.decode, json.null
|
||||
local ws_parseUrl, ws_connect = websocket.parseUrl, websocket.connect
|
||||
|
||||
local GATEWAY_DELAY = constants.GATEWAY_DELAY
|
||||
|
||||
local TEXT = 1
|
||||
local BINARY = 2
|
||||
local CLOSE = 8
|
||||
|
||||
local function connect(url, path)
|
||||
local options = assert(ws_parseUrl(url))
|
||||
options.pathname = path
|
||||
return assert(ws_connect(options))
|
||||
end
|
||||
|
||||
local WebSocket = require('class')('WebSocket', Emitter)
|
||||
|
||||
function WebSocket:__init(parent)
|
||||
Emitter.__init(self)
|
||||
self._parent = parent
|
||||
self._mutex = Mutex()
|
||||
self._sw = Stopwatch()
|
||||
end
|
||||
|
||||
function WebSocket:connect(url, path)
|
||||
|
||||
local success, res, read, write = pcall(connect, url, path)
|
||||
|
||||
if success then
|
||||
self._read = read
|
||||
self._write = write
|
||||
self._reconnect = nil
|
||||
self:info('Connected to %s', url)
|
||||
local parent = self._parent
|
||||
for message in self._read do
|
||||
local payload, str = self:parseMessage(message)
|
||||
if not payload then break end
|
||||
parent:emit('raw', str)
|
||||
if self.handlePayload then -- virtual method
|
||||
self:handlePayload(payload)
|
||||
end
|
||||
end
|
||||
self:info('Disconnected')
|
||||
else
|
||||
self:error('Could not connect to %s (%s)', url, res) -- TODO: get new url?
|
||||
end
|
||||
|
||||
self._read = nil
|
||||
self._write = nil
|
||||
self._identified = nil
|
||||
|
||||
if self.stopHeartbeat then -- virtual method
|
||||
self:stopHeartbeat()
|
||||
end
|
||||
|
||||
if self.handleDisconnect then -- virtual method
|
||||
return self:handleDisconnect(url, path)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function WebSocket:parseMessage(message)
|
||||
|
||||
local opcode = message.opcode
|
||||
local payload = message.payload
|
||||
|
||||
if opcode == TEXT then
|
||||
|
||||
return decode(payload, 1, null), payload
|
||||
|
||||
elseif opcode == BINARY then
|
||||
|
||||
payload = inflate(payload, 1)
|
||||
return decode(payload, 1, null), payload
|
||||
|
||||
elseif opcode == CLOSE then
|
||||
|
||||
local code, i = ('>H'):unpack(payload)
|
||||
local msg = #payload > i and payload:sub(i) or 'Connection closed'
|
||||
self:warning('%i - %s', code, msg)
|
||||
return nil
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function WebSocket:_send(op, d, identify)
|
||||
self._mutex:lock()
|
||||
local success, err
|
||||
if identify or self._session_id then
|
||||
if self._write then
|
||||
success, err = self._write {opcode = TEXT, payload = encode {op = op, d = d}}
|
||||
else
|
||||
success, err = false, 'Not connected to gateway'
|
||||
end
|
||||
else
|
||||
success, err = false, 'Invalid session'
|
||||
end
|
||||
self._mutex:unlockAfter(GATEWAY_DELAY)
|
||||
return success, err
|
||||
end
|
||||
|
||||
function WebSocket:disconnect(reconnect)
|
||||
if not self._write then return end
|
||||
self._reconnect = not not reconnect
|
||||
self._write()
|
||||
self._read = nil
|
||||
self._write = nil
|
||||
self._session_id = nil
|
||||
end
|
||||
|
||||
return WebSocket
|
||||
17
Lua/rpbot/deps/discordia/libs/constants.lua
Normal file
17
Lua/rpbot/deps/discordia/libs/constants.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
return {
|
||||
CACHE_AGE = 3600, -- seconds
|
||||
ID_DELAY = 5000, -- milliseconds
|
||||
GATEWAY_DELAY = 500, -- milliseconds,
|
||||
DISCORD_EPOCH = 1420070400000, -- milliseconds
|
||||
GATEWAY_VERSION = 6,
|
||||
DEFAULT_AVATARS = 5,
|
||||
ZWSP = '\226\128\139',
|
||||
NS_PER_US = 1000,
|
||||
US_PER_MS = 1000,
|
||||
MS_PER_S = 1000,
|
||||
S_PER_MIN = 60,
|
||||
MIN_PER_HOUR = 60,
|
||||
HOUR_PER_DAY = 24,
|
||||
DAY_PER_WEEK = 7,
|
||||
GATEWAY_VERSION_VOICE = 3,
|
||||
}
|
||||
111
Lua/rpbot/deps/discordia/libs/containers/Activity.lua
Normal file
111
Lua/rpbot/deps/discordia/libs/containers/Activity.lua
Normal file
@@ -0,0 +1,111 @@
|
||||
--[=[
|
||||
@c Activity
|
||||
@d Represents a Discord user's presence data, either plain game or streaming presence or a rich presence.
|
||||
]=]
|
||||
|
||||
local Container = require('containers/abstract/Container')
|
||||
|
||||
local Activity, get = require('class')('Activity', Container)
|
||||
|
||||
function Activity:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Activity:_load(data)
|
||||
Container._load(self, data)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Activity:_loadMore(data)
|
||||
local timestamps = data.timestamps
|
||||
self._start = timestamps and timestamps.start
|
||||
self._stop = timestamps and timestamps['end'] -- thanks discord
|
||||
local assets = data.assets
|
||||
self._small_text = assets and assets.small_text
|
||||
self._large_text = assets and assets.large_text
|
||||
self._small_image = assets and assets.small_image
|
||||
self._large_image = assets and assets.large_image
|
||||
local party = data.party
|
||||
self._party_id = party and party.id
|
||||
self._party_size = party and party.size and party.size[1]
|
||||
self._party_max = party and party.size and party.size[2]
|
||||
end
|
||||
|
||||
--[=[@p start number/nil The Unix timestamp for when this activity was started.]=]
|
||||
function get.start(self)
|
||||
return self._start
|
||||
end
|
||||
|
||||
--[=[@p stop number/nil The Unix timestamp for when this activity was stopped.]=]
|
||||
function get.stop(self)
|
||||
return self._stop
|
||||
end
|
||||
|
||||
--[=[@p name string/nil The game that the user is currently playing.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p type number/nil The type of user's game status. See the `gameType`
|
||||
enumeration for a human-readable representation.]=]
|
||||
function get.type(self)
|
||||
return self._type
|
||||
end
|
||||
|
||||
--[=[@p url string/nil The URL that is set for a user's streaming game status.]=]
|
||||
function get.url(self)
|
||||
return self._url
|
||||
end
|
||||
|
||||
--[=[@p applicationId string The application id controlling this activity.]=]
|
||||
function get.applicationId(self)
|
||||
return self._application_id
|
||||
end
|
||||
|
||||
--[=[@p state string/nil string for the Rich Presence state section.]=]
|
||||
function get.state(self)
|
||||
return self._state
|
||||
end
|
||||
|
||||
--[=[@p details string/nil string for the Rich Presence details section.]=]
|
||||
function get.details(self)
|
||||
return self._details
|
||||
end
|
||||
|
||||
--[=[@p textSmall string/nil string for the Rich Presence small image text.]=]
|
||||
function get.textSmall(self)
|
||||
return self._small_text
|
||||
end
|
||||
|
||||
--[=[@p textLarge string/nil string for the Rich Presence large image text.]=]
|
||||
function get.textLarge(self)
|
||||
return self._large_text
|
||||
end
|
||||
|
||||
--[=[@p imageSmall string/nil URL for the Rich Presence small image.]=]
|
||||
function get.imageSmall(self)
|
||||
return self._small_image
|
||||
end
|
||||
|
||||
--[=[@p imageLarge string/nil URL for the Rich Presence large image.]=]
|
||||
function get.imageLarge(self)
|
||||
return self._large_image
|
||||
end
|
||||
|
||||
--[=[@p partyId string/nil Party id for this Rich Presence.]=]
|
||||
function get.partyId(self)
|
||||
return self._party_id
|
||||
end
|
||||
|
||||
--[=[@p partySize number/nil Size of the Rich Presence party.]=]
|
||||
function get.partySize(self)
|
||||
return self._party_size
|
||||
end
|
||||
|
||||
--[=[@p partyMax number/nil Max size for the Rich Presence party.]=]
|
||||
function get.partyMax(self)
|
||||
return self._party_max
|
||||
end
|
||||
|
||||
return Activity
|
||||
223
Lua/rpbot/deps/discordia/libs/containers/AuditLogEntry.lua
Normal file
223
Lua/rpbot/deps/discordia/libs/containers/AuditLogEntry.lua
Normal file
@@ -0,0 +1,223 @@
|
||||
--[=[
|
||||
@c AuditLogEntry x Snowflake
|
||||
@d Represents an entry made into a guild's audit log.
|
||||
]=]
|
||||
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
|
||||
local enums = require('enums')
|
||||
local actionType = enums.actionType
|
||||
|
||||
local AuditLogEntry, get = require('class')('AuditLogEntry', Snowflake)
|
||||
|
||||
function AuditLogEntry:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
if data.changes then
|
||||
for i, change in ipairs(data.changes) do
|
||||
data.changes[change.key] = change
|
||||
data.changes[i] = nil
|
||||
change.key = nil
|
||||
change.old = change.old_value
|
||||
change.new = change.new_value
|
||||
change.old_value = nil
|
||||
change.new_value = nil
|
||||
end
|
||||
self._changes = data.changes
|
||||
end
|
||||
self._options = data.options
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getBeforeAfter
|
||||
@r table
|
||||
@r table
|
||||
@d Returns two tables of the target's properties before the change, and after the change.
|
||||
]=]
|
||||
function AuditLogEntry:getBeforeAfter()
|
||||
local before, after = {}, {}
|
||||
for k, change in pairs(self._changes) do
|
||||
before[k], after[k] = change.old, change.new
|
||||
end
|
||||
return before, after
|
||||
end
|
||||
|
||||
local function unknown(self)
|
||||
return nil, 'unknown audit log action type: ' .. self._action_type
|
||||
end
|
||||
|
||||
local targets = setmetatable({
|
||||
|
||||
[actionType.guildUpdate] = function(self)
|
||||
return self._parent
|
||||
end,
|
||||
|
||||
[actionType.channelCreate] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.channelUpdate] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.channelDelete] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.channelOverwriteCreate] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.channelOverwriteUpdate] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.channelOverwriteDelete] = function(self)
|
||||
return self._parent:getChannel(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.memberKick] = function(self)
|
||||
return self._parent._parent:getUser(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.memberPrune] = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
[actionType.memberBanAdd] = function(self)
|
||||
return self._parent._parent:getUser(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.memberBanRemove] = function(self)
|
||||
return self._parent._parent:getUser(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.memberUpdate] = function(self)
|
||||
return self._parent:getMember(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.memberRoleUpdate] = function(self)
|
||||
return self._parent:getMember(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.roleCreate] = function(self)
|
||||
return self._parent:getRole(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.roleUpdate] = function(self)
|
||||
return self._parent:getRole(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.roleDelete] = function(self)
|
||||
return self._parent:getRole(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.inviteCreate] = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
[actionType.inviteUpdate] = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
[actionType.inviteDelete] = function()
|
||||
return nil
|
||||
end,
|
||||
|
||||
[actionType.webhookCreate] = function(self)
|
||||
return self._parent._parent._webhooks:get(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.webhookUpdate] = function(self)
|
||||
return self._parent._parent._webhooks:get(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.webhookDelete] = function(self)
|
||||
return self._parent._parent._webhooks:get(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.emojiCreate] = function(self)
|
||||
return self._parent:getEmoji(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.emojiUpdate] = function(self)
|
||||
return self._parent:getEmoji(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.emojiDelete] = function(self)
|
||||
return self._parent:getEmoji(self._target_id)
|
||||
end,
|
||||
|
||||
[actionType.messageDelete] = function(self)
|
||||
return self._parent._parent:getUser(self._target_id)
|
||||
end,
|
||||
|
||||
}, {__index = function() return unknown end})
|
||||
|
||||
--[=[
|
||||
@m getTarget
|
||||
@r *
|
||||
@d Gets the target object of the affected entity. The returned object can be:
|
||||
- [[Guild]]
|
||||
- [[GuildChannel]]
|
||||
- [[User]]
|
||||
- [[Member]]
|
||||
- [[Role]]
|
||||
- [[Webhook]]
|
||||
- [[Emoji]]
|
||||
- nil
|
||||
]=]
|
||||
function AuditLogEntry:getTarget()
|
||||
return targets[self._action_type](self)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getUser
|
||||
@r User
|
||||
@d Gets the user who performed the changes.
|
||||
]=]
|
||||
function AuditLogEntry:getUser()
|
||||
return self._parent._parent:getUser(self._user_id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMember
|
||||
@r Member
|
||||
@d Gets the member object of the user who performed the changes.
|
||||
]=]
|
||||
function AuditLogEntry:getMember()
|
||||
return self._parent:getMember(self._user_id)
|
||||
end
|
||||
|
||||
--[=[@p changes table/nil A table of audit log change objects. The key represents
|
||||
the property of the changed target and the value contains a table of `new` and
|
||||
possibly `old`, representing the property's new and old value.]=]
|
||||
function get.changes(self)
|
||||
return self._changes
|
||||
end
|
||||
|
||||
--[=[@p options table/nil A table of optional audit log information.]=]
|
||||
function get.options(self)
|
||||
return self._options
|
||||
end
|
||||
|
||||
--[=[@p actionType number The action type. Use the `actionType `enumeration for a human-readable representation.]=]
|
||||
function get.actionType(self)
|
||||
return self._action_type
|
||||
end
|
||||
|
||||
--[=[@p targetId string/nil The Snowflake ID of the affected entity. Will be `nil` for certain targets.]=]
|
||||
function get.targetId(self)
|
||||
return self._target_id
|
||||
end
|
||||
|
||||
--[=[@p reason string/nil The reason provided by the user for the change.]=]
|
||||
function get.reason(self)
|
||||
return self._reason
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this audit log entry was found.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
return AuditLogEntry
|
||||
51
Lua/rpbot/deps/discordia/libs/containers/Ban.lua
Normal file
51
Lua/rpbot/deps/discordia/libs/containers/Ban.lua
Normal file
@@ -0,0 +1,51 @@
|
||||
--[=[
|
||||
@c Ban x Container
|
||||
@d Represents a Discord guild ban. Essentially a combination of the banned user and
|
||||
a reason explaining the ban, if one was provided.
|
||||
]=]
|
||||
|
||||
local Container = require('containers/abstract/Container')
|
||||
|
||||
local Ban, get = require('class')('Ban', Container)
|
||||
|
||||
function Ban:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
self._user = self.client._users:_insert(data.user)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __hash
|
||||
@r string
|
||||
@d Returns `Ban.user.id`
|
||||
]=]
|
||||
function Ban:__hash()
|
||||
return self._user._id
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Deletes the ban object, essentially unbanning the corresponding user.
|
||||
Equivalent to `Ban.guild:unbanUser(Ban.user)`.
|
||||
]=]
|
||||
function Ban:delete()
|
||||
return self._parent:unbanUser(self._user)
|
||||
end
|
||||
|
||||
--[=[@p reason string/nil The reason for the ban, if one was set. This should be from 1 to 512 characters
|
||||
in length.]=]
|
||||
function get.reason(self)
|
||||
return self._reason
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this ban object exists.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p user User The user that this ban object represents.]=]
|
||||
function get.user(self)
|
||||
return self._user
|
||||
end
|
||||
|
||||
return Ban
|
||||
163
Lua/rpbot/deps/discordia/libs/containers/Emoji.lua
Normal file
163
Lua/rpbot/deps/discordia/libs/containers/Emoji.lua
Normal file
@@ -0,0 +1,163 @@
|
||||
--[=[
|
||||
@c Emoji x Snowflake
|
||||
@d Represents a custom emoji object usable in message content and reactions.
|
||||
Standard unicode emojis do not have a class; they are just strings.
|
||||
]=]
|
||||
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local Resolver = require('client/Resolver')
|
||||
local ArrayIterable = require('iterables/ArrayIterable')
|
||||
local json = require('json')
|
||||
|
||||
local format = string.format
|
||||
|
||||
local Emoji, get = require('class')('Emoji', Snowflake)
|
||||
|
||||
function Emoji:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
self.client._emoji_map[self._id] = parent
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Emoji:_load(data)
|
||||
Snowflake._load(self, data)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Emoji:_loadMore(data)
|
||||
if data.roles then
|
||||
local roles = #data.roles > 0 and data.roles or nil
|
||||
if self._roles then
|
||||
self._roles._array = roles
|
||||
else
|
||||
self._roles_raw = roles
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Emoji:_modify(payload)
|
||||
local data, err = self.client._api:modifyGuildEmoji(self._parent._id, self._id, payload)
|
||||
if data then
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the emoji's name. The name must be between 2 and 32 characters in length.
|
||||
]=]
|
||||
function Emoji:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setRoles
|
||||
@p roles Role-ID-Resolvables
|
||||
@r boolean
|
||||
@d Sets the roles that can use the emoji.
|
||||
]=]
|
||||
function Emoji:setRoles(roles)
|
||||
roles = Resolver.roleIds(roles)
|
||||
return self:_modify({roles = roles or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the emoji. This cannot be undone!
|
||||
]=]
|
||||
function Emoji:delete()
|
||||
local data, err = self.client._api:deleteGuildEmoji(self._parent._id, self._id)
|
||||
if data then
|
||||
local cache = self._parent._emojis
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m hasRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r boolean
|
||||
@d Returns whether or not the provided role is allowed to use the emoji.
|
||||
]=]
|
||||
function Emoji:hasRole(id)
|
||||
id = Resolver.roleId(id)
|
||||
local roles = self._roles and self._roles._array or self._roles_raw
|
||||
if roles then
|
||||
for _, v in ipairs(roles) do
|
||||
if v == id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--[=[@p name string The name of the emoji.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which the emoji exists.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p mentionString string A string that, when included in a message content, may resolve as an emoji image
|
||||
in the official Discord client.]=]
|
||||
function get.mentionString(self)
|
||||
local fmt = self._animated and '<a:%s>' or '<:%s>'
|
||||
return format(fmt, self.hash)
|
||||
end
|
||||
|
||||
--[=[@p url string The URL that can be used to view a full version of the emoji.]=]
|
||||
function get.url(self)
|
||||
local ext = self._animated and 'gif' or 'png'
|
||||
return format('https://cdn.discordapp.com/emojis/%s.%s', self._id, ext)
|
||||
end
|
||||
|
||||
--[=[@p managed boolean Whether this emoji is managed by an integration such as Twitch or YouTube.]=]
|
||||
function get.managed(self)
|
||||
return self._managed
|
||||
end
|
||||
|
||||
--[=[@p requireColons boolean Whether this emoji requires colons to be used in the official Discord client.]=]
|
||||
function get.requireColons(self)
|
||||
return self._require_colons
|
||||
end
|
||||
|
||||
--[=[@p hash string An iterable array of roles that may be required to use this emoji, generally
|
||||
related to integration-managed emojis. Object order is not guaranteed.
|
||||
]=]
|
||||
function get.hash(self)
|
||||
return self._name .. ':' .. self._id
|
||||
end
|
||||
|
||||
--[=[@p animated boolean Whether this emoji is animated. (a .gif)]=]
|
||||
function get.animated(self)
|
||||
return self._animated
|
||||
end
|
||||
|
||||
--[=[@p roles ArrayIterable An iterable of roles that have access to the emoji.]=]
|
||||
function get.roles(self)
|
||||
if not self._roles then
|
||||
local roles = self._parent._roles
|
||||
self._roles = ArrayIterable(self._roles_raw, function(id)
|
||||
return roles:get(id)
|
||||
end)
|
||||
self._roles_raw = nil
|
||||
end
|
||||
return self._roles
|
||||
end
|
||||
|
||||
return Emoji
|
||||
117
Lua/rpbot/deps/discordia/libs/containers/GroupChannel.lua
Normal file
117
Lua/rpbot/deps/discordia/libs/containers/GroupChannel.lua
Normal file
@@ -0,0 +1,117 @@
|
||||
--[=[
|
||||
@c GroupChannel x TextChannel
|
||||
@d Represents a Discord group channel. Essentially a private channel that may have
|
||||
more than one and up to ten recipients. This class should only be relevant to
|
||||
user-accounts; bots cannot normally join group channels.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
|
||||
local TextChannel = require('containers/abstract/TextChannel')
|
||||
local SecondaryCache = require('iterables/SecondaryCache')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local format = string.format
|
||||
|
||||
local GroupChannel, get = require('class')('GroupChannel', TextChannel)
|
||||
|
||||
function GroupChannel:__init(data, parent)
|
||||
TextChannel.__init(self, data, parent)
|
||||
self._recipients = SecondaryCache(data.recipients, self.client._users)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the channel's name. This must be between 1 and 100 characters in length.
|
||||
]=]
|
||||
function GroupChannel:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setIcon
|
||||
@p icon Base64-Resolvable
|
||||
@r boolean
|
||||
@d Sets the channel's icon. To remove the icon, pass `nil`.
|
||||
]=]
|
||||
function GroupChannel:setIcon(icon)
|
||||
icon = icon and Resolver.base64(icon)
|
||||
return self:_modify({icon = icon or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m addRecipient
|
||||
@p id User-ID-Resolvable
|
||||
@r boolean
|
||||
@d Adds a user to the channel.
|
||||
]=]
|
||||
function GroupChannel:addRecipient(id)
|
||||
id = Resolver.userId(id)
|
||||
local data, err = self.client._api:groupDMAddRecipient(self._id, id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m removeRecipient
|
||||
@p id User-ID-Resolvable
|
||||
@r boolean
|
||||
@d Removes a user from the channel.
|
||||
]=]
|
||||
function GroupChannel:removeRecipient(id)
|
||||
id = Resolver.userId(id)
|
||||
local data, err = self.client._api:groupDMRemoveRecipient(self._id, id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m leave
|
||||
@r boolean
|
||||
@d Removes the client's user from the channel. If no users remain, the channel
|
||||
is destroyed.
|
||||
]=]
|
||||
function GroupChannel:leave()
|
||||
return self:_delete()
|
||||
end
|
||||
|
||||
--[=[@p recipients SecondaryCache A secondary cache of users that are present in the channel.]=]
|
||||
function get.recipients(self)
|
||||
return self._recipients
|
||||
end
|
||||
|
||||
--[=[@p name string The name of the channel.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p ownerId string The Snowflake ID of the user that owns (created) the channel.]=]
|
||||
function get.ownerId(self)
|
||||
return self._owner_id
|
||||
end
|
||||
|
||||
--[=[@p owner User/nil Equivalent to `GroupChannel.recipients:get(GroupChannel.ownerId)`.]=]
|
||||
function get.owner(self)
|
||||
return self._recipients:get(self._owner_id)
|
||||
end
|
||||
|
||||
--[=[@p icon string/nil The hash for the channel's custom icon, if one is set.]=]
|
||||
function get.icon(self)
|
||||
return self._icon
|
||||
end
|
||||
|
||||
--[=[@p iconURL string/nil The URL that can be used to view the channel's icon, if one is set.]=]
|
||||
function get.iconURL(self)
|
||||
local icon = self._icon
|
||||
return icon and format('https://cdn.discordapp.com/channel-icons/%s/%s.png', self._id, icon)
|
||||
end
|
||||
|
||||
return GroupChannel
|
||||
812
Lua/rpbot/deps/discordia/libs/containers/Guild.lua
Normal file
812
Lua/rpbot/deps/discordia/libs/containers/Guild.lua
Normal file
@@ -0,0 +1,812 @@
|
||||
--[=[
|
||||
@c Guild x Snowflake
|
||||
@d Represents a Discord guild (or server). Guilds are a collection of members,
|
||||
channels, and roles that represents one community.
|
||||
]=]
|
||||
|
||||
local Cache = require('iterables/Cache')
|
||||
local Role = require('containers/Role')
|
||||
local Emoji = require('containers/Emoji')
|
||||
local Invite = require('containers/Invite')
|
||||
local Webhook = require('containers/Webhook')
|
||||
local Ban = require('containers/Ban')
|
||||
local Member = require('containers/Member')
|
||||
local Resolver = require('client/Resolver')
|
||||
local AuditLogEntry = require('containers/AuditLogEntry')
|
||||
local GuildTextChannel = require('containers/GuildTextChannel')
|
||||
local GuildVoiceChannel = require('containers/GuildVoiceChannel')
|
||||
local GuildCategoryChannel = require('containers/GuildCategoryChannel')
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
|
||||
local json = require('json')
|
||||
local enums = require('enums')
|
||||
|
||||
local channelType = enums.channelType
|
||||
local floor = math.floor
|
||||
local format = string.format
|
||||
|
||||
local Guild, get = require('class')('Guild', Snowflake)
|
||||
|
||||
function Guild:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
self._roles = Cache({}, Role, self)
|
||||
self._emojis = Cache({}, Emoji, self)
|
||||
self._members = Cache({}, Member, self)
|
||||
self._text_channels = Cache({}, GuildTextChannel, self)
|
||||
self._voice_channels = Cache({}, GuildVoiceChannel, self)
|
||||
self._categories = Cache({}, GuildCategoryChannel, self)
|
||||
self._voice_states = {}
|
||||
if not data.unavailable then
|
||||
return self:_makeAvailable(data)
|
||||
end
|
||||
end
|
||||
|
||||
function Guild:_makeAvailable(data)
|
||||
|
||||
self._roles:_load(data.roles)
|
||||
self._emojis:_load(data.emojis)
|
||||
self._features = data.features
|
||||
|
||||
if not data.channels then return end -- incomplete guild
|
||||
|
||||
local states = self._voice_states
|
||||
for _, state in ipairs(data.voice_states) do
|
||||
states[state.user_id] = state
|
||||
end
|
||||
|
||||
local text_channels = self._text_channels
|
||||
local voice_channels = self._voice_channels
|
||||
local categories = self._categories
|
||||
|
||||
for _, channel in ipairs(data.channels) do
|
||||
local t = channel.type
|
||||
if t == channelType.text then
|
||||
text_channels:_insert(channel)
|
||||
elseif t == channelType.voice then
|
||||
voice_channels:_insert(channel)
|
||||
elseif t == channelType.category then
|
||||
categories:_insert(channel)
|
||||
end
|
||||
end
|
||||
|
||||
return self:_loadMembers(data)
|
||||
|
||||
end
|
||||
|
||||
function Guild:_loadMembers(data)
|
||||
local members = self._members
|
||||
members:_load(data.members)
|
||||
for _, presence in ipairs(data.presences) do
|
||||
local member = members:get(presence.user.id)
|
||||
if member then -- rogue presence check
|
||||
member:_loadPresence(presence)
|
||||
end
|
||||
end
|
||||
if self._large and self.client._options.cacheAllMembers then
|
||||
return self:requestMembers()
|
||||
end
|
||||
end
|
||||
|
||||
function Guild:_modify(payload)
|
||||
local data, err = self.client._api:modifyGuild(self._id, payload)
|
||||
if data then
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m requestMembers
|
||||
@r boolean
|
||||
@d Asynchronously loads all members for this guild. You do not need to call this
|
||||
if the `cacheAllMembers` client option (and the `syncGuilds` option for
|
||||
user-accounts) is enabled on start-up.
|
||||
]=]
|
||||
function Guild:requestMembers()
|
||||
local shard = self.client._shards[self.shardId]
|
||||
if not shard then
|
||||
return false, 'Invalid shard'
|
||||
end
|
||||
if shard._loading then
|
||||
shard._loading.chunks[self._id] = true
|
||||
end
|
||||
return shard:requestGuildMembers(self._id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m sync
|
||||
@r boolean
|
||||
@d Asynchronously loads certain data and enables the receiving of certain events
|
||||
for this guild. You do not need to call this if the `syncGuilds` client option
|
||||
is enabled on start-up.
|
||||
|
||||
Note: This is only for user accounts. Bot accounts never need to sync guilds!
|
||||
]=]
|
||||
function Guild:sync()
|
||||
local shard = self.client._shards[self.shardId]
|
||||
if not shard then
|
||||
return false, 'Invalid shard'
|
||||
end
|
||||
if shard._loading then
|
||||
shard._loading.syncs[self._id] = true
|
||||
end
|
||||
return shard:syncGuilds({self._id})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMember
|
||||
@p id User-ID-Resolvable
|
||||
@r Member
|
||||
@d Gets a member object by ID. If the object is already cached, then the cached
|
||||
object will be returned; otherwise, an HTTP request is made.
|
||||
]=]
|
||||
function Guild:getMember(id)
|
||||
id = Resolver.userId(id)
|
||||
local member = self._members:get(id)
|
||||
if member then
|
||||
return member
|
||||
else
|
||||
local data, err = self.client._api:getGuildMember(self._id, id)
|
||||
if data then
|
||||
return self._members:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r Role
|
||||
@d Gets a role object by ID.
|
||||
]=]
|
||||
function Guild:getRole(id)
|
||||
id = Resolver.roleId(id)
|
||||
return self._roles:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getEmoji
|
||||
@p id Emoji-ID-Resolvable
|
||||
@r Emoji
|
||||
@d Gets a emoji object by ID.
|
||||
]=]
|
||||
function Guild:getEmoji(id)
|
||||
id = Resolver.emojiId(id)
|
||||
return self._emojis:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getChannel
|
||||
@p id Channel-ID-Resolvable
|
||||
@r GuildChannel
|
||||
@d Gets a text, voice, or category channel object by ID.
|
||||
]=]
|
||||
function Guild:getChannel(id)
|
||||
id = Resolver.channelId(id)
|
||||
return self._text_channels:get(id) or self._voice_channels:get(id) or self._categories:get(id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createTextChannel
|
||||
@p name string
|
||||
@r GuildTextChannel
|
||||
@d Creates a new text channel in this guild. The name must be between 2 and 100
|
||||
characters in length.
|
||||
]=]
|
||||
function Guild:createTextChannel(name)
|
||||
local data, err = self.client._api:createGuildChannel(self._id, {name = name, type = channelType.text})
|
||||
if data then
|
||||
return self._text_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createVoiceChannel
|
||||
@p name string
|
||||
@r GuildVoiceChannel
|
||||
@d Creates a new voice channel in this guild. The name must be between 2 and 100
|
||||
characters in length.
|
||||
]=]
|
||||
function Guild:createVoiceChannel(name)
|
||||
local data, err = self.client._api:createGuildChannel(self._id, {name = name, type = channelType.voice})
|
||||
if data then
|
||||
return self._voice_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createCategory
|
||||
@p name string
|
||||
@r GuildCategoryChannel
|
||||
@d Creates a channel category in this guild. The name must be between 2 and 100
|
||||
characters in length.
|
||||
]=]
|
||||
function Guild:createCategory(name)
|
||||
local data, err = self.client._api:createGuildChannel(self._id, {name = name, type = channelType.category})
|
||||
if data then
|
||||
return self._categories:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createRole
|
||||
@p name string
|
||||
@r Role
|
||||
@d Creates a new role in this guild. The name must be between 1 and 100 characters
|
||||
in length.
|
||||
]=]
|
||||
function Guild:createRole(name)
|
||||
local data, err = self.client._api:createGuildRole(self._id, {name = name})
|
||||
if data then
|
||||
return self._roles:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createEmoji
|
||||
@p name string
|
||||
@p image Base64-Resolvable
|
||||
@r Emoji
|
||||
@d Creates a new emoji in this guild. The name must be between 2 and 32 characters
|
||||
in length. The image must not be over 256kb, any higher will return a 400 Bad Request
|
||||
]=]
|
||||
function Guild:createEmoji(name, image)
|
||||
image = Resolver.base64(image)
|
||||
local data, err = self.client._api:createGuildEmoji(self._id, {name = name, image = image})
|
||||
if data then
|
||||
return self._emojis:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the guilds name. This must be between 2 and 100 characters in length.
|
||||
]=]
|
||||
function Guild:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setRegion
|
||||
@p region string
|
||||
@r boolean
|
||||
@d Sets the guild's voice region (eg: `us-east`). See `listVoiceRegions` for a list
|
||||
of acceptable regions.
|
||||
]=]
|
||||
function Guild:setRegion(region)
|
||||
return self:_modify({region = region or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setVerificationLevel
|
||||
@p verification_level number
|
||||
@r boolean
|
||||
@d Sets the guild's verification level setting. See the `verificationLevel`
|
||||
enumeration for acceptable values.
|
||||
]=]
|
||||
function Guild:setVerificationLevel(verification_level)
|
||||
return self:_modify({verification_level = verification_level or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setNotificationSetting
|
||||
@p default_message_notifications number
|
||||
@r boolean
|
||||
@d Sets the guild's default notification setting. See the `notficationSetting`
|
||||
enumeration for acceptable values.
|
||||
]=]
|
||||
function Guild:setNotificationSetting(default_message_notifications)
|
||||
return self:_modify({default_message_notifications = default_message_notifications or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setExplicitContentSetting
|
||||
@p explicit_content_filter number
|
||||
@r boolean
|
||||
@d Sets the guild's explicit content level setting. See the `explicitContentLevel`
|
||||
enumeration for acceptable values.
|
||||
]=]
|
||||
function Guild:setExplicitContentSetting(explicit_content_filter)
|
||||
return self:_modify({explicit_content_filter = explicit_content_filter or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAFKTimeout
|
||||
@p afk_timeout number
|
||||
@r number
|
||||
@d Sets the guild's AFK timeout in seconds.
|
||||
]=]
|
||||
function Guild:setAFKTimeout(afk_timeout)
|
||||
return self:_modify({afk_timeout = afk_timeout or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAFKChannel
|
||||
@p id Channel-ID-Resolvable
|
||||
@r boolean
|
||||
@d Sets the guild's AFK channel.
|
||||
]=]
|
||||
function Guild:setAFKChannel(id)
|
||||
id = id and Resolver.channelId(id)
|
||||
return self:_modify({afk_channel_id = id or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setSystemChannel
|
||||
@p id Channel-Id-Resolvable
|
||||
@r boolean
|
||||
@d Transfers ownership of the guild to another user. Only the current guild owner
|
||||
can do this.
|
||||
]=]
|
||||
function Guild:setSystemChannel(id)
|
||||
id = id and Resolver.channelId(id)
|
||||
return self:_modify({system_channel_id = id or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setOwner
|
||||
@p id User-ID-Resolvable
|
||||
@r boolean
|
||||
@d Transfers ownership of the guild to another user. Only the current guild owner
|
||||
can do this.
|
||||
]=]
|
||||
function Guild:setOwner(id)
|
||||
id = id and Resolver.userId(id)
|
||||
return self:_modify({owner_id = id or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setIcon
|
||||
@p icon Base64-Resolvable
|
||||
@r boolean
|
||||
@d Sets the guild's icon. To remove the icon, pass `nil`.
|
||||
]=]
|
||||
function Guild:setIcon(icon)
|
||||
icon = icon and Resolver.base64(icon)
|
||||
return self:_modify({icon = icon or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setSplash
|
||||
@p splash Base64-Resolvable
|
||||
@r boolean
|
||||
@d Sets the guild's splash. To remove the splash, pass `nil`.
|
||||
]=]
|
||||
function Guild:setSplash(splash)
|
||||
splash = splash and Resolver.base64(splash)
|
||||
return self:_modify({splash = splash or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPruneCount
|
||||
@op days number
|
||||
@r number
|
||||
@d Returns the number of members that would be pruned from the guild if a prune
|
||||
were to be executed.
|
||||
]=]
|
||||
function Guild:getPruneCount(days)
|
||||
local data, err = self.client._api:getGuildPruneCount(self._id, days and {days = days} or nil)
|
||||
if data then
|
||||
return data.pruned
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m pruneMembers
|
||||
@op days number
|
||||
@r number
|
||||
@d Prunes (removes) inactive, roleless members from the guild.
|
||||
]=]
|
||||
function Guild:pruneMembers(days)
|
||||
local data, err = self.client._api:beginGuildPrune(self._id, nil, days and {days = days} or nil)
|
||||
if data then
|
||||
return data.pruned
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getBans
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of all ban objects for the guild. The
|
||||
cache is not automatically updated via gateway events, but the internally
|
||||
referenced user objects may be updated. You must call this method again to
|
||||
guarantee that the objects are up to date.
|
||||
]=]
|
||||
function Guild:getBans()
|
||||
local data, err = self.client._api:getGuildBans(self._id)
|
||||
if data then
|
||||
return Cache(data, Ban, self)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getBan
|
||||
@p id User-ID-Resolvable
|
||||
@r Ban
|
||||
@d This will return a Ban object for a giver user if that user is banned
|
||||
from the guild; otherwise, `nil` is returned.
|
||||
]=]
|
||||
function Guild:getBan(id)
|
||||
id = Resolver.userId(id)
|
||||
local data, err = self.client._api:getGuildBan(self._id, id)
|
||||
if data then
|
||||
return Ban(data, self._parent)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getInvites
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of all invite objects for the guild. The
|
||||
cache and its objects are not automatically updated via gateway events. You must
|
||||
call this method again to get the updated objects.
|
||||
]=]
|
||||
function Guild:getInvites()
|
||||
local data, err = self.client._api:getGuildInvites(self._id)
|
||||
if data then
|
||||
return Cache(data, Invite, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getAuditLogs
|
||||
@op query table
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of audit log entry objects for the guild. The
|
||||
cache and its objects are not automatically updated via gateway events. You must
|
||||
call this method again to get the updated objects.
|
||||
|
||||
- query.limit: number
|
||||
- query.user: UserId Resolvable
|
||||
- query.before: EntryId Resolvable
|
||||
- query.type: ActionType Resolvable
|
||||
]=]
|
||||
function Guild:getAuditLogs(query)
|
||||
if type(query) == 'table' then
|
||||
query = {
|
||||
limit = query.limit,
|
||||
user_id = Resolver.userId(query.user),
|
||||
before = Resolver.entryId(query.before),
|
||||
action_type = Resolver.actionType(query.type),
|
||||
}
|
||||
end
|
||||
local data, err = self.client._api:getGuildAuditLog(self._id, query)
|
||||
if data then
|
||||
self.client._users:_load(data.users)
|
||||
self.client._webhooks:_load(data.webhooks)
|
||||
return Cache(data.audit_log_entries, AuditLogEntry, self)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getWebhooks
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of all webhook objects for the guild. The
|
||||
cache and its objects are not automatically updated via gateway events. You must
|
||||
call this method again to get the updated objects.
|
||||
]=]
|
||||
function Guild:getWebhooks()
|
||||
local data, err = self.client._api:getGuildWebhooks(self._id)
|
||||
if data then
|
||||
return Cache(data, Webhook, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m listVoiceRegions
|
||||
@r table
|
||||
@d Returns a raw data table that contains a list of available voice regions for
|
||||
this guild, as provided by Discord, with no additional parsing.
|
||||
]=]
|
||||
function Guild:listVoiceRegions()
|
||||
return self.client._api:getGuildVoiceRegions(self._id)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m leave
|
||||
@r boolean
|
||||
@d Removes the current user from the guild.
|
||||
]=]
|
||||
function Guild:leave()
|
||||
local data, err = self.client._api:leaveGuild(self._id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the guild. This cannot be undone!
|
||||
]=]
|
||||
function Guild:delete()
|
||||
local data, err = self.client._api:deleteGuild(self._id)
|
||||
if data then
|
||||
local cache = self._parent._guilds
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m kickUser
|
||||
@p id User-ID-Resolvable
|
||||
@op reason string
|
||||
@r boolean
|
||||
@d Kicks a user/member from the guild with an optional reason.
|
||||
]=]
|
||||
function Guild:kickUser(id, reason)
|
||||
id = Resolver.userId(id)
|
||||
local query = reason and {reason = reason}
|
||||
local data, err = self.client._api:removeGuildMember(self._id, id, query)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m banUser
|
||||
@p id User-ID-Resolvable
|
||||
@op reason string
|
||||
@op days number
|
||||
@r boolean
|
||||
@d Bans a user/member from the guild with an optional reason. The `days` parameter
|
||||
is the number of days to consider when purging messages, up to 7.
|
||||
]=]
|
||||
function Guild:banUser(id, reason, days)
|
||||
local query = reason and {reason = reason}
|
||||
if days then
|
||||
query = query or {}
|
||||
query['delete-message-days'] = days
|
||||
end
|
||||
id = Resolver.userId(id)
|
||||
local data, err = self.client._api:createGuildBan(self._id, id, query)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m unbanUser
|
||||
@p id User-ID-Resolvable
|
||||
@op reason string
|
||||
@r boolean
|
||||
@d Unbans a user/member from the guild with an optional reason.
|
||||
]=]
|
||||
function Guild:unbanUser(id, reason)
|
||||
id = Resolver.userId(id)
|
||||
local query = reason and {reason = reason}
|
||||
local data, err = self.client._api:removeGuildBan(self._id, id, query)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p shardId number The ID of the shard on which this guild is served. If only one shard is in
|
||||
operation, then this will always be 0.]=]
|
||||
function get.shardId(self)
|
||||
return floor(self._id / 2^22) % self.client._total_shard_count
|
||||
end
|
||||
|
||||
--[=[@p name string The guild's name. This should be between 2 and 100 characters in length.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p icon string/nil The hash for the guild's custom icon, if one is set.]=]
|
||||
function get.icon(self)
|
||||
return self._icon
|
||||
end
|
||||
|
||||
--[=[@p iconURL string/nil The URL that can be used to view the guild's icon, if one is set.]=]
|
||||
function get.iconURL(self)
|
||||
local icon = self._icon
|
||||
return icon and format('https://cdn.discordapp.com/icons/%s/%s.png', self._id, icon)
|
||||
end
|
||||
|
||||
--[=[@p splash string/nil The hash for the guild's custom splash image, if one is set. Only partnered
|
||||
guilds may have this.]=]
|
||||
function get.splash(self)
|
||||
return self._splash
|
||||
end
|
||||
|
||||
--[=[@p splashURL string/nil The URL that can be used to view the guild's custom splash image, if one is set.
|
||||
Only partnered guilds may have this.]=]
|
||||
function get.splashURL(self)
|
||||
local splash = self._splash
|
||||
return splash and format('https://cdn.discordapp.com/splashs/%s/%s.png', self._id, splash)
|
||||
end
|
||||
|
||||
--[=[@p large boolean Whether the guild has an arbitrarily large amount of members. Guilds that are
|
||||
"large" will not initialize with all members.]=]
|
||||
function get.large(self)
|
||||
return self._large
|
||||
end
|
||||
|
||||
--[=[@p lazy boolean Whether the guild follows rules for the lazy-loading of client data.]=]
|
||||
function get.lazy(self)
|
||||
return self._lazy
|
||||
end
|
||||
|
||||
--[=[@p region string The voice region that is used for all voice connections in the guild.]=]
|
||||
function get.region(self)
|
||||
return self._region
|
||||
end
|
||||
|
||||
--[=[@p mfaLevel number The guild's multi-factor (or two-factor) verification level setting. A value of
|
||||
0 indicates that MFA is not required; a value of 1 indicates that MFA is
|
||||
required for administrative actions.]=]
|
||||
function get.mfaLevel(self)
|
||||
return self._mfa_level
|
||||
end
|
||||
|
||||
--[=[@p joinedAt string The date and time at which the current user joined the guild, represented as
|
||||
an ISO 8601 string plus microseconds when available.]=]
|
||||
function get.joinedAt(self)
|
||||
return self._joined_at
|
||||
end
|
||||
|
||||
--[=[@p afkTimeout number The guild's voice AFK timeout in seconds.]=]
|
||||
function get.afkTimeout(self)
|
||||
return self._afk_timeout
|
||||
end
|
||||
|
||||
--[=[@p unavailable boolean Whether the guild is unavailable. If the guild is unavailable, then no property
|
||||
is guaranteed to exist except for this one and the guild's ID.]=]
|
||||
function get.unavailable(self)
|
||||
return self._unavailable or false
|
||||
end
|
||||
|
||||
--[=[@p totalMemberCount number The total number of members that belong to this guild. This should always be
|
||||
greater than or equal to the total number of cached members.]=]
|
||||
function get.totalMemberCount(self)
|
||||
return self._member_count
|
||||
end
|
||||
|
||||
--[=[@p verificationLevel number The guild's verification level setting. See the `verificationLevel`
|
||||
enumeration for a human-readable representation.]=]
|
||||
function get.verificationLevel(self)
|
||||
return self._verification_level
|
||||
end
|
||||
|
||||
--[=[@p notificationSetting number The guild's default notification setting. See the `notficationSetting`
|
||||
enumeration for a human-readable representation.]=]
|
||||
function get.notificationSetting(self)
|
||||
return self._default_message_notifications
|
||||
end
|
||||
|
||||
--[=[@p explicitContentSetting number The guild's explicit content level setting. See the `explicitContentLevel`
|
||||
enumeration for a human-readable representation.]=]
|
||||
function get.explicitContentSetting(self)
|
||||
return self._explicit_content_filter
|
||||
end
|
||||
|
||||
--[=[@p features table Raw table of VIP features that are enabled for the guild.]=]
|
||||
function get.features(self)
|
||||
return self._features
|
||||
end
|
||||
|
||||
--[=[@p me Member/nil Equivalent to `Guild.members:get(Guild.client.user.id)`.]=]
|
||||
function get.me(self)
|
||||
return self._members:get(self.client._user._id)
|
||||
end
|
||||
|
||||
--[=[@p owner Member/nil Equivalent to `Guild.members:get(Guild.ownerId)`.]=]
|
||||
function get.owner(self)
|
||||
return self._members:get(self._owner_id)
|
||||
end
|
||||
|
||||
--[=[@p ownerId string The Snowflake ID of the guild member that owns the guild.]=]
|
||||
function get.ownerId(self)
|
||||
return self._owner_id
|
||||
end
|
||||
|
||||
--[=[@p afkChannelId string/nil The Snowflake ID of the channel that is used for AFK members, if one is set.]=]
|
||||
function get.afkChannelId(self)
|
||||
return self._afk_channel_id
|
||||
end
|
||||
|
||||
--[=[@p afkChannel GuildVoiceChannel/nil Equivalent to `Guild.voiceChannels:get(Guild.afkChannelId)`.]=]
|
||||
function get.afkChannel(self)
|
||||
return self._voice_channels:get(self._afk_channel_id)
|
||||
end
|
||||
|
||||
--[=[@p systemChannelId string/nil The channel id where Discord's join messages will be displayed]=]
|
||||
function get.systemChannelId(self)
|
||||
return self._system_channel_id
|
||||
end
|
||||
|
||||
--[=[@p systemChannel GuildTextChannel/nil The channel where Discord's join messages will be displayed]=]
|
||||
function get.systemChannel(self)
|
||||
return self._text_channels:get(self._system_channel_id)
|
||||
end
|
||||
|
||||
--[=[@p defaultRole Role Equivalent to `Guild.roles:get(Guild.id)`.]=]
|
||||
function get.defaultRole(self)
|
||||
return self._roles:get(self._id)
|
||||
end
|
||||
|
||||
--[=[@p connection VoiceConnection/nil The VoiceConnection for this guild if one exists.]=]
|
||||
function get.connection(self)
|
||||
return self._connection
|
||||
end
|
||||
|
||||
--[=[@p roles Cache An iterable cache of all roles that exist in this guild. This includes the
|
||||
default everyone role.]=]
|
||||
function get.roles(self)
|
||||
return self._roles
|
||||
end
|
||||
|
||||
--[=[@p emojis Cache An iterable cache of all emojis that exist in this guild. Note that standard
|
||||
unicode emojis are not found here; only custom emojis.]=]
|
||||
function get.emojis(self)
|
||||
return self._emojis
|
||||
end
|
||||
|
||||
--[=[@p members Cache An iterable cache of all members that exist in this guild and have been
|
||||
already loaded. If the `cacheAllMembers` client option (and the `syncGuilds`
|
||||
option for user-accounts) is enabled on start-up, then all members will be
|
||||
cached. Otherwise, offline members may not be cached. To access a member that
|
||||
may exist, but is not cached, use `Guild:getMember`.]=]
|
||||
function get.members(self)
|
||||
return self._members
|
||||
end
|
||||
|
||||
--[=[@p textChannels Cache An iterable cache of all text channels that exist in this guild.]=]
|
||||
function get.textChannels(self)
|
||||
return self._text_channels
|
||||
end
|
||||
|
||||
--[=[@p voiceChannels Cache An iterable cache of all voice channels that exist in this guild.]=]
|
||||
function get.voiceChannels(self)
|
||||
return self._voice_channels
|
||||
end
|
||||
|
||||
--[=[@p categories Cache An iterable cache of all channel categories that exist in this guild.]=]
|
||||
function get.categories(self)
|
||||
return self._categories
|
||||
end
|
||||
|
||||
return Guild
|
||||
@@ -0,0 +1,81 @@
|
||||
--[=[
|
||||
@c GuildCategoryChannel x GuildChannel
|
||||
@d Represents a channel category in a Discord guild, used to organize individual
|
||||
text or voice channels in that guild.
|
||||
]=]
|
||||
|
||||
local GuildChannel = require('containers/abstract/GuildChannel')
|
||||
local FilteredIterable = require('iterables/FilteredIterable')
|
||||
local enums = require('enums')
|
||||
|
||||
local channelType = enums.channelType
|
||||
|
||||
local GuildCategoryChannel, get = require('class')('GuildCategoryChannel', GuildChannel)
|
||||
|
||||
function GuildCategoryChannel:__init(data, parent)
|
||||
GuildChannel.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createTextChannel
|
||||
@p name string
|
||||
@r GuildTextChannel
|
||||
@d Creates a new GuildTextChannel with this category as it's parent. `Guild:createTextChannel(name)`
|
||||
]=]
|
||||
function GuildCategoryChannel:createTextChannel(name)
|
||||
local guild = self._parent
|
||||
local data, err = guild.client._api:createGuildChannel(guild._id, {
|
||||
name = name,
|
||||
type = channelType.text,
|
||||
parent_id = self._id
|
||||
})
|
||||
if data then
|
||||
return guild._text_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createVoiceChannel
|
||||
@p name string
|
||||
@r GuildVoiceChannel
|
||||
@d Creates a new GuildVoiceChannel with this category as it's parent. Similar to `Guild:createVoiceChannel(name)`
|
||||
]=]
|
||||
function GuildCategoryChannel:createVoiceChannel(name)
|
||||
local guild = self._parent
|
||||
local data, err = guild.client._api:createGuildChannel(guild._id, {
|
||||
name = name,
|
||||
type = channelType.voice,
|
||||
parent_id = self._id
|
||||
})
|
||||
if data then
|
||||
return guild._voice_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p textChannels FilteredIterable Returns all textChannels in the Category]=]
|
||||
function get.textChannels(self)
|
||||
if not self._text_channels then
|
||||
local id = self._id
|
||||
self._text_channels = FilteredIterable(self._parent._text_channels, function(c)
|
||||
return c._parent_id == id
|
||||
end)
|
||||
end
|
||||
return self._text_channels
|
||||
end
|
||||
|
||||
--[=[@p voiceChannels FilteredIterable Returns all voiceChannels in the Category]=]
|
||||
function get.voiceChannels(self)
|
||||
if not self._voice_channels then
|
||||
local id = self._id
|
||||
self._voice_channels = FilteredIterable(self._parent._voice_channels, function(c)
|
||||
return c._parent_id == id
|
||||
end)
|
||||
end
|
||||
return self._voice_channels
|
||||
end
|
||||
|
||||
return GuildCategoryChannel
|
||||
152
Lua/rpbot/deps/discordia/libs/containers/GuildTextChannel.lua
Normal file
152
Lua/rpbot/deps/discordia/libs/containers/GuildTextChannel.lua
Normal file
@@ -0,0 +1,152 @@
|
||||
--[=[
|
||||
@c GuildTextChannel x GuildChannel x TextChannel
|
||||
@d Represents a text channel in a Discord guild, where guild members and webhooks
|
||||
can send and receive messages.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
|
||||
local GuildChannel = require('containers/abstract/GuildChannel')
|
||||
local TextChannel = require('containers/abstract/TextChannel')
|
||||
local FilteredIterable = require('iterables/FilteredIterable')
|
||||
local Webhook = require('containers/Webhook')
|
||||
local Cache = require('iterables/Cache')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local GuildTextChannel, get = require('class')('GuildTextChannel', GuildChannel, TextChannel)
|
||||
|
||||
function GuildTextChannel:__init(data, parent)
|
||||
GuildChannel.__init(self, data, parent)
|
||||
TextChannel.__init(self, data, parent)
|
||||
end
|
||||
|
||||
function GuildTextChannel:_load(data)
|
||||
GuildChannel._load(self, data)
|
||||
TextChannel._load(self, data)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createWebhook
|
||||
@p name string
|
||||
@r Webhook
|
||||
@d Creates a webhook for this channel. The name must be between 2 and 32 characters
|
||||
in length.
|
||||
]=]
|
||||
function GuildTextChannel:createWebhook(name)
|
||||
local data, err = self.client._api:createWebhook(self._id, {name = name})
|
||||
if data then
|
||||
return Webhook(data, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getWebhooks
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of all webhook objects for the channel. The
|
||||
cache and its objects are not automatically updated via gateway events. You must
|
||||
call this method again to get the updated objects.
|
||||
]=]
|
||||
function GuildTextChannel:getWebhooks()
|
||||
local data, err = self.client._api:getChannelWebhooks(self._id)
|
||||
if data then
|
||||
return Cache(data, Webhook, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m bulkDelete
|
||||
@p messages Message-ID-Resolvables
|
||||
@r boolean
|
||||
@d Bulk deletes multiple messages, from 2 to 100, from the channel.
|
||||
]=]
|
||||
function GuildTextChannel:bulkDelete(messages)
|
||||
messages = Resolver.messageIds(messages)
|
||||
local data, err
|
||||
if #messages == 1 then
|
||||
data, err = self.client._api:deleteMessage(self._id, messages[1])
|
||||
else
|
||||
data, err = self.client._api:bulkDeleteMessages(self._id, {messages = messages})
|
||||
end
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setTopic
|
||||
@p topic string
|
||||
@r boolean
|
||||
@d Sets the channel's topic. This must be between 1 and 1024 characters. Pass `nil`
|
||||
to remove the topic.
|
||||
]=]
|
||||
function GuildTextChannel:setTopic(topic)
|
||||
return self:_modify({topic = topic or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setRateLimit
|
||||
@p limit number
|
||||
@r boolean
|
||||
@d Sets the channel's slowmode rate limit in seconds. This must be between 0 and 120.
|
||||
Passing 0 or `nil` will clear the limit.
|
||||
]=]
|
||||
function GuildTextChannel:setRateLimit(limit)
|
||||
return self:_modify({rate_limit_per_user = limit or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m enableNSFW
|
||||
@r boolean
|
||||
@d Enables the NSFW setting for the channel. NSFW channels are hidden from users
|
||||
until the user explicitly requests to view them.
|
||||
]=]
|
||||
function GuildTextChannel:enableNSFW()
|
||||
return self:_modify({nsfw = true})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m disableNSFW
|
||||
@r boolean
|
||||
@d Disables the NSFW setting for the channel. NSFW channels are hidden from users
|
||||
until the user explicitly requests to view them.
|
||||
]=]
|
||||
function GuildTextChannel:disableNSFW()
|
||||
return self:_modify({nsfw = false})
|
||||
end
|
||||
|
||||
--[=[@p topic string/nil The channel's topic. This should be between 1 and 1024 characters.]=]
|
||||
function get.topic(self)
|
||||
return self._topic
|
||||
end
|
||||
|
||||
--[=[@p nsfw boolean Whether this channel is marked as NSFW (not safe for work).]=]
|
||||
function get.nsfw(self)
|
||||
return self._nsfw or false
|
||||
end
|
||||
|
||||
--[=[@p rateLimit number Slowmode rate limit per guild member.]=]
|
||||
function get.rateLimit(self)
|
||||
return self._rate_limit_per_user or 0
|
||||
end
|
||||
|
||||
--[=[@p members FilteredIterable A filtered iterable of guild members that have
|
||||
permission to read this channel. If you want to check whether a specific member
|
||||
has permission to read this channel, it would be better to get the member object
|
||||
elsewhere and use `Member:hasPermission` rather than check whether the member
|
||||
exists here.]=]
|
||||
function get.members(self)
|
||||
if not self._members then
|
||||
self._members = FilteredIterable(self._parent._members, function(m)
|
||||
return m:hasPermission(self, 'readMessages')
|
||||
end)
|
||||
end
|
||||
return self._members
|
||||
end
|
||||
|
||||
return GuildTextChannel
|
||||
134
Lua/rpbot/deps/discordia/libs/containers/GuildVoiceChannel.lua
Normal file
134
Lua/rpbot/deps/discordia/libs/containers/GuildVoiceChannel.lua
Normal file
@@ -0,0 +1,134 @@
|
||||
--[=[
|
||||
@c GuildVoiceChannel x GuildChannel
|
||||
@d Represents a voice channel in a Discord guild, where guild members can connect
|
||||
and communicate via voice chat.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
|
||||
local GuildChannel = require('containers/abstract/GuildChannel')
|
||||
local VoiceConnection = require('voice/VoiceConnection')
|
||||
local TableIterable = require('iterables/TableIterable')
|
||||
|
||||
local GuildVoiceChannel, get = require('class')('GuildVoiceChannel', GuildChannel)
|
||||
|
||||
function GuildVoiceChannel:__init(data, parent)
|
||||
GuildChannel.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setBitrate
|
||||
@p bitrate number
|
||||
@r boolean
|
||||
@d Sets the channel's audio bitrate in bits per second (bps). This must be between
|
||||
8000 and 96000 (or 128000 for partnered servers). If `nil` is passed, the
|
||||
default is set, which is 64000.
|
||||
]=]
|
||||
function GuildVoiceChannel:setBitrate(bitrate)
|
||||
return self:_modify({bitrate = bitrate or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setUserLimit
|
||||
@p user_limit number
|
||||
@r boolean
|
||||
@d Sets the channel's user limit. This must be between 0 and 99 (where 0 is
|
||||
unlimited). If `nil` is passed, the default is set, which is 0.
|
||||
]=]
|
||||
function GuildVoiceChannel:setUserLimit(user_limit)
|
||||
return self:_modify({user_limit = user_limit or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m join
|
||||
@r VoiceConnection
|
||||
@d Join this channel and form a connection to the Voice Gateway.
|
||||
]=]
|
||||
function GuildVoiceChannel:join()
|
||||
|
||||
local success, err
|
||||
|
||||
local connection = self._connection
|
||||
|
||||
if connection then
|
||||
|
||||
if connection._ready then
|
||||
return connection
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
local guild = self._parent
|
||||
local client = guild._parent
|
||||
|
||||
success, err = client._shards[guild.shardId]:updateVoice(guild._id, self._id)
|
||||
|
||||
if not success then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
connection = guild._connection
|
||||
|
||||
if not connection then
|
||||
connection = VoiceConnection(self)
|
||||
guild._connection = connection
|
||||
end
|
||||
|
||||
self._connection = connection
|
||||
|
||||
end
|
||||
|
||||
success, err = connection:_await()
|
||||
|
||||
if success then
|
||||
return connection
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m leave
|
||||
@r boolean
|
||||
@d Leave this channel if there is an existing voice connection to it.
|
||||
Equivalent to GuildVoiceChannel.connection:close()
|
||||
]=]
|
||||
function GuildVoiceChannel:leave()
|
||||
if self._connection then
|
||||
return self._connection:close()
|
||||
else
|
||||
return false, 'No voice connection exists for this channel'
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p bitrate number The channel's bitrate in bits per second (bps). This should be between 8000 and
|
||||
96000 (or 128000 for partnered servers).]=]
|
||||
function get.bitrate(self)
|
||||
return self._bitrate
|
||||
end
|
||||
|
||||
--[=[@p userLimit number The amount of users allowed to be in this channel.
|
||||
Users with `moveMembers` permission ignore this limit.]=]
|
||||
function get.userLimit(self)
|
||||
return self._user_limit
|
||||
end
|
||||
|
||||
--[=[@p connectedMembers TableIterable The channel's user limit. This should between 0 and 99 (where 0 is unlimited).]=]
|
||||
function get.connectedMembers(self)
|
||||
if not self._connected_members then
|
||||
local id = self._id
|
||||
local members = self._parent._members
|
||||
self._connected_members = TableIterable(self._parent._voice_states, function(state)
|
||||
return state.channel_id == id and members:get(state.user_id)
|
||||
end)
|
||||
end
|
||||
return self._connected_members
|
||||
end
|
||||
|
||||
--[=[@p connection VoiceConnection/nil The VoiceConnection for this channel if one exists.]=]
|
||||
function get.connection(self)
|
||||
return self._connection
|
||||
end
|
||||
|
||||
return GuildVoiceChannel
|
||||
162
Lua/rpbot/deps/discordia/libs/containers/Invite.lua
Normal file
162
Lua/rpbot/deps/discordia/libs/containers/Invite.lua
Normal file
@@ -0,0 +1,162 @@
|
||||
--[=[
|
||||
@c Invite x Container
|
||||
@d Represents an invitation to a Discord guild channel. Invites can be used to join
|
||||
a guild, though they are not always permanent.
|
||||
]=]
|
||||
|
||||
local Container = require('containers/abstract/Container')
|
||||
local json = require('json')
|
||||
|
||||
local format = string.format
|
||||
local null = json.null
|
||||
|
||||
local function load(v)
|
||||
return v ~= null and v or nil
|
||||
end
|
||||
|
||||
local Invite, get = require('class')('Invite', Container)
|
||||
|
||||
function Invite:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
self._guild_id = load(data.guild.id)
|
||||
self._channel_id = load(data.channel.id)
|
||||
self._guild_name = load(data.guild.name)
|
||||
self._guild_icon = load(data.guild.icon)
|
||||
self._guild_splash = load(data.guild.splash)
|
||||
self._channel_name = load(data.channel.name)
|
||||
self._channel_type = load(data.channel.type)
|
||||
if data.inviter then
|
||||
self._inviter = self.client._users:_insert(data.inviter)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __hash
|
||||
@r string
|
||||
@d Returns `Invite.code`
|
||||
]=]
|
||||
function Invite:__hash()
|
||||
return self._code
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the invite. This cannot be undone!
|
||||
]=]
|
||||
function Invite:delete()
|
||||
local data, err = self.client._api:deleteInvite(self._code)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p code string The invite's code which can be used to identify the invite.]=]
|
||||
function get.code(self)
|
||||
return self._code
|
||||
end
|
||||
|
||||
--[=[@p guildId string The Snowflake ID of the guild to which this invite belongs.]=]
|
||||
function get.guildId(self)
|
||||
return self._guild_id
|
||||
end
|
||||
|
||||
--[=[@p guildName string The name of the guild to which this invite belongs.]=]
|
||||
function get.guildName(self)
|
||||
return self._guild_name
|
||||
end
|
||||
|
||||
--[=[@p channelId string The Snowflake ID of the channel to which this belongs.]=]
|
||||
function get.channelId(self)
|
||||
return self._channel_id
|
||||
end
|
||||
|
||||
--[=[@p channelName string The name of the channel to which this invite belongs.]=]
|
||||
function get.channelName(self)
|
||||
return self._channel_name
|
||||
end
|
||||
|
||||
--[=[@p channelType number The type of the channel to which this invite belongs. Use the `channelType`
|
||||
enumeration for a human-readable representation.]=]
|
||||
function get.channelType(self)
|
||||
return self._channel_type
|
||||
end
|
||||
|
||||
--[=[@p guildIcon string/nil The hash for the guild's custom icon, if one is set.]=]
|
||||
function get.guildIcon(self)
|
||||
return self._guild_icon
|
||||
end
|
||||
|
||||
--[=[@p guildSplash string/nil The hash for the guild's custom splash, if one is set.]=]
|
||||
function get.guildSplash(self)
|
||||
return self._guild_splash
|
||||
end
|
||||
|
||||
--[=[@p guildIconURL string/nil The URL that can be used to view the guild's icon, if one is set.]=]
|
||||
function get.guildIconURL(self)
|
||||
local icon = self._guild_icon
|
||||
return icon and format('https://cdn.discordapp.com/icons/%s/%s.png', self._guild_id, icon) or nil
|
||||
end
|
||||
|
||||
--[=[@p guildSplashURL string/nil The URL that can be used to view the guild's splash, if one is set.]=]
|
||||
function get.guildSplashURL(self)
|
||||
local splash = self._guild_splash
|
||||
return splash and format('https://cdn.discordapp.com/splashs/%s/%s.png', self._guild_id, splash) or nil
|
||||
end
|
||||
|
||||
--[=[@p inviter User/nil The object of the user that created the invite. This will not exist if the
|
||||
invite is a guild widget or a vanity invite.]=]
|
||||
function get.inviter(self)
|
||||
return self._inviter
|
||||
end
|
||||
|
||||
--[=[@p uses number/nil How many times this invite has been used. This will not exist if the invite is
|
||||
accessed via `Client:getInvite`.]=]
|
||||
function get.uses(self)
|
||||
return self._uses
|
||||
end
|
||||
|
||||
--[=[@p maxUses number/nil The maximum amount of times this invite can be used. This will not exist if the
|
||||
invite is accessed via `Client:getInvite`.]=]
|
||||
function get.maxUses(self)
|
||||
return self._max_uses
|
||||
end
|
||||
|
||||
--[=[@p maxAge number/nil How long, in seconds, this invite lasts before it expires. This will not exist
|
||||
if the invite is accessed via `Client:getInvite`.]=]
|
||||
function get.maxAge(self)
|
||||
return self._max_age
|
||||
end
|
||||
|
||||
--[=[@p temporary boolean/nil Whether the invite grants temporary membership. This will not exist if the
|
||||
invite is accessed via `Client:getInvite`.]=]
|
||||
function get.temporary(self)
|
||||
return self._temporary
|
||||
end
|
||||
|
||||
--[=[@p createdAt string The date and time at which the invite was created, represented as an ISO 8601
|
||||
string plus microseconds when available. This will not exist if the invite is
|
||||
accessed via `Client:getInvite`.]=]
|
||||
function get.createdAt(self)
|
||||
return self._created_at
|
||||
end
|
||||
|
||||
--[=[@p revoked boolean/nil Whether the invite has been revoked. This will not exist if the invite is
|
||||
accessed via `Client:getInvite`.]=]
|
||||
function get.revoked(self)
|
||||
return self._revoked
|
||||
end
|
||||
|
||||
--[=[@p approximatePresenceCount number/nil The approximate count of online members.]=]
|
||||
function get.approximatePresenceCount(self)
|
||||
return self._approximate_presence_count
|
||||
end
|
||||
|
||||
--[=[@p approximateMemberCount number/nil The approximate count of all members.]=]
|
||||
function get.approximateMemberCount(self)
|
||||
return self._approximate_member_count
|
||||
end
|
||||
|
||||
return Invite
|
||||
517
Lua/rpbot/deps/discordia/libs/containers/Member.lua
Normal file
517
Lua/rpbot/deps/discordia/libs/containers/Member.lua
Normal file
@@ -0,0 +1,517 @@
|
||||
--[=[
|
||||
@c Member x UserPresence
|
||||
@d Represents a Discord guild member. Though one user may be a member in more than
|
||||
one guild, each presence is represented by a different member object associated
|
||||
with that guild.
|
||||
]=]
|
||||
|
||||
local enums = require('enums')
|
||||
local class = require('class')
|
||||
local UserPresence = require('containers/abstract/UserPresence')
|
||||
local ArrayIterable = require('iterables/ArrayIterable')
|
||||
local Color = require('utils/Color')
|
||||
local Resolver = require('client/Resolver')
|
||||
local GuildChannel = require('containers/abstract/GuildChannel')
|
||||
local Permissions = require('utils/Permissions')
|
||||
|
||||
local insert, remove, sort = table.insert, table.remove, table.sort
|
||||
local band, bor, bnot = bit.band, bit.bor, bit.bnot
|
||||
local isInstance = class.isInstance
|
||||
local permission = enums.permission
|
||||
|
||||
local Member, get = class('Member', UserPresence)
|
||||
|
||||
function Member:__init(data, parent)
|
||||
UserPresence.__init(self, data, parent)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Member:_load(data)
|
||||
UserPresence._load(self, data)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Member:_loadMore(data)
|
||||
if data.roles then
|
||||
local roles = #data.roles > 0 and data.roles or nil
|
||||
if self._roles then
|
||||
self._roles._array = roles
|
||||
else
|
||||
self._roles_raw = roles
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function sorter(a, b)
|
||||
if a._position == b._position then
|
||||
return tonumber(a._id) < tonumber(b._id)
|
||||
else
|
||||
return a._position > b._position
|
||||
end
|
||||
end
|
||||
|
||||
local function predicate(role)
|
||||
return role._color > 0
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getColor
|
||||
@r Color
|
||||
@d Returns a color object that represents the member's color as determined by
|
||||
its highest colored role. If the member has no colored roles, then the default
|
||||
color with a value of 0 is returned.
|
||||
]=]
|
||||
function Member:getColor()
|
||||
local roles = {}
|
||||
for role in self.roles:findAll(predicate) do
|
||||
insert(roles, role)
|
||||
end
|
||||
sort(roles, sorter)
|
||||
return roles[1] and roles[1]:getColor() or Color()
|
||||
end
|
||||
|
||||
local function has(a, b, admin)
|
||||
return band(a, b) > 0 or admin and band(a, permission.administrator) > 0
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m hasPermission
|
||||
@op channel GuildChannel
|
||||
@p perm Permissions-Resolvable
|
||||
@r boolean
|
||||
@d Checks whether the member has a specific permission. If `channel` is omitted,
|
||||
then only guild-level permissions are checked. This is a relatively expensive
|
||||
operation. If you need to check multiple permissions at once, use the
|
||||
`getPermissions` method and check the resulting object.
|
||||
]=]
|
||||
function Member:hasPermission(channel, perm)
|
||||
|
||||
if not perm then
|
||||
perm = channel
|
||||
channel = nil
|
||||
end
|
||||
|
||||
local guild = self.guild
|
||||
if channel then
|
||||
if not isInstance(channel, GuildChannel) or channel.guild ~= guild then
|
||||
return error('Invalid GuildChannel: ' .. tostring(channel), 2)
|
||||
end
|
||||
end
|
||||
|
||||
local n = Resolver.permission(perm)
|
||||
if not n then
|
||||
return error('Invalid permission: ' .. tostring(perm), 2)
|
||||
end
|
||||
|
||||
if self.id == guild.ownerId then
|
||||
return true
|
||||
end
|
||||
|
||||
if channel then
|
||||
|
||||
local overwrites = channel.permissionOverwrites
|
||||
|
||||
local overwrite = overwrites:get(self.id)
|
||||
if overwrite then
|
||||
if has(overwrite.allowedPermissions, n) then
|
||||
return true
|
||||
end
|
||||
if has(overwrite.deniedPermissions, n) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local allow, deny = 0, 0
|
||||
for role in self.roles:iter() do
|
||||
if role.id ~= guild.id then -- just in case
|
||||
overwrite = overwrites:get(role.id)
|
||||
if overwrite then
|
||||
allow = bor(allow, overwrite.allowedPermissions)
|
||||
deny = bor(deny, overwrite.deniedPermissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if has(allow, n) then
|
||||
return true
|
||||
end
|
||||
if has(deny, n) then
|
||||
return false
|
||||
end
|
||||
|
||||
local everyone = overwrites:get(guild.id)
|
||||
if everyone then
|
||||
if has(everyone.allowedPermissions, n) then
|
||||
return true
|
||||
end
|
||||
if has(everyone.deniedPermissions, n) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
for role in self.roles:iter() do
|
||||
if role.id ~= guild.id then -- just in case
|
||||
if has(role.permissions, n, true) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if has(guild.defaultRole.permissions, n, true) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPermissions
|
||||
@op channel GuildChannel
|
||||
@r Permissions
|
||||
@d Returns a permissions object that represents the member's total permissions for
|
||||
the guild, or for a specific channel if one is provided. If you just need to
|
||||
check one permission, use the `hasPermission` method.
|
||||
]=]
|
||||
function Member:getPermissions(channel)
|
||||
|
||||
local guild = self.guild
|
||||
if channel then
|
||||
if not isInstance(channel, GuildChannel) or channel.guild ~= guild then
|
||||
return error('Invalid GuildChannel: ' .. tostring(channel), 2)
|
||||
end
|
||||
end
|
||||
|
||||
if self.id == guild.ownerId then
|
||||
return Permissions.all()
|
||||
end
|
||||
|
||||
local ret = guild.defaultRole.permissions
|
||||
|
||||
for role in self.roles:iter() do
|
||||
if role.id ~= guild.id then -- just in case
|
||||
ret = bor(ret, role.permissions)
|
||||
end
|
||||
end
|
||||
|
||||
if band(ret, permission.administrator) > 0 then
|
||||
return Permissions.all()
|
||||
end
|
||||
|
||||
if channel then
|
||||
|
||||
local overwrites = channel.permissionOverwrites
|
||||
|
||||
local everyone = overwrites:get(guild.id)
|
||||
if everyone then
|
||||
ret = band(ret, bnot(everyone.deniedPermissions))
|
||||
ret = bor(ret, everyone.allowedPermissions)
|
||||
end
|
||||
|
||||
local allow, deny = 0, 0
|
||||
for role in self.roles:iter() do
|
||||
if role.id ~= guild.id then -- just in case
|
||||
local overwrite = overwrites:get(role.id)
|
||||
if overwrite then
|
||||
deny = bor(deny, overwrite.deniedPermissions)
|
||||
allow = bor(allow, overwrite.allowedPermissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
ret = band(ret, bnot(deny))
|
||||
ret = bor(ret, allow)
|
||||
|
||||
local overwrite = overwrites:get(self.id)
|
||||
if overwrite then
|
||||
ret = band(ret, bnot(overwrite.deniedPermissions))
|
||||
ret = bor(ret, overwrite.allowedPermissions)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return Permissions(ret)
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m addRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r boolean
|
||||
@d Adds a role to the member. If the member already has the role, then no action is
|
||||
taken. Note that the everyone role cannot be explicitly added.
|
||||
]=]
|
||||
function Member:addRole(id)
|
||||
if self:hasRole(id) then return true end
|
||||
id = Resolver.roleId(id)
|
||||
local data, err = self.client._api:addGuildMemberRole(self._parent._id, self.id, id)
|
||||
if data then
|
||||
local roles = self._roles and self._roles._array or self._roles_raw
|
||||
if roles then
|
||||
insert(roles, id)
|
||||
else
|
||||
self._roles_raw = {id}
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m removeRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r boolean
|
||||
@d Removes a role from the member. If the member does not have the role, then no
|
||||
action is taken. Note that the everyone role cannot be removed.
|
||||
]=]
|
||||
function Member:removeRole(id)
|
||||
if not self:hasRole(id) then return true end
|
||||
id = Resolver.roleId(id)
|
||||
local data, err = self.client._api:removeGuildMemberRole(self._parent._id, self.id, id)
|
||||
if data then
|
||||
local roles = self._roles and self._roles._array or self._roles_raw
|
||||
if roles then
|
||||
for i, v in ipairs(roles) do
|
||||
if v == id then
|
||||
remove(roles, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
if #roles == 0 then
|
||||
if self._roles then
|
||||
self._roles._array = nil
|
||||
else
|
||||
self._roles_raw = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m hasRole
|
||||
@p id Role-ID-Resolvable
|
||||
@r boolean
|
||||
@d Checks whether the member has a specific role. This will return true for the
|
||||
guild's default role in addition to any explicitly assigned roles.
|
||||
]=]
|
||||
function Member:hasRole(id)
|
||||
id = Resolver.roleId(id)
|
||||
if id == self._parent._id then return true end -- @everyone
|
||||
local roles = self._roles and self._roles._array or self._roles_raw
|
||||
if roles then
|
||||
for _, v in ipairs(roles) do
|
||||
if v == id then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setNickname
|
||||
@p nick string
|
||||
@r boolean
|
||||
@d Sets the member's nickname. This must be between 1 and 32 characters in length.
|
||||
Pass `nil` to remove the nickname.
|
||||
]=]
|
||||
function Member:setNickname(nick)
|
||||
nick = nick or ''
|
||||
local data, err
|
||||
if self.id == self.client._user._id then
|
||||
data, err = self.client._api:modifyCurrentUsersNick(self._parent._id, {nick = nick})
|
||||
else
|
||||
data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {nick = nick})
|
||||
end
|
||||
if data then
|
||||
self._nick = nick ~= '' and nick or nil
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setVoiceChannel
|
||||
@p id Channel-ID-Resolvable
|
||||
@r boolean
|
||||
@d Moves the member to a new voice channel, but only if the member has an active
|
||||
voice connection in the current guild. Due to complexities in voice state
|
||||
handling, the member's `voiceChannel` property will update asynchronously via
|
||||
WebSocket; not as a result of the HTTP request.
|
||||
]=]
|
||||
function Member:setVoiceChannel(id)
|
||||
id = Resolver.channelId(id)
|
||||
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {channel_id = id})
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m mute
|
||||
@r boolean
|
||||
@d Mutes the member in its guild.
|
||||
]=]
|
||||
function Member:mute()
|
||||
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {mute = true})
|
||||
if data then
|
||||
self._mute = true
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m unmute
|
||||
@r boolean
|
||||
@d Unmutes the member in its guild.
|
||||
]=]
|
||||
function Member:unmute()
|
||||
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {mute = false})
|
||||
if data then
|
||||
self._mute = false
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m deafen
|
||||
@r boolean
|
||||
@d Deafens the member in its guild.
|
||||
]=]
|
||||
function Member:deafen()
|
||||
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {deaf = true})
|
||||
if data then
|
||||
self._deaf = true
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m undeafen
|
||||
@r boolean
|
||||
@d Undeafens the member in its guild.
|
||||
]=]
|
||||
function Member:undeafen()
|
||||
local data, err = self.client._api:modifyGuildMember(self._parent._id, self.id, {deaf = false})
|
||||
if data then
|
||||
self._deaf = false
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m kick
|
||||
@p reason string
|
||||
@r boolean
|
||||
@d Equivalent to `Member.guild:kickUser(Member.user, reason)`
|
||||
]=]
|
||||
function Member:kick(reason)
|
||||
return self._parent:kickUser(self._user, reason)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m ban
|
||||
@p reason string
|
||||
@p days number
|
||||
@r boolean
|
||||
@d Equivalent to `Member.guild:banUser(Member.user, reason, days)`
|
||||
]=]
|
||||
function Member:ban(reason, days)
|
||||
return self._parent:banUser(self._user, reason, days)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m unban
|
||||
@p reason string
|
||||
@r boolean
|
||||
@d Equivalent to `Member.guild:unbanUser(Member.user, reason)`
|
||||
]=]
|
||||
function Member:unban(reason)
|
||||
return self._parent:unbanUser(self._user, reason)
|
||||
end
|
||||
|
||||
--[=[@p roles ArrayIterable An iterable array of guild roles that the member has. This does not explicitly
|
||||
include the default everyone role. Object order is not guaranteed.]=]
|
||||
function get.roles(self)
|
||||
if not self._roles then
|
||||
local roles = self._parent._roles
|
||||
self._roles = ArrayIterable(self._roles_raw, function(id)
|
||||
return roles:get(id)
|
||||
end)
|
||||
self._roles_raw = nil
|
||||
end
|
||||
return self._roles
|
||||
end
|
||||
|
||||
--[=[@p name string If the member has a nickname, then this will be equivalent to that nickname.
|
||||
Otherwise, this is equivalent to `Member.user.username`.]=]
|
||||
function get.name(self)
|
||||
return self._nick or self._user._username
|
||||
end
|
||||
|
||||
--[=[@p nickname string/nil The member's nickname, if one is set.]=]
|
||||
function get.nickname(self)
|
||||
return self._nick
|
||||
end
|
||||
|
||||
--[=[@p joinedAt string/nil The date and time at which the current member joined the guild, represented as
|
||||
an ISO 8601 string plus microseconds when available. Member objects generated
|
||||
via presence updates lack this property.]=]
|
||||
function get.joinedAt(self)
|
||||
return self._joined_at
|
||||
end
|
||||
|
||||
--[=[@p voiceChannel GuildVoiceChannel/nil The voice channel to which this member is connected in the current guild.]=]
|
||||
function get.voiceChannel(self)
|
||||
local guild = self._parent
|
||||
local state = guild._voice_states[self:__hash()]
|
||||
return state and guild._voice_channels:get(state.channel_id)
|
||||
end
|
||||
|
||||
--[=[@p muted boolean Whether the member is voice muted in its guild.]=]
|
||||
function get.muted(self)
|
||||
local state = self._parent._voice_states[self:__hash()]
|
||||
return state and (state.mute or state.self_mute) or self._mute
|
||||
end
|
||||
|
||||
--[=[@p deafened boolean Whether the member is voice deafened in its guild.]=]
|
||||
function get.deafened(self)
|
||||
local state = self._parent._voice_states[self:__hash()]
|
||||
return state and (state.deaf or state.self_deaf) or self._deaf
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this member exists.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p highestRole Role The highest positioned role that the member has. If the member has no
|
||||
explicit roles, then this is equivalent to `Member.guild.defaultRole`.]=]
|
||||
function get.highestRole(self)
|
||||
local ret
|
||||
for role in self.roles:iter() do
|
||||
if not ret or sorter(role, ret) then
|
||||
ret = role
|
||||
end
|
||||
end
|
||||
return ret or self.guild.defaultRole
|
||||
end
|
||||
|
||||
return Member
|
||||
513
Lua/rpbot/deps/discordia/libs/containers/Message.lua
Normal file
513
Lua/rpbot/deps/discordia/libs/containers/Message.lua
Normal file
@@ -0,0 +1,513 @@
|
||||
--[=[
|
||||
@c Message x Snowflake
|
||||
@d Represents a text message sent in a Discord text channel. Messages can contain
|
||||
simple content strings, rich embeds, attachments, or reactions.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local constants = require('constants')
|
||||
local Cache = require('iterables/Cache')
|
||||
local ArrayIterable = require('iterables/ArrayIterable')
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local Reaction = require('containers/Reaction')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local insert = table.insert
|
||||
local null = json.null
|
||||
local format = string.format
|
||||
|
||||
local Message, get = require('class')('Message', Snowflake)
|
||||
|
||||
function Message:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
self._author = self.client._users:_insert(data.author)
|
||||
if data.member then
|
||||
data.member.user = data.author
|
||||
self._parent._parent._members:_insert(data.member)
|
||||
end
|
||||
self._timestamp = nil -- waste of space; can be calculated from Snowflake ID
|
||||
if data.reactions and #data.reactions > 0 then
|
||||
self._reactions = Cache(data.reactions, Reaction, self)
|
||||
end
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function Message:_load(data)
|
||||
Snowflake._load(self, data)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
local function parseMentions(content, pattern)
|
||||
if not content:find('%b<>') then return end
|
||||
local mentions, seen = {}, {}
|
||||
for id in content:gmatch(pattern) do
|
||||
if not seen[id] then
|
||||
insert(mentions, id)
|
||||
seen[id] = true
|
||||
end
|
||||
end
|
||||
return mentions
|
||||
end
|
||||
|
||||
function Message:_loadMore(data)
|
||||
|
||||
if data.mentions then
|
||||
for _, user in ipairs(data.mentions) do
|
||||
if user.member then
|
||||
user.member.user = user
|
||||
self._parent._parent._members:_insert(user.member)
|
||||
else
|
||||
self.client._users:_insert(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local content = data.content
|
||||
if content then
|
||||
if self._mentioned_users then
|
||||
self._mentioned_users._array = parseMentions(content, '<@!?(%d+)>')
|
||||
end
|
||||
if self._mentioned_roles then
|
||||
self._mentioned_roles._array = parseMentions(content, '<@&(%d+)>')
|
||||
end
|
||||
if self._mentioned_channels then
|
||||
self._mentioned_channels._array = parseMentions(content, '<#(%d+)>')
|
||||
end
|
||||
if self._mentioned_emojis then
|
||||
self._mentioned_emojis._array = parseMentions(content, '<a?:[%w_]+:(%d+)>')
|
||||
end
|
||||
self._clean_content = nil
|
||||
end
|
||||
|
||||
if data.embeds then
|
||||
self._embeds = #data.embeds > 0 and data.embeds or nil
|
||||
end
|
||||
|
||||
if data.attachments then
|
||||
self._attachments = #data.attachments > 0 and data.attachments or nil
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function Message:_addReaction(d)
|
||||
|
||||
local reactions = self._reactions
|
||||
|
||||
if not reactions then
|
||||
reactions = Cache({}, Reaction, self)
|
||||
self._reactions = reactions
|
||||
end
|
||||
|
||||
local emoji = d.emoji
|
||||
local k = emoji.id ~= null and emoji.id or emoji.name
|
||||
local reaction = reactions:get(k)
|
||||
|
||||
if reaction then
|
||||
reaction._count = reaction._count + 1
|
||||
if d.user_id == self.client._user._id then
|
||||
reaction._me = true
|
||||
end
|
||||
else
|
||||
d.me = d.user_id == self.client._user._id
|
||||
d.count = 1
|
||||
reaction = reactions:_insert(d)
|
||||
end
|
||||
return reaction
|
||||
|
||||
end
|
||||
|
||||
function Message:_removeReaction(d)
|
||||
|
||||
local reactions = self._reactions
|
||||
|
||||
local emoji = d.emoji
|
||||
local k = emoji.id ~= null and emoji.id or emoji.name
|
||||
local reaction = reactions:get(k)
|
||||
|
||||
if not reaction then return nil end -- uncached reaction?
|
||||
|
||||
reaction._count = reaction._count - 1
|
||||
if d.user_id == self.client._user._id then
|
||||
reaction._me = false
|
||||
end
|
||||
|
||||
if reaction._count == 0 then
|
||||
reactions:_delete(k)
|
||||
end
|
||||
|
||||
return reaction
|
||||
|
||||
end
|
||||
|
||||
function Message:_setOldContent(d)
|
||||
local ts = d.edited_timestamp
|
||||
if not ts then return end
|
||||
local old = self._old
|
||||
if old then
|
||||
old[ts] = old[ts] or self._content
|
||||
else
|
||||
self._old = {[ts] = self._content}
|
||||
end
|
||||
end
|
||||
|
||||
function Message:_modify(payload)
|
||||
local data, err = self.client._api:editMessage(self._parent._id, self._id, payload)
|
||||
if data then
|
||||
self:_setOldContent(data)
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setContent
|
||||
@p content string
|
||||
@r boolean
|
||||
@d Sets the message's content. The message must be authored by the current user
|
||||
(ie: you cannot change the content of messages sent by other users). The content
|
||||
must be from 1 to 2000 characters in length.
|
||||
]=]
|
||||
function Message:setContent(content)
|
||||
return self:_modify({content = content or null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setEmbed
|
||||
@p embed table
|
||||
@r boolean
|
||||
@d Sets the message's embed. The message must be authored by the current user.
|
||||
(ie: you cannot change the embed of messages sent by other users).
|
||||
]=]
|
||||
function Message:setEmbed(embed)
|
||||
return self:_modify({embed = embed or null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m pin
|
||||
@r boolean
|
||||
@d Pins the message in the channel.
|
||||
]=]
|
||||
function Message:pin()
|
||||
local data, err = self.client._api:addPinnedChannelMessage(self._parent._id, self._id)
|
||||
if data then
|
||||
self._pinned = true
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m unpin
|
||||
@r boolean
|
||||
@d Unpins the message in the channel.
|
||||
]=]
|
||||
function Message:unpin()
|
||||
local data, err = self.client._api:deletePinnedChannelMessage(self._parent._id, self._id)
|
||||
if data then
|
||||
self._pinned = false
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m addReaction
|
||||
@p emoji Emoji-Resolvable
|
||||
@r boolean
|
||||
@d Adds a reaction to the message. Note that this does not return the new reaction
|
||||
object; wait for the `reactionAdd` event instead.
|
||||
]=]
|
||||
function Message:addReaction(emoji)
|
||||
emoji = Resolver.emoji(emoji)
|
||||
local data, err = self.client._api:createReaction(self._parent._id, self._id, emoji)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m removeReaction
|
||||
@p emoji Emoji-Resolvable
|
||||
@op id User-ID-Resolvable
|
||||
@r boolean
|
||||
@d Removes a reaction from the message. Note that this does not return the old
|
||||
reaction object; wait for the `reactionAdd` event instead. If no user is
|
||||
indicated, then this will remove the current user's reaction.
|
||||
]=]
|
||||
function Message:removeReaction(emoji, id)
|
||||
emoji = Resolver.emoji(emoji)
|
||||
local data, err
|
||||
if id then
|
||||
id = Resolver.userId(id)
|
||||
data, err = self.client._api:deleteUserReaction(self._parent._id, self._id, emoji, id)
|
||||
else
|
||||
data, err = self.client._api:deleteOwnReaction(self._parent._id, self._id, emoji)
|
||||
end
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m clearReactions
|
||||
@r boolean
|
||||
@d Removes all reactions from the message.
|
||||
]=]
|
||||
function Message:clearReactions()
|
||||
local data, err = self.client._api:deleteAllReactions(self._parent._id, self._id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the message. This cannot be undone!
|
||||
]=]
|
||||
function Message:delete()
|
||||
local data, err = self.client._api:deleteMessage(self._parent._id, self._id)
|
||||
if data then
|
||||
local cache = self._parent._messages
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m reply
|
||||
@p content string/table
|
||||
@r Message
|
||||
@d Equivalent to `Message.channel:send(content)`.
|
||||
]=]
|
||||
function Message:reply(content)
|
||||
return self._parent:send(content)
|
||||
end
|
||||
|
||||
--[=[@p reactions Cache An iterable cache of all reactions that exist for this message.]=]
|
||||
function get.reactions(self)
|
||||
if not self._reactions then
|
||||
self._reactions = Cache({}, Reaction, self)
|
||||
end
|
||||
return self._reactions
|
||||
end
|
||||
|
||||
--[=[@p mentionedUsers ArrayIterable An iterable array of all users that are mentioned in this message. Object order
|
||||
is not guaranteed.]=]
|
||||
function get.mentionedUsers(self)
|
||||
if not self._mentioned_users then
|
||||
local users = self.client._users
|
||||
local mentions = parseMentions(self._content, '<@!?(%d+)>')
|
||||
self._mentioned_users = ArrayIterable(mentions, function(id)
|
||||
return users:get(id)
|
||||
end)
|
||||
end
|
||||
return self._mentioned_users
|
||||
end
|
||||
|
||||
--[=[@p mentionedRoles ArrayIterable An iterable array of known roles that are mentioned in this message, excluding
|
||||
the default everyone role. The message must be in a guild text channel and the
|
||||
roles must be cached in that channel's guild for them to appear here. Object
|
||||
order is not guaranteed.]=]
|
||||
function get.mentionedRoles(self)
|
||||
if not self._mentioned_roles then
|
||||
local client = self.client
|
||||
local mentions = parseMentions(self._content, '<@&(%d+)>')
|
||||
self._mentioned_roles = ArrayIterable(mentions, function(id)
|
||||
local guild = client._role_map[id]
|
||||
return guild and guild._roles:get(id) or nil
|
||||
end)
|
||||
end
|
||||
return self._mentioned_roles
|
||||
end
|
||||
|
||||
--[=[@p mentionedEmojis ArrayIterable An iterable array of all known emojis that are mentioned in this message. If
|
||||
the client does not have the emoji cached, then it will not appear here. Object order is not guaranteed.]=]
|
||||
function get.mentionedEmojis(self)
|
||||
if not self._mentioned_emojis then
|
||||
local client = self.client
|
||||
local mentions = parseMentions(self._content, '<a?:[%w_]+:(%d+)>')
|
||||
self._mentioned_emojis = ArrayIterable(mentions, function(id)
|
||||
local guild = client._emoji_map[id]
|
||||
return guild and guild._emojis:get(id)
|
||||
end)
|
||||
end
|
||||
return self._mentioned_emojis
|
||||
end
|
||||
|
||||
--[=[@p mentionedChannels ArrayIterable An iterable array of all known channels that are mentioned in this message. If
|
||||
the client does not have the channel cached, then it will not appear here.
|
||||
Object order is not guaranteed.]=]
|
||||
function get.mentionedChannels(self)
|
||||
if not self._mentioned_channels then
|
||||
local client = self.client
|
||||
local mentions = parseMentions(self._content, '<#(%d+)>')
|
||||
self._mentioned_channels = ArrayIterable(mentions, function(id)
|
||||
local guild = client._channel_map[id]
|
||||
if guild then
|
||||
return guild._text_channels:get(id) or guild._voice_channels:get(id) or guild._categories:get(id)
|
||||
else
|
||||
return client._private_channels:get(id) or client._group_channels:get(id)
|
||||
end
|
||||
end)
|
||||
end
|
||||
return self._mentioned_channels
|
||||
end
|
||||
|
||||
local usersMeta = {__index = function(_, k) return '@' .. k end}
|
||||
local rolesMeta = {__index = function(_, k) return '@' .. k end}
|
||||
local channelsMeta = {__index = function(_, k) return '#' .. k end}
|
||||
local everyone = '@' .. constants.ZWSP .. 'everyone'
|
||||
local here = '@' .. constants.ZWSP .. 'here'
|
||||
|
||||
--[=[@p cleanContent string The message content with all recognized mentions replaced by names and with
|
||||
@everyone and @here mentions escaped by a zero-width space (ZWSP).]=]
|
||||
function get.cleanContent(self)
|
||||
|
||||
if not self._clean_content then
|
||||
|
||||
local content = self._content
|
||||
local guild = self.guild
|
||||
|
||||
local users = setmetatable({}, usersMeta)
|
||||
for user in self.mentionedUsers:iter() do
|
||||
local member = guild and guild._members:get(user._id)
|
||||
users[user._id] = '@' .. (member and member._nick or user._username)
|
||||
end
|
||||
|
||||
local roles = setmetatable({}, rolesMeta)
|
||||
for role in self.mentionedRoles:iter() do
|
||||
roles[role._id] = '@' .. role._name
|
||||
end
|
||||
|
||||
local channels = setmetatable({}, channelsMeta)
|
||||
for channel in self.mentionedChannels:iter() do
|
||||
channels[channel._id] = '#' .. channel._name
|
||||
end
|
||||
|
||||
self._clean_content = content
|
||||
:gsub('<@!?(%d+)>', users)
|
||||
:gsub('<@&(%d+)>', roles)
|
||||
:gsub('<#(%d+)>', channels)
|
||||
:gsub('<a?(:.+:)%d+>', '%1')
|
||||
:gsub('@everyone', everyone)
|
||||
:gsub('@here', here)
|
||||
|
||||
end
|
||||
|
||||
return self._clean_content
|
||||
|
||||
end
|
||||
|
||||
--[=[@p mentionsEveryone boolean Whether this message mentions @everyone or @here.]=]
|
||||
function get.mentionsEveryone(self)
|
||||
return self._mention_everyone
|
||||
end
|
||||
|
||||
--[=[@p pinned boolean Whether this message belongs to its channel's pinned messages.]=]
|
||||
function get.pinned(self)
|
||||
return self._pinned
|
||||
end
|
||||
|
||||
--[=[@p tts boolean Whether this message is a text-to-speech message.]=]
|
||||
function get.tts(self)
|
||||
return self._tts
|
||||
end
|
||||
|
||||
--[=[@p nonce string/number/boolean/nil Used by the official Discord client to detect the success of a sent message.]=]
|
||||
function get.nonce(self)
|
||||
return self._nonce
|
||||
end
|
||||
|
||||
--[=[@p editedTimestamp string/nil The date and time at which the message was most recently edited, represented as
|
||||
an ISO 8601 string plus microseconds when available.]=]
|
||||
function get.editedTimestamp(self)
|
||||
return self._edited_timestamp
|
||||
end
|
||||
|
||||
--[=[@p oldContent string/table Yields a table containing keys as timestamps and
|
||||
value as content of the message at that time.]=]
|
||||
function get.oldContent(self)
|
||||
return self._old
|
||||
end
|
||||
|
||||
--[=[@p content string The raw message content. This should be between 0 and 2000 characters in length.]=]
|
||||
function get.content(self)
|
||||
return self._content
|
||||
end
|
||||
|
||||
--[=[@p author User The object of the user that created the message.]=]
|
||||
function get.author(self)
|
||||
return self._author
|
||||
end
|
||||
|
||||
--[=[@p channel TextChannel The channel in which this message was sent.]=]
|
||||
function get.channel(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p type number The message type. Use the `messageType` enumeration for a human-readable
|
||||
representation.]=]
|
||||
function get.type(self)
|
||||
return self._type
|
||||
end
|
||||
|
||||
--[=[@p embed table/nil A raw data table that represents the first rich embed that exists in this
|
||||
message. See the Discord documentation for more information.]=]
|
||||
function get.embed(self)
|
||||
return self._embeds and self._embeds[1]
|
||||
end
|
||||
|
||||
--[=[@p attachment table/nil A raw data table that represents the first file attachment that exists in this
|
||||
message. See the Discord documentation for more information.]=]
|
||||
function get.attachment(self)
|
||||
return self._attachments and self._attachments[1]
|
||||
end
|
||||
|
||||
--[=[@p embeds table A raw data table that contains all embeds that exist for this message. If
|
||||
there are none, this table will not be present.]=]
|
||||
function get.embeds(self)
|
||||
return self._embeds
|
||||
end
|
||||
|
||||
--[=[@p attachments table A raw data table that contains all attachments that exist for this message. If
|
||||
there are none, this table will not be present.]=]
|
||||
function get.attachments(self)
|
||||
return self._attachments
|
||||
end
|
||||
|
||||
--[=[@p guild Guild/nil The guild in which this message was sent. This will not exist if the message
|
||||
was not sent in a guild text channel. Equivalent to `Message.channel.guild`.]=]
|
||||
function get.guild(self)
|
||||
return self._parent.guild
|
||||
end
|
||||
|
||||
--[=[@p member Member/nil The member object of the message's author. This will not exist if the message
|
||||
is not sent in a guild text channel or if the member object is not cached.
|
||||
Equivalent to `Message.guild.members:get(Message.author.id)`.]=]
|
||||
function get.member(self)
|
||||
local guild = self.guild
|
||||
return guild and guild._members:get(self._author._id)
|
||||
end
|
||||
|
||||
--[=[@p link string URL that can be used to jump-to the message in the Discord client.]=]
|
||||
function get.link(self)
|
||||
local guild = self.guild
|
||||
return format('https://discordapp.com/channels/%s/%s/%s', guild and guild._id or '@me', self._parent._id, self._id)
|
||||
end
|
||||
|
||||
return Message
|
||||
221
Lua/rpbot/deps/discordia/libs/containers/PermissionOverwrite.lua
Normal file
221
Lua/rpbot/deps/discordia/libs/containers/PermissionOverwrite.lua
Normal file
@@ -0,0 +1,221 @@
|
||||
--[=[
|
||||
@c PermissionOverwrite x Snowflake
|
||||
@d Represents an object that is used to allow or deny specific permissions for a
|
||||
role or member in a Discord guild channel.
|
||||
]=]
|
||||
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local Permissions = require('utils/Permissions')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local band, bnot = bit.band, bit.bnot
|
||||
|
||||
local PermissionOverwrite, get = require('class')('PermissionOverwrite', Snowflake)
|
||||
|
||||
function PermissionOverwrite:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Deletes the permission overwrite. This can be undone by created a new version of
|
||||
the same overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:delete()
|
||||
local data, err = self.client._api:deleteChannelPermission(self._parent._id, self._id)
|
||||
if data then
|
||||
local cache = self._parent._permission_overwrites
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getObject
|
||||
@r Role/Member
|
||||
@d Returns the object associated with this overwrite, either a role or member.
|
||||
This may make an HTTP request if the object is not cached.
|
||||
]=]
|
||||
function PermissionOverwrite:getObject()
|
||||
local guild = self._parent._parent
|
||||
if self._type == 'role' then
|
||||
return guild:getRole(self._id)
|
||||
elseif self._type == 'member' then
|
||||
return guild:getMember(self._id)
|
||||
end
|
||||
end
|
||||
|
||||
local function getPermissions(self)
|
||||
return Permissions(self._allow), Permissions(self._deny)
|
||||
end
|
||||
|
||||
local function setPermissions(self, allow, deny)
|
||||
local data, err = self.client._api:editChannelPermissions(self._parent._id, self._id, {
|
||||
allow = allow, deny = deny, type = self._type
|
||||
})
|
||||
if data then
|
||||
self._allow, self._deny = allow, deny
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getAllowedPermissions
|
||||
@r Permissions
|
||||
@d Returns a permissions object that represents the permissions that this overwrite
|
||||
explicitly allows.
|
||||
]=]
|
||||
function PermissionOverwrite:getAllowedPermissions()
|
||||
return Permissions(self._allow)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getDeniedPermissions
|
||||
@r Permissions
|
||||
@d Returns a permissions object that represents the permissions that this overwrite
|
||||
explicitly denies.
|
||||
]=]
|
||||
function PermissionOverwrite:getDeniedPermissions()
|
||||
return Permissions(self._deny)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setPermissions
|
||||
@p allowed Permissions-Resolvables
|
||||
@p denied Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Sets the permissions that this overwrite explicitly allows and denies. This
|
||||
method does NOT resolve conflicts. Please be sure to use the correct parameters.
|
||||
]=]
|
||||
function PermissionOverwrite:setPermissions(allowed, denied)
|
||||
local allow = Resolver.permissions(allowed)
|
||||
local deny = Resolver.permissions(denied)
|
||||
return setPermissions(self, allow, deny)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAllowedPermissions
|
||||
@p allowed Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Sets the permissions that this overwrite explicitly allows.
|
||||
]=]
|
||||
function PermissionOverwrite:setAllowedPermissions(allowed)
|
||||
local allow = Resolver.permissions(allowed)
|
||||
local deny = band(bnot(allow), self._deny) -- un-deny the allowed permissions
|
||||
return setPermissions(self, allow, deny)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setDeniedPermissions
|
||||
@p denied Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Sets the permissions that this overwrite explicitly denies.
|
||||
]=]
|
||||
function PermissionOverwrite:setDeniedPermissions(denied)
|
||||
local deny = Resolver.permissions(denied)
|
||||
local allow = band(bnot(deny), self._allow) -- un-allow the denied permissions
|
||||
return setPermissions(self, allow, deny)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m allowPermissions
|
||||
@p ... Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Allows individual permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:allowPermissions(...)
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:enable(...); denied:disable(...)
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m denyPermissions
|
||||
@p ... Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Denies individual permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:denyPermissions(...)
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:disable(...); denied:enable(...)
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m clearPermissions
|
||||
@p ... Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Clears individual permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:clearPermissions(...)
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:disable(...); denied:disable(...)
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m allowAllPermissions
|
||||
@r boolean
|
||||
@d Allows all permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:allowAllPermissions()
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:enableAll(); denied:disableAll()
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m denyAllPermissions
|
||||
@r boolean
|
||||
@d Denies all permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:denyAllPermissions()
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:disableAll(); denied:enableAll()
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m clearAllPermissions
|
||||
@r boolean
|
||||
@d Clears all permissions in this overwrite.
|
||||
]=]
|
||||
function PermissionOverwrite:clearAllPermissions()
|
||||
local allowed, denied = getPermissions(self)
|
||||
allowed:disableAll(); denied:disableAll()
|
||||
return setPermissions(self, allowed._value, denied._value)
|
||||
end
|
||||
|
||||
--[=[@p type string The overwrite type; either "role" or "member".]=]
|
||||
function get.type(self)
|
||||
return self._type
|
||||
end
|
||||
|
||||
--[=[@p channel GuildChannel The channel in which this overwrite exists.]=]
|
||||
function get.channel(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this overwrite exists. Equivalent to `PermissionOverwrite.channel.guild`.]=]
|
||||
function get.guild(self)
|
||||
return self._parent._parent
|
||||
end
|
||||
|
||||
--[=[@p allowedPermissions number The number representing the total permissions allowed by this overwrite.]=]
|
||||
function get.allowedPermissions(self)
|
||||
return self._allow
|
||||
end
|
||||
|
||||
--[=[@p deniedPermissions number The number representing the total permissions denied by this overwrite.]=]
|
||||
function get.deniedPermissions(self)
|
||||
return self._deny
|
||||
end
|
||||
|
||||
return PermissionOverwrite
|
||||
36
Lua/rpbot/deps/discordia/libs/containers/PrivateChannel.lua
Normal file
36
Lua/rpbot/deps/discordia/libs/containers/PrivateChannel.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
--[=[
|
||||
@c PrivateChannel x TextChannel
|
||||
@d Represents a private Discord text channel used to track correspondences between
|
||||
the current user and one other recipient.
|
||||
]=]
|
||||
|
||||
local TextChannel = require('containers/abstract/TextChannel')
|
||||
|
||||
local PrivateChannel, get = require('class')('PrivateChannel', TextChannel)
|
||||
|
||||
function PrivateChannel:__init(data, parent)
|
||||
TextChannel.__init(self, data, parent)
|
||||
self._recipient = self.client._users:_insert(data.recipients[1])
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m close
|
||||
@r boolean
|
||||
@d Closes the channel. This does not delete the channel. To re-open the channel,
|
||||
use `User:getPrivateChannel`.
|
||||
]=]
|
||||
function PrivateChannel:close()
|
||||
return self:_delete()
|
||||
end
|
||||
|
||||
--[=[@p name string Equivalent to `PrivateChannel.recipient.username`.]=]
|
||||
function get.name(self)
|
||||
return self._recipient._username
|
||||
end
|
||||
|
||||
--[=[@p recipient User The recipient of this channel's messages, other than the current user.]=]
|
||||
function get.recipient(self)
|
||||
return self._recipient
|
||||
end
|
||||
|
||||
return PrivateChannel
|
||||
139
Lua/rpbot/deps/discordia/libs/containers/Reaction.lua
Normal file
139
Lua/rpbot/deps/discordia/libs/containers/Reaction.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
--[=[
|
||||
@c Reaction x Container
|
||||
@d Represents an emoji that has been used to react to a Discord text message. Both
|
||||
standard and custom emojis can be used.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local Container = require('containers/abstract/Container')
|
||||
local SecondaryCache = require('iterables/SecondaryCache')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local null = json.null
|
||||
local format = string.format
|
||||
|
||||
local Reaction, get = require('class')('Reaction', Container)
|
||||
|
||||
function Reaction:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
self._emoji_id = data.emoji.id ~= null and data.emoji.id or nil
|
||||
self._emoji_name = data.emoji.name
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __hash
|
||||
@r string
|
||||
@d Returns `Reaction.emojiId or Reaction.emojiName`
|
||||
]=]
|
||||
function Reaction:__hash()
|
||||
return self._emoji_id or self._emoji_name
|
||||
end
|
||||
|
||||
local function getUsers(self, query)
|
||||
local emoji = Resolver.emoji(self)
|
||||
local message = self._parent
|
||||
local channel = message._parent
|
||||
local data, err = self.client._api:getReactions(channel._id, message._id, emoji, query)
|
||||
if data then
|
||||
return SecondaryCache(data, self.client._users)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getUsers
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of all users that have used this reaction in
|
||||
its parent message. The cache is not automatically updated via gateway events,
|
||||
but the internally referenced user objects may be updated. You must call this
|
||||
method again to guarantee that the objects are update to date.
|
||||
]=]
|
||||
function Reaction:getUsers(limit)
|
||||
return getUsers(self, limit and {limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getUsersBefore
|
||||
@p id User-ID-Resolvable
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of all users that have used this reaction before the specified id in
|
||||
its parent message. The cache is not automatically updated via gateway events,
|
||||
but the internally referenced user objects may be updated. You must call this
|
||||
method again to guarantee that the objects are update to date.
|
||||
]=]
|
||||
function Reaction:getUsersBefore(id, limit)
|
||||
id = Resolver.userId(id)
|
||||
return getUsers(self, {before = id, limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getUsersAfter
|
||||
@p id User-ID-Resolvable
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of all users that have used this reaction
|
||||
after the specified id in its parent message. The cache is not automatically
|
||||
updated via gateway events, but the internally referenced user objects may be
|
||||
updated. You must call this method again to guarantee that the objects are update to date.
|
||||
]=]
|
||||
function Reaction:getUsersAfter(id, limit)
|
||||
id = Resolver.userId(id)
|
||||
return getUsers(self, {after = id, limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@op id User-ID-Resolvable
|
||||
@r boolean
|
||||
@d Equivalent to `Reaction.message:removeReaction(Reaction)`
|
||||
]=]
|
||||
function Reaction:delete(id)
|
||||
return self._parent:removeReaction(self, id)
|
||||
end
|
||||
|
||||
--[=[@p emojiId string/nil The ID of the emoji used in this reaction if it is a custom emoji.]=]
|
||||
function get.emojiId(self)
|
||||
return self._emoji_id
|
||||
end
|
||||
|
||||
--[=[@p emojiName string The name of the emoji used in this reaction if it is a custom emoji. Otherwise,
|
||||
this will be the raw string for a standard emoji.]=]
|
||||
function get.emojiName(self)
|
||||
return self._emoji_name
|
||||
end
|
||||
|
||||
--[=[@p emojiHash The discord hash for the emoji, or Unicode string if it is not custom.]=]
|
||||
function get.emojiHash(self)
|
||||
if self._emoji_id then
|
||||
return self._emoji_name .. ':' .. self._emoji_id
|
||||
else
|
||||
return self._emoji_name
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p emojiURL string/nil string The URL that can be used to view a full version of the emoji used in this
|
||||
reaction if it is a custom emoji.]=]
|
||||
function get.emojiURL(self)
|
||||
local id = self._emoji_id
|
||||
return id and format('https://cdn.discordapp.com/emojis/%s.png', id) or nil
|
||||
end
|
||||
|
||||
--[=[@p me boolean Whether the current user has used this reaction.]=]
|
||||
function get.me(self)
|
||||
return self._me
|
||||
end
|
||||
|
||||
--[=[@p count number The total number of users that have used this reaction.]=]
|
||||
function get.count(self)
|
||||
return self._count
|
||||
end
|
||||
|
||||
--[=[@p message Message The message on which this reaction exists.]=]
|
||||
function get.message(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
return Reaction
|
||||
27
Lua/rpbot/deps/discordia/libs/containers/Relationship.lua
Normal file
27
Lua/rpbot/deps/discordia/libs/containers/Relationship.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
--[=[
|
||||
@c Relationship x UserPresence
|
||||
@d Represents a relationship between the current user and another Discord user.
|
||||
This is generally either a friend or a blocked user. This class should only be
|
||||
relevant to user-accounts; bots cannot normally have relationships.
|
||||
]=]
|
||||
|
||||
local UserPresence = require('containers/abstract/UserPresence')
|
||||
|
||||
local Relationship, get = require('class')('Relationship', UserPresence)
|
||||
|
||||
function Relationship:__init(data, parent)
|
||||
UserPresence.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[@p name string Equivalent to `Relationship.user.username`.]=]
|
||||
function get.name(self)
|
||||
return self._user._username
|
||||
end
|
||||
|
||||
--[=[@p type number The relationship type. See the `relationshipType` enumeration for a
|
||||
human-readable representation.]=]
|
||||
function get.type(self)
|
||||
return self._type
|
||||
end
|
||||
|
||||
return Relationship
|
||||
367
Lua/rpbot/deps/discordia/libs/containers/Role.lua
Normal file
367
Lua/rpbot/deps/discordia/libs/containers/Role.lua
Normal file
@@ -0,0 +1,367 @@
|
||||
--[=[
|
||||
@c Role x Snowflake
|
||||
@d Represents a Discord guild role, which is used to assign priority, permissions,
|
||||
and a color to guild members.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local Color = require('utils/Color')
|
||||
local Permissions = require('utils/Permissions')
|
||||
local Resolver = require('client/Resolver')
|
||||
local FilteredIterable = require('iterables/FilteredIterable')
|
||||
|
||||
local format = string.format
|
||||
local insert, sort = table.insert, table.sort
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
local huge = math.huge
|
||||
|
||||
local Role, get = require('class')('Role', Snowflake)
|
||||
|
||||
function Role:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
self.client._role_map[self._id] = parent
|
||||
end
|
||||
|
||||
function Role:_modify(payload)
|
||||
local data, err = self.client._api:modifyGuildRole(self._parent._id, self._id, payload)
|
||||
if data then
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the role. This cannot be undone!
|
||||
]=]
|
||||
function Role:delete()
|
||||
local data, err = self.client._api:deleteGuildRole(self._parent._id, self._id)
|
||||
if data then
|
||||
local cache = self._parent._roles
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
local function sorter(a, b)
|
||||
if a.position == b.position then
|
||||
return tonumber(a.id) < tonumber(b.id)
|
||||
else
|
||||
return a.position < b.position
|
||||
end
|
||||
end
|
||||
|
||||
local function getSortedRoles(self)
|
||||
local guild = self._parent
|
||||
local id = self._parent._id
|
||||
local ret = {}
|
||||
for role in guild.roles:iter() do
|
||||
if role._id ~= id then
|
||||
insert(ret, {id = role._id, position = role._position})
|
||||
end
|
||||
end
|
||||
sort(ret, sorter)
|
||||
return ret
|
||||
end
|
||||
|
||||
local function setSortedRoles(self, roles)
|
||||
local id = self._parent._id
|
||||
insert(roles, {id = id, position = 0})
|
||||
local data, err = self.client._api:modifyGuildRolePositions(id, roles)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m moveDown
|
||||
@p n number
|
||||
@r boolean
|
||||
@d Moves a role down its list. The parameter `n` indicates how many spaces the
|
||||
role should be moved, clamped to the lowest position, with a default of 1 if
|
||||
it is omitted. This will also normalize the positions of all roles. Note that
|
||||
the default everyone role cannot be moved.
|
||||
]=]
|
||||
function Role:moveDown(n) -- TODO: fix attempt to move roles that cannot be moved
|
||||
|
||||
n = tonumber(n) or 1
|
||||
if n < 0 then
|
||||
return self:moveDown(-n)
|
||||
end
|
||||
|
||||
local roles = getSortedRoles(self)
|
||||
|
||||
local new = huge
|
||||
for i = #roles, 1, -1 do
|
||||
local v = roles[i]
|
||||
if v.id == self._id then
|
||||
new = max(1, i - floor(n))
|
||||
v.position = new
|
||||
elseif i >= new then
|
||||
v.position = i + 1
|
||||
else
|
||||
v.position = i
|
||||
end
|
||||
end
|
||||
|
||||
return setSortedRoles(self, roles)
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m moveUp
|
||||
@p n number
|
||||
@r boolean
|
||||
@d Moves a role up its list. The parameter `n` indicates how many spaces the
|
||||
role should be moved, clamped to the highest position, with a default of 1 if
|
||||
it is omitted. This will also normalize the positions of all roles. Note that
|
||||
the default everyone role cannot be moved.
|
||||
]=]
|
||||
function Role:moveUp(n) -- TODO: fix attempt to move roles that cannot be moved
|
||||
|
||||
n = tonumber(n) or 1
|
||||
if n < 0 then
|
||||
return self:moveUp(-n)
|
||||
end
|
||||
|
||||
local roles = getSortedRoles(self)
|
||||
|
||||
local new = -huge
|
||||
for i = 1, #roles do
|
||||
local v = roles[i]
|
||||
if v.id == self._id then
|
||||
new = min(i + floor(n), #roles)
|
||||
v.position = new
|
||||
elseif i <= new then
|
||||
v.position = i - 1
|
||||
else
|
||||
v.position = i
|
||||
end
|
||||
end
|
||||
|
||||
return setSortedRoles(self, roles)
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the role's name. The name must be between 1 and 100 characters in length.
|
||||
]=]
|
||||
function Role:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setColor
|
||||
@p color Color-Resolvable
|
||||
@r boolean
|
||||
@d Sets the role's display color.
|
||||
]=]
|
||||
function Role:setColor(color)
|
||||
color = color and Resolver.color(color)
|
||||
return self:_modify({color = color or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setPermissions
|
||||
@p permissions Permissions-Resolvable
|
||||
@r boolean
|
||||
@d Sets the permissions that this role explicitly allows.
|
||||
]=]
|
||||
function Role:setPermissions(permissions)
|
||||
permissions = permissions and Resolver.permissions(permissions)
|
||||
return self:_modify({permissions = permissions or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m hoist
|
||||
@r boolean
|
||||
@d Causes members with this role to display above unhoisted roles in the member
|
||||
list.
|
||||
]=]
|
||||
function Role:hoist()
|
||||
return self:_modify({hoist = true})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m unhoist
|
||||
@r boolean
|
||||
@d Causes member with this role to display amongst other unhoisted members.
|
||||
]=]
|
||||
function Role:unhoist()
|
||||
return self:_modify({hoist = false})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m enableMentioning
|
||||
@r boolean
|
||||
@d Allows anyone to mention this role in text messages.
|
||||
]=]
|
||||
function Role:enableMentioning()
|
||||
return self:_modify({mentionable = true})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m disableMentioning
|
||||
@r boolean
|
||||
@d Disallows anyone to mention this role in text messages.
|
||||
]=]
|
||||
function Role:disableMentioning()
|
||||
return self:_modify({mentionable = false})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m enablePermissions
|
||||
@p ... Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Enables individual permissions for this role. This does not necessarily fully
|
||||
allow the permissions.
|
||||
]=]
|
||||
function Role:enablePermissions(...)
|
||||
local permissions = self:getPermissions()
|
||||
permissions:enable(...)
|
||||
return self:setPermissions(permissions)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m disablePermissions
|
||||
@p ... Permissions-Resolvables
|
||||
@r boolean
|
||||
@d Disables individual permissions for this role. This does not necessarily fully
|
||||
disallow the permissions.
|
||||
]=]
|
||||
function Role:disablePermissions(...)
|
||||
local permissions = self:getPermissions()
|
||||
permissions:disable(...)
|
||||
return self:setPermissions(permissions)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m enableAllPermissions
|
||||
@r boolean
|
||||
@d Enables all permissions for this role. This does not necessarily fully
|
||||
allow the permissions.
|
||||
]=]
|
||||
function Role:enableAllPermissions()
|
||||
local permissions = self:getPermissions()
|
||||
permissions:enableAll()
|
||||
return self:setPermissions(permissions)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m disableAllPermissions
|
||||
@r boolean
|
||||
@d Disables all permissions for this role. This does not necessarily fully
|
||||
disallow the permissions.
|
||||
]=]
|
||||
function Role:disableAllPermissions()
|
||||
local permissions = self:getPermissions()
|
||||
permissions:disableAll()
|
||||
return self:setPermissions(permissions)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getColor
|
||||
@r Color
|
||||
@d Returns a color object that represents the role's display color.
|
||||
]=]
|
||||
function Role:getColor()
|
||||
return Color(self._color)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPermissions
|
||||
@r Permissions
|
||||
@d Returns a permissions object that represents the permissions that this role
|
||||
has enabled.
|
||||
]=]
|
||||
function Role:getPermissions()
|
||||
return Permissions(self._permissions)
|
||||
end
|
||||
|
||||
--[=[@p hoisted boolean Whether members with this role should be shown separated from other members
|
||||
in the guild member list.]=]
|
||||
function get.hoisted(self)
|
||||
return self._hoist
|
||||
end
|
||||
|
||||
--[=[@p mentionable boolean Whether this role can be mentioned in a text channel message.]=]
|
||||
function get.mentionable(self)
|
||||
return self._mentionable
|
||||
end
|
||||
|
||||
--[=[@p managed boolean Whether this role is managed by some integration or bot inclusion.]=]
|
||||
function get.managed(self)
|
||||
return self._managed
|
||||
end
|
||||
|
||||
--[=[@p name string The name of the role. This should be between 1 and 100 characters in length.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p position number The position of the role, where 0 is the lowest.]=]
|
||||
function get.position(self)
|
||||
return self._position
|
||||
end
|
||||
|
||||
--[=[@p color number Represents the display color of the role as a decimal value.]=]
|
||||
function get.color(self)
|
||||
return self._color
|
||||
end
|
||||
|
||||
--[=[@p permissions number Represents the total permissions of the role as a decimal value.]=]
|
||||
function get.permissions(self)
|
||||
return self._permissions
|
||||
end
|
||||
|
||||
--[=[@p mentionString string A string that, when included in a message content, may resolve as a role
|
||||
notification in the official Discord client.]=]
|
||||
function get.mentionString(self)
|
||||
return format('<@&%s>', self._id)
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this role exists.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p members FilteredIterable A filtered iterable of guild members that have
|
||||
this role. If you want to check whether a specific member has this role, it would
|
||||
be better to get the member object elsewhere and use `Member:hasRole` rather
|
||||
than check whether the member exists here.]=]
|
||||
function get.members(self)
|
||||
if not self._members then
|
||||
self._members = FilteredIterable(self._parent._members, function(m)
|
||||
return m:hasRole(self)
|
||||
end)
|
||||
end
|
||||
return self._members
|
||||
end
|
||||
|
||||
--[=[@p emojis FilteredIterable A filtered iterable of guild emojis that have
|
||||
this role. If you want to check whether a specific emoji has this role, it would
|
||||
be better to get the emoji object elsewhere and use `Emoji:hasRole` rather
|
||||
than check whether the emoji exists here.]=]
|
||||
function get.emojis(self)
|
||||
if not self._emojis then
|
||||
self._emojis = FilteredIterable(self._parent._emojis, function(e)
|
||||
return e:hasRole(self)
|
||||
end)
|
||||
end
|
||||
return self._emojis
|
||||
end
|
||||
|
||||
return Role
|
||||
181
Lua/rpbot/deps/discordia/libs/containers/User.lua
Normal file
181
Lua/rpbot/deps/discordia/libs/containers/User.lua
Normal file
@@ -0,0 +1,181 @@
|
||||
--[=[
|
||||
@c User x Snowflake
|
||||
@d Represents a single user of Discord, either a human or a bot, outside of any
|
||||
specific guild's context.
|
||||
]=]
|
||||
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local FilteredIterable = require('iterables/FilteredIterable')
|
||||
local constants = require('constants')
|
||||
|
||||
local format = string.format
|
||||
local DEFAULT_AVATARS = constants.DEFAULT_AVATARS
|
||||
|
||||
local User, get = require('class')('User', Snowflake)
|
||||
|
||||
function User:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getAvatarURL
|
||||
@op size number
|
||||
@op ext string
|
||||
@r string
|
||||
@d Returns a URL that can be used to view the user's full avatar. If provided, the
|
||||
size must be a power of 2 while the extension must be a valid image format. If
|
||||
the user does not have a custom avatar, the default URL is returned.
|
||||
]=]
|
||||
function User:getAvatarURL(size, ext)
|
||||
local avatar = self._avatar
|
||||
if avatar then
|
||||
ext = ext or avatar:find('a_') == 1 and 'gif' or 'png'
|
||||
if size then
|
||||
return format('https://cdn.discordapp.com/avatars/%s/%s.%s?size=%s', self._id, avatar, ext, size)
|
||||
else
|
||||
return format('https://cdn.discordapp.com/avatars/%s/%s.%s', self._id, avatar, ext)
|
||||
end
|
||||
else
|
||||
return self:getDefaultAvatarURL(size)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getDefaultAvatarURL
|
||||
@op size number
|
||||
@r string
|
||||
@d Returns a URL that can be used to view the user's default avatar.
|
||||
]=]
|
||||
function User:getDefaultAvatarURL(size)
|
||||
local avatar = self.defaultAvatar
|
||||
if size then
|
||||
return format('https://cdn.discordapp.com/embed/avatars/%s.png?size=%s', avatar, size)
|
||||
else
|
||||
return format('https://cdn.discordapp.com/embed/avatars/%s.png', avatar)
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPrivateChannel
|
||||
@r PrivateChannel
|
||||
@d Returns a private channel that can be used to communicate with the user. If the
|
||||
channel is not cached an HTTP request is made to open one.
|
||||
]=]
|
||||
function User:getPrivateChannel()
|
||||
local id = self._id
|
||||
local client = self.client
|
||||
local channel = client._private_channels:find(function(e) return e._recipient._id == id end)
|
||||
if channel then
|
||||
return channel
|
||||
else
|
||||
local data, err = client._api:createDM({recipient_id = id})
|
||||
if data then
|
||||
return client._private_channels:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m send
|
||||
@p content string/table
|
||||
@r Message
|
||||
@d Equivalent to `User:getPrivateChannel():send(content)`
|
||||
]=]
|
||||
function User:send(content)
|
||||
local channel, err = self:getPrivateChannel()
|
||||
if channel then
|
||||
return channel:send(content)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m sendf
|
||||
@p content string
|
||||
@r Message
|
||||
@d Equivalent to `User:getPrivateChannel():sendf(content)`
|
||||
]=]
|
||||
function User:sendf(content, ...)
|
||||
local channel, err = self:getPrivateChannel()
|
||||
if channel then
|
||||
return channel:sendf(content, ...)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p bot boolean Whether this user is a bot.]=]
|
||||
function get.bot(self)
|
||||
return self._bot or false
|
||||
end
|
||||
|
||||
--[=[@p name string Equivalent to `User.username`.]=]
|
||||
function get.name(self)
|
||||
return self._username
|
||||
end
|
||||
|
||||
--[=[@p username string The name of the user. This should be between 2 and 32 characters in length.]=]
|
||||
function get.username(self)
|
||||
return self._username
|
||||
end
|
||||
|
||||
--[=[@p discriminator number The discriminator of the user. This is a 4-digit string that is used to
|
||||
discriminate the user from other users with the same username.]=]
|
||||
function get.discriminator(self)
|
||||
return self._discriminator
|
||||
end
|
||||
|
||||
--[=[@p tag string The user's username and discriminator concatenated by an `#`.]=]
|
||||
function get.tag(self)
|
||||
return self._username .. '#' .. self._discriminator
|
||||
end
|
||||
|
||||
function get.fullname(self)
|
||||
self.client:_deprecated(self.__name, 'fullname', 'tag')
|
||||
return self._username .. '#' .. self._discriminator
|
||||
end
|
||||
|
||||
--[=[@p avatar string/nil The hash for the user's custom avatar, if one is set.]=]
|
||||
function get.avatar(self)
|
||||
return self._avatar
|
||||
end
|
||||
|
||||
--[=[@p defaultAvatar number The user's default avatar. See the `defaultAvatar` enumeration for a
|
||||
human-readable representation.]=]
|
||||
function get.defaultAvatar(self)
|
||||
return self._discriminator % DEFAULT_AVATARS
|
||||
end
|
||||
|
||||
--[=[@p avatarURL string Equivalent to the result of calling `User:getAvatarURL()`.]=]
|
||||
function get.avatarURL(self)
|
||||
return self:getAvatarURL()
|
||||
end
|
||||
|
||||
--[=[@p defaultAvatarURL string Equivalent to the result of calling `User:getDefaultAvatarURL()`.]=]
|
||||
function get.defaultAvatarURL(self)
|
||||
return self:getDefaultAvatarURL()
|
||||
end
|
||||
|
||||
--[=[@p mentionString string A string that, when included in a message content, may resolve as user
|
||||
notification in the official Discord client.]=]
|
||||
function get.mentionString(self)
|
||||
return format('<@%s>', self._id)
|
||||
end
|
||||
|
||||
--[=[@p mutualGuilds FilteredIterable A iterable cache of all guilds where this user shares a membership with the
|
||||
current user. The guild must be cached on the current client and the user's
|
||||
member object must be cached in that guild in order for it to appear here.]=]
|
||||
function get.mutualGuilds(self)
|
||||
if not self._mutual_guilds then
|
||||
local id = self._id
|
||||
self._mutual_guilds = FilteredIterable(self.client._guilds, function(g)
|
||||
return g._members:get(id)
|
||||
end)
|
||||
end
|
||||
return self._mutual_guilds
|
||||
end
|
||||
|
||||
return User
|
||||
137
Lua/rpbot/deps/discordia/libs/containers/Webhook.lua
Normal file
137
Lua/rpbot/deps/discordia/libs/containers/Webhook.lua
Normal file
@@ -0,0 +1,137 @@
|
||||
--[=[
|
||||
@c Webhook x Snowflake
|
||||
@d Represents a handle used to send webhook messages to a guild text channel in a
|
||||
one-way fashion. This class defines methods and properties for managing the
|
||||
webhook, not for sending messages.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local enums = require('enums')
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local User = require('containers/User')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local defaultAvatar = enums.defaultAvatar
|
||||
|
||||
local Webhook, get = require('class')('Webhook', Snowflake)
|
||||
|
||||
function Webhook:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
self._user = data.user and self.client._users:_insert(data.user) -- DNE if getting by token
|
||||
end
|
||||
|
||||
function Webhook:_modify(payload)
|
||||
local data, err = self.client._api:modifyWebhook(self._id, payload)
|
||||
if data then
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getAvatarURL
|
||||
@op size number
|
||||
@op ext string
|
||||
@r string
|
||||
@d Returns a URL that can be used to view the webhooks's full avatar. If provided,
|
||||
the size must be a power of 2 while the extension must be a valid image format.
|
||||
If the webhook does not have a custom avatar, the default URL is returned.
|
||||
]=]
|
||||
function Webhook:getAvatarURL(size, ext)
|
||||
return User.getAvatarURL(self, size, ext)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getDefaultAvatarURL
|
||||
@op size number
|
||||
@r string
|
||||
@d Returns a URL that can be used to view the webhooks's default avatar.
|
||||
]=]
|
||||
function Webhook:getDefaultAvatarURL(size)
|
||||
return User.getDefaultAvatarURL(self, size)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the webhook's name. This must be between 2 and 32 characters in length.
|
||||
]=]
|
||||
function Webhook:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setAvatar
|
||||
@p avatar Base64-Resolvable
|
||||
@r boolean
|
||||
@d Sets the webhook's avatar. If `nil` is passed, the avatar is removed.
|
||||
]=]
|
||||
function Webhook:setAvatar(avatar)
|
||||
avatar = avatar and Resolver.base64(avatar)
|
||||
return self:_modify({avatar = avatar or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the webhook. This cannot be undone!
|
||||
]=]
|
||||
function Webhook:delete()
|
||||
local data, err = self.client._api:deleteWebhook(self._id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p guildId string The ID of the guild in which this webhook exists.]=]
|
||||
function get.guildId(self)
|
||||
return self._guild_id
|
||||
end
|
||||
|
||||
--[=[@p channelId string The ID of the channel in which this webhook exists.]=]
|
||||
function get.channelId(self)
|
||||
return self._channel_id
|
||||
end
|
||||
|
||||
--[=[@p user User/nil The user that created this webhook.]=]
|
||||
function get.user(self)
|
||||
return self._user
|
||||
end
|
||||
|
||||
--[=[@p token string The token that can be used to access this webhook.]=]
|
||||
function get.token(self)
|
||||
return self._token
|
||||
end
|
||||
|
||||
--[=[@p name string The name of the webhook. This should be between 2 and 32 characters in length.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p avatar string/nil The hash for the webhook's custom avatar, if one is set.]=]
|
||||
function get.avatar(self)
|
||||
return self._avatar
|
||||
end
|
||||
|
||||
--[=[@p avatarURL string Equivalent to the result of calling `Webhook:getAvatarURL()`.]=]
|
||||
function get.avatarURL(self)
|
||||
return self:getAvatarURL()
|
||||
end
|
||||
|
||||
--[=[@p defaultAvatar number The default avatar for the webhook. See the `defaultAvatar` enumeration for
|
||||
a human-readable representation. This should always be `defaultAvatar.blurple`.]=]
|
||||
function get.defaultAvatar()
|
||||
return defaultAvatar.blurple
|
||||
end
|
||||
|
||||
--[=[@p defaultAvatarURL string Equivalent to the result of calling `Webhook:getDefaultAvatarURL()`.]=]
|
||||
function get.defaultAvatarURL(self)
|
||||
return self:getDefaultAvatarURL()
|
||||
end
|
||||
|
||||
return Webhook
|
||||
@@ -0,0 +1,66 @@
|
||||
--[=[
|
||||
@c Channel x Snowflake
|
||||
@d Abstract base class that defines the base methods and/or properties for all
|
||||
Discord channel types.
|
||||
]=]
|
||||
|
||||
local Snowflake = require('containers/abstract/Snowflake')
|
||||
local enums = require('enums')
|
||||
|
||||
local format = string.format
|
||||
local channelType = enums.channelType
|
||||
|
||||
local Channel, get = require('class')('Channel', Snowflake)
|
||||
|
||||
function Channel:__init(data, parent)
|
||||
Snowflake.__init(self, data, parent)
|
||||
end
|
||||
|
||||
function Channel:_modify(payload)
|
||||
local data, err = self.client._api:modifyChannel(self._id, payload)
|
||||
if data then
|
||||
self:_load(data)
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
function Channel:_delete()
|
||||
local data, err = self.client._api:deleteChannel(self._id)
|
||||
if data then
|
||||
local cache
|
||||
local t = self._type
|
||||
if t == channelType.text then
|
||||
cache = self._parent._text_channels
|
||||
elseif t == channelType.private then
|
||||
cache = self._parent._private_channels
|
||||
elseif t == channelType.group then
|
||||
cache = self._parent._group_channels
|
||||
elseif t == channelType.voice then
|
||||
cache = self._parent._voice_channels
|
||||
elseif t == channelType.category then
|
||||
cache = self._parent._categories
|
||||
end
|
||||
if cache then
|
||||
cache:_delete(self._id)
|
||||
end
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p type number The channel type. See the `channelType` enumeration for a
|
||||
human-readable representation.]=]
|
||||
function get.type(self)
|
||||
return self._type
|
||||
end
|
||||
|
||||
--[=[@p mentionString string A string that, when included in a message content,
|
||||
may resolve as a link to a channel in the official Discord client.]=]
|
||||
function get.mentionString(self)
|
||||
return format('<#%s>', self._id)
|
||||
end
|
||||
|
||||
return Channel
|
||||
@@ -0,0 +1,67 @@
|
||||
--[=[
|
||||
@c Container
|
||||
@d Abstract base class that defines the base methods and/or properties for all
|
||||
Discord objects and structures. Container classes are constructed internally
|
||||
with information received from Discord and should never be manually constructed.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
|
||||
local null = json.null
|
||||
local format = string.format
|
||||
|
||||
local Container, get = require('class')('Container')
|
||||
|
||||
local types = {['string'] = true, ['number'] = true, ['boolean'] = true}
|
||||
|
||||
local function load(self, data)
|
||||
-- assert(type(data) == 'table') -- debug
|
||||
for k, v in pairs(data) do
|
||||
if types[type(v)] then
|
||||
self['_' .. k] = v
|
||||
elseif v == null then
|
||||
self['_' .. k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Container:__init(data, parent)
|
||||
-- assert(type(parent) == 'table') -- debug
|
||||
self._parent = parent
|
||||
return load(self, data)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __eq
|
||||
@r boolean
|
||||
@d Defines the behavior of the `==` operator. Allows containers to be directly
|
||||
compared according to their type and `__hash` return values.
|
||||
]=]
|
||||
function Container:__eq(other)
|
||||
return self.__class == other.__class and self:__hash() == other:__hash()
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __tostring
|
||||
@r string
|
||||
@d Defines the behavior of the `tostring` function. All containers follow the format
|
||||
`ClassName: hash`.
|
||||
]=]
|
||||
function Container:__tostring()
|
||||
return format('%s: %s', self.__name, self:__hash())
|
||||
end
|
||||
|
||||
Container._load = load
|
||||
|
||||
--[=[@p client Client A shortcut to the client object to which this container is visible.]=]
|
||||
function get.client(self)
|
||||
return self._parent.client or self._parent
|
||||
end
|
||||
|
||||
--[=[@p parent Container/Client The parent object of to which this container is
|
||||
a child. For example, the parent of a role is the guild in which the role exists.]=]
|
||||
function get.parent(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
return Container
|
||||
@@ -0,0 +1,265 @@
|
||||
--[=[
|
||||
@c GuildChannel x Channel
|
||||
@d Abstract base class that defines the base methods and/or properties for all
|
||||
Discord guild channels.
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local enums = require('enums')
|
||||
local class = require('class')
|
||||
local Channel = require('containers/abstract/Channel')
|
||||
local PermissionOverwrite = require('containers/PermissionOverwrite')
|
||||
local Invite = require('containers/Invite')
|
||||
local Cache = require('iterables/Cache')
|
||||
local Resolver = require('client/Resolver')
|
||||
|
||||
local isInstance = class.isInstance
|
||||
local classes = class.classes
|
||||
local channelType = enums.channelType
|
||||
|
||||
local insert, sort = table.insert, table.sort
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
local huge = math.huge
|
||||
|
||||
local GuildChannel, get = class('GuildChannel', Channel)
|
||||
|
||||
function GuildChannel:__init(data, parent)
|
||||
Channel.__init(self, data, parent)
|
||||
self.client._channel_map[self._id] = parent
|
||||
self._permission_overwrites = Cache({}, PermissionOverwrite, self)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function GuildChannel:_load(data)
|
||||
Channel._load(self, data)
|
||||
return self:_loadMore(data)
|
||||
end
|
||||
|
||||
function GuildChannel:_loadMore(data)
|
||||
return self._permission_overwrites:_load(data.permission_overwrites, true)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setName
|
||||
@p name string
|
||||
@r boolean
|
||||
@d Sets the channel's name. This must be between 2 and 100 characters in length.
|
||||
]=]
|
||||
function GuildChannel:setName(name)
|
||||
return self:_modify({name = name or json.null})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m setCategory
|
||||
@p id Channel-ID-Resolvable
|
||||
@r boolean
|
||||
@d Sets the channel's parent category.
|
||||
]=]
|
||||
function GuildChannel:setCategory(id)
|
||||
id = Resolver.channelId(id)
|
||||
return self:_modify({parent_id = id or json.null})
|
||||
end
|
||||
|
||||
local function sorter(a, b)
|
||||
if a.position == b.position then
|
||||
return tonumber(a.id) < tonumber(b.id)
|
||||
else
|
||||
return a.position < b.position
|
||||
end
|
||||
end
|
||||
|
||||
local function getSortedChannels(self)
|
||||
|
||||
local channels
|
||||
local t = self._type
|
||||
if t == channelType.text then
|
||||
channels = self._parent._text_channels
|
||||
elseif t == channelType.voice then
|
||||
channels = self._parent._voice_channels
|
||||
elseif t == channelType.category then
|
||||
channels = self._parent._categories
|
||||
end
|
||||
|
||||
local ret = {}
|
||||
for channel in channels:iter() do
|
||||
insert(ret, {id = channel._id, position = channel._position})
|
||||
end
|
||||
sort(ret, sorter)
|
||||
|
||||
return ret
|
||||
|
||||
end
|
||||
|
||||
local function setSortedChannels(self, channels)
|
||||
local data, err = self.client._api:modifyGuildChannelPositions(self._parent._id, channels)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m moveUp
|
||||
@p n number
|
||||
@r boolean
|
||||
@d Moves a channel up its list. The parameter `n` indicates how many spaces the
|
||||
channel should be moved, clamped to the highest position, with a default of 1 if
|
||||
it is omitted. This will also normalize the positions of all channels.
|
||||
]=]
|
||||
function GuildChannel:moveUp(n)
|
||||
|
||||
n = tonumber(n) or 1
|
||||
if n < 0 then
|
||||
return self:moveDown(-n)
|
||||
end
|
||||
|
||||
local channels = getSortedChannels(self)
|
||||
|
||||
local new = huge
|
||||
for i = #channels - 1, 0, -1 do
|
||||
local v = channels[i + 1]
|
||||
if v.id == self._id then
|
||||
new = max(0, i - floor(n))
|
||||
v.position = new
|
||||
elseif i >= new then
|
||||
v.position = i + 1
|
||||
else
|
||||
v.position = i
|
||||
end
|
||||
end
|
||||
|
||||
return setSortedChannels(self, channels)
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m moveDown
|
||||
@p n number
|
||||
@r boolean
|
||||
@d Moves a channel down its list. The parameter `n` indicates how many spaces the
|
||||
channel should be moved, clamped to the lowest position, with a default of 1 if
|
||||
it is omitted. This will also normalize the positions of all channels.
|
||||
]=]
|
||||
function GuildChannel:moveDown(n)
|
||||
|
||||
n = tonumber(n) or 1
|
||||
if n < 0 then
|
||||
return self:moveUp(-n)
|
||||
end
|
||||
|
||||
local channels = getSortedChannels(self)
|
||||
|
||||
local new = -huge
|
||||
for i = 0, #channels - 1 do
|
||||
local v = channels[i + 1]
|
||||
if v.id == self._id then
|
||||
new = min(i + floor(n), #channels - 1)
|
||||
v.position = new
|
||||
elseif i <= new then
|
||||
v.position = i - 1
|
||||
else
|
||||
v.position = i
|
||||
end
|
||||
end
|
||||
|
||||
return setSortedChannels(self, channels)
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m createInvite
|
||||
@p payload table
|
||||
@r Invite
|
||||
@d Creates an invite to the channel. Optional payload fields are:
|
||||
- max_age:number time in seconds until expiration, default = 86400 (24 hours)
|
||||
- max_uses:number total number of uses allowed, default = 0 (unlimited)
|
||||
- temporary:boolean whether the invite grants temporary membership, default = false
|
||||
- unique:boolean whether a unique code should be guaranteed, default = false
|
||||
]=]
|
||||
function GuildChannel:createInvite(payload)
|
||||
local data, err = self.client._api:createChannelInvite(self._id, payload)
|
||||
if data then
|
||||
return Invite(data, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getInvites
|
||||
@r Cache
|
||||
@d Returns a newly constructed cache of all invite objects for the channel. The
|
||||
cache and its objects are not automatically updated via gateway events. You must
|
||||
call this method again to get the updated objects.
|
||||
]=]
|
||||
function GuildChannel:getInvites()
|
||||
local data, err = self.client._api:getChannelInvites(self._id)
|
||||
if data then
|
||||
return Cache(data, Invite, self.client)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPermissionOverwriteFor
|
||||
@p obj Role/Member
|
||||
@r PermissionOverwrite
|
||||
@d Returns a permission overwrite object corresponding to the provided member or
|
||||
role object. If a cached overwrite is not found, an empty overwrite with
|
||||
zero-permissions is returned instead. Therefore, this can be used to create a
|
||||
new overwrite when one does not exist. Note that the member or role must exist
|
||||
in the same guild as the channel does.
|
||||
]=]
|
||||
function GuildChannel:getPermissionOverwriteFor(obj)
|
||||
local id, type
|
||||
if isInstance(obj, classes.Role) and self._parent == obj._parent then
|
||||
id, type = obj._id, 'role'
|
||||
elseif isInstance(obj, classes.Member) and self._parent == obj._parent then
|
||||
id, type = obj._user._id, 'member'
|
||||
else
|
||||
return nil, 'Invalid Role or Member: ' .. tostring(obj)
|
||||
end
|
||||
local overwrites = self._permission_overwrites
|
||||
return overwrites:get(id) or overwrites:_insert(setmetatable({
|
||||
id = id, type = type, allow = 0, deny = 0
|
||||
}, {__jsontype = 'object'}))
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m delete
|
||||
@r boolean
|
||||
@d Permanently deletes the channel. This cannot be undone!
|
||||
]=]
|
||||
function GuildChannel:delete()
|
||||
return self:_delete()
|
||||
end
|
||||
|
||||
--[=[@p permissionOverwrites Cache An iterable cache of all overwrites that exist in this channel. To access an
|
||||
overwrite that may exist, but is not cached, use `GuildChannel:getPermissionOverwriteFor`.]=]
|
||||
function get.permissionOverwrites(self)
|
||||
return self._permission_overwrites
|
||||
end
|
||||
|
||||
--[=[@p name string The name of the channel. This should be between 2 and 100 characters in length.]=]
|
||||
function get.name(self)
|
||||
return self._name
|
||||
end
|
||||
|
||||
--[=[@p position number The position of the channel, where 0 is the highest.]=]
|
||||
function get.position(self)
|
||||
return self._position
|
||||
end
|
||||
|
||||
--[=[@p guild Guild The guild in which this channel exists.]=]
|
||||
function get.guild(self)
|
||||
return self._parent
|
||||
end
|
||||
|
||||
--[=[@p category GuildCategoryChannel/nil The parent channel category that may contain this channel.]=]
|
||||
function get.category(self)
|
||||
return self._parent._categories:get(self._parent_id)
|
||||
end
|
||||
|
||||
return GuildChannel
|
||||
@@ -0,0 +1,47 @@
|
||||
--[=[
|
||||
@c Snowflake x Container
|
||||
@d Abstract base class that defines the base methods and/or properties for all
|
||||
Discord objects that have a Snowflake ID.
|
||||
]=]
|
||||
|
||||
local Date = require('utils/Date')
|
||||
local Container = require('containers/abstract/Container')
|
||||
|
||||
local Snowflake, get = require('class')('Snowflake', Container)
|
||||
|
||||
function Snowflake:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __hash
|
||||
@r string
|
||||
@d Returns `Snowflake.id`
|
||||
]=]
|
||||
function Snowflake:__hash()
|
||||
return self._id
|
||||
end
|
||||
|
||||
--[=[@p id string The Snowflake ID that can be used to identify the object. This is guaranteed to
|
||||
be unique except in cases where an object shares the ID of its parent.]=]
|
||||
function get.id(self)
|
||||
return self._id
|
||||
end
|
||||
|
||||
--[=[@p createdAt number The Unix time in seconds at which this object was created by Discord. Additional
|
||||
decimal points may be present, though only the first 3 (milliseconds) should be
|
||||
considered accurate.]=]
|
||||
function get.createdAt(self)
|
||||
return Date.parseSnowflake(self._id)
|
||||
end
|
||||
|
||||
--[=[@p timestamp string The date and time at which this object was created by Discord, represented as
|
||||
an ISO 8601 string plus microseconds when available.
|
||||
|
||||
Equivalent to `Date.fromSnowflake(Snowflake.id):toISO()`.
|
||||
]=]
|
||||
function get.timestamp(self)
|
||||
return Date.fromSnowflake(self._id):toISO()
|
||||
end
|
||||
|
||||
return Snowflake
|
||||
@@ -0,0 +1,315 @@
|
||||
--[=[
|
||||
@c TextChannel x Channel
|
||||
@d Abstract base class that defines the base methods and/or properties for all
|
||||
Discord text channels.
|
||||
]=]
|
||||
|
||||
local pathjoin = require('pathjoin')
|
||||
local Channel = require('containers/abstract/Channel')
|
||||
local Message = require('containers/Message')
|
||||
local WeakCache = require('iterables/WeakCache')
|
||||
local SecondaryCache = require('iterables/SecondaryCache')
|
||||
local Resolver = require('client/Resolver')
|
||||
local fs = require('fs')
|
||||
|
||||
local splitPath = pathjoin.splitPath
|
||||
local insert, remove, concat = table.insert, table.remove, table.concat
|
||||
local format = string.format
|
||||
local readFileSync = fs.readFileSync
|
||||
|
||||
local TextChannel, get = require('class')('TextChannel', Channel)
|
||||
|
||||
function TextChannel:__init(data, parent)
|
||||
Channel.__init(self, data, parent)
|
||||
self._messages = WeakCache({}, Message, self)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMessage
|
||||
@p id Message-ID-Resolvable
|
||||
@r Message
|
||||
@d Gets a message object by ID. If the object is already cached, then the cached
|
||||
object will be returned; otherwise, an HTTP request is made.
|
||||
]=]
|
||||
function TextChannel:getMessage(id)
|
||||
id = Resolver.messageId(id)
|
||||
local message = self._messages:get(id)
|
||||
if message then
|
||||
return message
|
||||
else
|
||||
local data, err = self.client._api:getChannelMessage(self._id, id)
|
||||
if data then
|
||||
return self._messages:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getFirstMessage
|
||||
@r Message
|
||||
@d Returns the first message found in the channel, if any exist. This is not a
|
||||
cache shortcut; an HTTP request is made each time this method is called.
|
||||
]=]
|
||||
function TextChannel:getFirstMessage()
|
||||
local data, err = self.client._api:getChannelMessages(self._id, {after = self._id, limit = 1})
|
||||
if data then
|
||||
if data[1] then
|
||||
return self._messages:_insert(data[1])
|
||||
else
|
||||
return nil, 'Channel has no messages'
|
||||
end
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getLastMessage
|
||||
@r Message
|
||||
@d Returns the last message found in the channel, if any exist. This is not a
|
||||
cache shortcut; an HTTP request is made each time this method is called.
|
||||
]=]
|
||||
function TextChannel:getLastMessage()
|
||||
local data, err = self.client._api:getChannelMessages(self._id, {limit = 1})
|
||||
if data then
|
||||
if data[1] then
|
||||
return self._messages:_insert(data[1])
|
||||
else
|
||||
return nil, 'Channel has no messages'
|
||||
end
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
local function getMessages(self, query)
|
||||
local data, err = self.client._api:getChannelMessages(self._id, query)
|
||||
if data then
|
||||
return SecondaryCache(data, self._messages)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMessages
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of between 1 and 100 (default = 50) message
|
||||
objects found in the channel. While the cache will never automatically gain or
|
||||
lose objects, the objects that it contains may be updated by gateway events.
|
||||
]=]
|
||||
function TextChannel:getMessages(limit)
|
||||
return getMessages(self, limit and {limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMessagesAfter
|
||||
@p id Message-ID-Resolvable
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of between 1 and 100 (default = 50) message
|
||||
objects found in the channel after a specific point. While the cache will never
|
||||
automatically gain or lose objects, the objects that it contains may be updated
|
||||
by gateway events.
|
||||
]=]
|
||||
function TextChannel:getMessagesAfter(id, limit)
|
||||
id = Resolver.messageId(id)
|
||||
return getMessages(self, {after = id, limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMessagesBefore
|
||||
@p id Message-ID-Resolvable
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of between 1 and 100 (default = 50) message
|
||||
objects found in the channel before a specific point. While the cache will never
|
||||
automatically gain or lose objects, the objects that it contains may be updated
|
||||
by gateway events.
|
||||
]=]
|
||||
function TextChannel:getMessagesBefore(id, limit)
|
||||
id = Resolver.messageId(id)
|
||||
return getMessages(self, {before = id, limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getMessagesAround
|
||||
@p id Message-ID-Resolvable
|
||||
@op limit number
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of between 1 and 100 (default = 50) message
|
||||
objects found in the channel around a specific point. While the cache will never
|
||||
automatically gain or lose objects, the objects that it contains may be updated
|
||||
by gateway events.
|
||||
]=]
|
||||
function TextChannel:getMessagesAround(id, limit)
|
||||
id = Resolver.messageId(id)
|
||||
return getMessages(self, {around = id, limit = limit})
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m getPinnedMessages
|
||||
@r SecondaryCache
|
||||
@d Returns a newly constructed cache of up to 50 messages that are pinned in the
|
||||
channel. While the cache will never automatically gain or lose objects, the
|
||||
objects that it contains may be updated by gateway events.
|
||||
]=]
|
||||
function TextChannel:getPinnedMessages()
|
||||
local data, err = self.client._api:getPinnedMessages(self._id)
|
||||
if data then
|
||||
return SecondaryCache(data, self._messages)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m broadcastTyping
|
||||
@r boolean
|
||||
@d Indicates in the channel that the client's user "is typing".
|
||||
]=]
|
||||
function TextChannel:broadcastTyping()
|
||||
local data, err = self.client._api:triggerTypingIndicator(self._id)
|
||||
if data then
|
||||
return true
|
||||
else
|
||||
return false, err
|
||||
end
|
||||
end
|
||||
|
||||
local function parseFile(obj, files)
|
||||
if type(obj) == 'string' then
|
||||
local data, err = readFileSync(obj)
|
||||
if not data then
|
||||
return nil, err
|
||||
end
|
||||
files = files or {}
|
||||
insert(files, {remove(splitPath(obj)), data})
|
||||
elseif type(obj) == 'table' and type(obj[1]) == 'string' and type(obj[2]) == 'string' then
|
||||
files = files or {}
|
||||
insert(files, obj)
|
||||
else
|
||||
return nil, 'Invalid file object: ' .. tostring(obj)
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
local function parseMention(obj, mentions)
|
||||
if type(obj) == 'table' and obj.mentionString then
|
||||
mentions = mentions or {}
|
||||
insert(mentions, obj.mentionString)
|
||||
else
|
||||
return nil, 'Unmentionable object: ' .. tostring(obj)
|
||||
end
|
||||
return mentions
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m send
|
||||
@p content string/table
|
||||
@r Message
|
||||
@d Sends a message to the channel. If `content` is a string, then this is simply
|
||||
sent as the message content. If it is a table, more advanced formatting is
|
||||
allowed. See [[managing messages]] for more information.
|
||||
]=]
|
||||
function TextChannel:send(content)
|
||||
|
||||
local data, err
|
||||
|
||||
if type(content) == 'table' then
|
||||
|
||||
local tbl = content
|
||||
content = tbl.content
|
||||
|
||||
if type(tbl.code) == 'string' then
|
||||
content = format('```%s\n%s\n```', tbl.code, content)
|
||||
elseif tbl.code == true then
|
||||
content = format('```\n%s\n```', content)
|
||||
end
|
||||
|
||||
local mentions
|
||||
if tbl.mention then
|
||||
mentions, err = parseMention(tbl.mention)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
if type(tbl.mentions) == 'table' then
|
||||
for _, mention in ipairs(tbl.mentions) do
|
||||
mentions, err = parseMention(mention, mentions)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if mentions then
|
||||
insert(mentions, content)
|
||||
content = concat(mentions, ' ')
|
||||
end
|
||||
|
||||
local files
|
||||
if tbl.file then
|
||||
files, err = parseFile(tbl.file)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
if type(tbl.files) == 'table' then
|
||||
for _, file in ipairs(tbl.files) do
|
||||
files, err = parseFile(file, files)
|
||||
if err then
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
data, err = self.client._api:createMessage(self._id, {
|
||||
content = content,
|
||||
tts = tbl.tts,
|
||||
nonce = tbl.nonce,
|
||||
embed = tbl.embed,
|
||||
}, files)
|
||||
|
||||
else
|
||||
|
||||
data, err = self.client._api:createMessage(self._id, {content = content})
|
||||
|
||||
end
|
||||
|
||||
if data then
|
||||
return self._messages:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m sendf
|
||||
@p content string
|
||||
@p ... *
|
||||
@r Message
|
||||
@d Sends a message to the channel with content formatted with `...` via `string.format`
|
||||
]=]
|
||||
function TextChannel:sendf(content, ...)
|
||||
local data, err = self.client._api:createMessage(self._id, {content = format(content, ...)})
|
||||
if data then
|
||||
return self._messages:_insert(data)
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p messages WeakCache An iterable weak cache of all messages that are
|
||||
visible to the client. Messages that are not referenced elsewhere are eventually
|
||||
garbage collected. To access a message that may exist but is not cached,
|
||||
use `TextChannel:getMessage`.]=]
|
||||
function get.messages(self)
|
||||
return self._messages
|
||||
end
|
||||
|
||||
return TextChannel
|
||||
@@ -0,0 +1,97 @@
|
||||
--[=[
|
||||
@c UserPresence x Container
|
||||
@d Abstract base class that defines the base methods and/or properties for
|
||||
classes that represent a user's current presence information. Note that any
|
||||
method or property that exists for the User class is also available in the
|
||||
UserPresence class and its subclasses.
|
||||
]=]
|
||||
|
||||
local null = require('json').null
|
||||
local User = require('containers/User')
|
||||
local Activity = require('containers/Activity')
|
||||
local Container = require('containers/abstract/Container')
|
||||
|
||||
local UserPresence, get = require('class')('UserPresence', Container)
|
||||
|
||||
function UserPresence:__init(data, parent)
|
||||
Container.__init(self, data, parent)
|
||||
self._user = self.client._users:_insert(data.user)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m __hash
|
||||
@r string
|
||||
@d Returns `UserPresence.user.id`
|
||||
]=]
|
||||
function UserPresence:__hash()
|
||||
return self._user._id
|
||||
end
|
||||
|
||||
local activities = setmetatable({}, {__mode = 'v'})
|
||||
|
||||
function UserPresence:_loadPresence(presence)
|
||||
self._status = presence.status
|
||||
local game = presence.game
|
||||
if game == null then
|
||||
self._activity = nil
|
||||
elseif game then
|
||||
if self._activity then
|
||||
self._activity:_load(game)
|
||||
else
|
||||
local activity = activities[self:__hash()]
|
||||
if activity then
|
||||
activity:_load(game)
|
||||
else
|
||||
activity = Activity(game, self)
|
||||
activities[self:__hash()] = activity
|
||||
end
|
||||
self._activity = activity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function get.gameName(self)
|
||||
self.client:_deprecated(self.__name, 'gameName', 'activity.name')
|
||||
return self._activity and self._activity._name
|
||||
end
|
||||
|
||||
function get.gameType(self)
|
||||
self.client:_deprecated(self.__name, 'gameType', 'activity.type')
|
||||
return self._activity and self._activity._type
|
||||
end
|
||||
|
||||
function get.gameURL(self)
|
||||
self.client:_deprecated(self.__name, 'gameURL', 'activity.url')
|
||||
return self._activity and self._activity._url
|
||||
end
|
||||
|
||||
--[=[@p status string The user's online status (online, dnd, idle, offline).]=]
|
||||
function get.status(self)
|
||||
return self._status or 'offline'
|
||||
end
|
||||
|
||||
--[=[@p user User The user that this presence represents.]=]
|
||||
function get.user(self)
|
||||
return self._user
|
||||
end
|
||||
|
||||
--[=[@p activity Activity The Activity that this presence represents.]=]
|
||||
function get.activity(self)
|
||||
return self._activity
|
||||
end
|
||||
|
||||
-- user shortcuts
|
||||
|
||||
for k, v in pairs(User) do
|
||||
UserPresence[k] = UserPresence[k] or function(self, ...)
|
||||
return v(self._user, ...)
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(User.__getters) do
|
||||
get[k] = get[k] or function(self)
|
||||
return v(self._user)
|
||||
end
|
||||
end
|
||||
|
||||
return UserPresence
|
||||
54
Lua/rpbot/deps/discordia/libs/endpoints.lua
Normal file
54
Lua/rpbot/deps/discordia/libs/endpoints.lua
Normal file
@@ -0,0 +1,54 @@
|
||||
return {
|
||||
CHANNEL = "/channels/%s",
|
||||
CHANNEL_INVITES = "/channels/%s/invites",
|
||||
CHANNEL_MESSAGE = "/channels/%s/messages/%s",
|
||||
CHANNEL_MESSAGES = "/channels/%s/messages",
|
||||
CHANNEL_MESSAGES_BULK_DELETE = "/channels/%s/messages/bulk-delete",
|
||||
CHANNEL_MESSAGE_REACTION = "/channels/%s/messages/%s/reactions/%s",
|
||||
CHANNEL_MESSAGE_REACTIONS = "/channels/%s/messages/%s/reactions",
|
||||
CHANNEL_MESSAGE_REACTION_ME = "/channels/%s/messages/%s/reactions/%s/@me",
|
||||
CHANNEL_MESSAGE_REACTION_USER = "/channels/%s/messages/%s/reactions/%s/%s",
|
||||
CHANNEL_PERMISSION = "/channels/%s/permissions/%s",
|
||||
CHANNEL_PIN = "/channels/%s/pins/%s",
|
||||
CHANNEL_PINS = "/channels/%s/pins",
|
||||
CHANNEL_RECIPIENT = "/channels/%s/recipients/%s",
|
||||
CHANNEL_TYPING = "/channels/%s/typing",
|
||||
CHANNEL_WEBHOOKS = "/channels/%s/webhooks",
|
||||
GATEWAY = "/gateway",
|
||||
GATEWAY_BOT = "/gateway/bot",
|
||||
GUILD = "/guilds/%s",
|
||||
GUILDS = "/guilds",
|
||||
GUILD_AUDIT_LOGS = "/guilds/%s/audit-logs",
|
||||
GUILD_BAN = "/guilds/%s/bans/%s",
|
||||
GUILD_BANS = "/guilds/%s/bans",
|
||||
GUILD_CHANNELS = "/guilds/%s/channels",
|
||||
GUILD_EMBED = "/guilds/%s/embed",
|
||||
GUILD_EMOJI = "/guilds/%s/emojis/%s",
|
||||
GUILD_EMOJIS = "/guilds/%s/emojis",
|
||||
GUILD_INTEGRATION = "/guilds/%s/integrations/%s",
|
||||
GUILD_INTEGRATIONS = "/guilds/%s/integrations",
|
||||
GUILD_INTEGRATION_SYNC = "/guilds/%s/integrations/%s/sync",
|
||||
GUILD_INVITES = "/guilds/%s/invites",
|
||||
GUILD_MEMBER = "/guilds/%s/members/%s",
|
||||
GUILD_MEMBERS = "/guilds/%s/members",
|
||||
GUILD_MEMBER_ME_NICK = "/guilds/%s/members/@me/nick",
|
||||
GUILD_MEMBER_ROLE = "/guilds/%s/members/%s/roles/%s",
|
||||
GUILD_PRUNE = "/guilds/%s/prune",
|
||||
GUILD_REGIONS = "/guilds/%s/regions",
|
||||
GUILD_ROLE = "/guilds/%s/roles/%s",
|
||||
GUILD_ROLES = "/guilds/%s/roles",
|
||||
GUILD_WEBHOOKS = "/guilds/%s/webhooks",
|
||||
INVITE = "/invites/%s",
|
||||
OAUTH2_APPLICATION_ME = "/oauth2/applications/@me",
|
||||
USER = "/users/%s",
|
||||
USER_ME = "/users/@me",
|
||||
USER_ME_CHANNELS = "/users/@me/channels",
|
||||
USER_ME_CONNECTIONS = "/users/@me/connections",
|
||||
USER_ME_GUILD = "/users/@me/guilds/%s",
|
||||
USER_ME_GUILDS = "/users/@me/guilds",
|
||||
VOICE_REGIONS = "/voice/regions",
|
||||
WEBHOOK = "/webhooks/%s",
|
||||
WEBHOOK_TOKEN = "/webhooks/%s/%s",
|
||||
WEBHOOK_TOKEN_GITHUB = "/webhooks/%s/%s/github",
|
||||
WEBHOOK_TOKEN_SLACK = "/webhooks/%s/%s/slack",
|
||||
}
|
||||
177
Lua/rpbot/deps/discordia/libs/enums.lua
Normal file
177
Lua/rpbot/deps/discordia/libs/enums.lua
Normal file
@@ -0,0 +1,177 @@
|
||||
local function enum(tbl)
|
||||
local call = {}
|
||||
for k, v in pairs(tbl) do
|
||||
if call[v] then
|
||||
return error(string.format('enum clash for %q and %q', k, call[v]))
|
||||
end
|
||||
call[v] = k
|
||||
end
|
||||
return setmetatable({}, {
|
||||
__call = function(_, k)
|
||||
if call[k] then
|
||||
return call[k]
|
||||
else
|
||||
return error('invalid enumeration: ' .. tostring(k))
|
||||
end
|
||||
end,
|
||||
__index = function(_, k)
|
||||
if tbl[k] then
|
||||
return tbl[k]
|
||||
else
|
||||
return error('invalid enumeration: ' .. tostring(k))
|
||||
end
|
||||
end,
|
||||
__pairs = function()
|
||||
return next, tbl
|
||||
end,
|
||||
__newindex = function()
|
||||
return error('cannot overwrite enumeration')
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
local enums = {enum = enum}
|
||||
|
||||
enums.defaultAvatar = enum {
|
||||
blurple = 0,
|
||||
gray = 1,
|
||||
green = 2,
|
||||
orange = 3,
|
||||
red = 4,
|
||||
}
|
||||
|
||||
enums.notificationSetting = enum {
|
||||
allMessages = 0,
|
||||
onlyMentions = 1,
|
||||
}
|
||||
|
||||
enums.channelType = enum {
|
||||
text = 0,
|
||||
private = 1,
|
||||
voice = 2,
|
||||
group = 3,
|
||||
category = 4,
|
||||
}
|
||||
|
||||
enums.messageType = enum {
|
||||
default = 0,
|
||||
recipientAdd = 1,
|
||||
recipientRemove = 2,
|
||||
call = 3,
|
||||
channelNameChange = 4,
|
||||
channelIconchange = 5,
|
||||
pinnedMessage = 6,
|
||||
memberJoin = 7,
|
||||
}
|
||||
|
||||
enums.relationshipType = enum {
|
||||
none = 0,
|
||||
friend = 1,
|
||||
blocked = 2,
|
||||
pendingIncoming = 3,
|
||||
pendingOutgoing = 4,
|
||||
}
|
||||
|
||||
enums.activityType = enum {
|
||||
default = 0,
|
||||
streaming = 1,
|
||||
listening = 2,
|
||||
}
|
||||
|
||||
enums.status = enum {
|
||||
online = 'online',
|
||||
idle = 'idle',
|
||||
doNotDisturb = 'dnd',
|
||||
invisible = 'invisible',
|
||||
}
|
||||
|
||||
enums.gameType = enum { -- NOTE: deprecated; use activityType
|
||||
default = 0,
|
||||
streaming = 1,
|
||||
listening = 2,
|
||||
}
|
||||
|
||||
enums.verificationLevel = enum {
|
||||
none = 0,
|
||||
low = 1,
|
||||
medium = 2,
|
||||
high = 3, -- (╯°□°)╯︵ ┻━┻
|
||||
veryHigh = 4, -- ┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻
|
||||
}
|
||||
|
||||
enums.explicitContentLevel = enum {
|
||||
none = 0,
|
||||
medium = 1,
|
||||
high = 2,
|
||||
}
|
||||
|
||||
enums.permission = enum {
|
||||
createInstantInvite = 0x00000001,
|
||||
kickMembers = 0x00000002,
|
||||
banMembers = 0x00000004,
|
||||
administrator = 0x00000008,
|
||||
manageChannels = 0x00000010,
|
||||
manageGuild = 0x00000020,
|
||||
addReactions = 0x00000040,
|
||||
viewAuditLog = 0x00000080,
|
||||
prioritySpeaker = 0x00000100,
|
||||
readMessages = 0x00000400,
|
||||
sendMessages = 0x00000800,
|
||||
sendTextToSpeech = 0x00001000,
|
||||
manageMessages = 0x00002000,
|
||||
embedLinks = 0x00004000,
|
||||
attachFiles = 0x00008000,
|
||||
readMessageHistory = 0x00010000,
|
||||
mentionEveryone = 0x00020000,
|
||||
useExternalEmojis = 0x00040000,
|
||||
connect = 0x00100000,
|
||||
speak = 0x00200000,
|
||||
muteMembers = 0x00400000,
|
||||
deafenMembers = 0x00800000,
|
||||
moveMembers = 0x01000000,
|
||||
useVoiceActivity = 0x02000000,
|
||||
changeNickname = 0x04000000,
|
||||
manageNicknames = 0x08000000,
|
||||
manageRoles = 0x10000000,
|
||||
manageWebhooks = 0x20000000,
|
||||
manageEmojis = 0x40000000,
|
||||
}
|
||||
|
||||
enums.actionType = enum {
|
||||
guildUpdate = 1,
|
||||
channelCreate = 10,
|
||||
channelUpdate = 11,
|
||||
channelDelete = 12,
|
||||
channelOverwriteCreate = 13,
|
||||
channelOverwriteUpdate = 14,
|
||||
channelOverwriteDelete = 15,
|
||||
memberKick = 20,
|
||||
memberPrune = 21,
|
||||
memberBanAdd = 22,
|
||||
memberBanRemove = 23,
|
||||
memberUpdate = 24,
|
||||
memberRoleUpdate = 25,
|
||||
roleCreate = 30,
|
||||
roleUpdate = 31,
|
||||
roleDelete = 32,
|
||||
inviteCreate = 40,
|
||||
inviteUpdate = 41,
|
||||
inviteDelete = 42,
|
||||
webhookCreate = 50,
|
||||
webhookUpdate = 51,
|
||||
webhookDelete = 52,
|
||||
emojiCreate = 60,
|
||||
emojiUpdate = 61,
|
||||
emojiDelete = 62,
|
||||
messageDelete = 72,
|
||||
}
|
||||
|
||||
enums.logLevel = enum {
|
||||
none = 0,
|
||||
error = 1,
|
||||
warning = 2,
|
||||
info = 3,
|
||||
debug = 4,
|
||||
}
|
||||
|
||||
return enums
|
||||
253
Lua/rpbot/deps/discordia/libs/extensions.lua
Normal file
253
Lua/rpbot/deps/discordia/libs/extensions.lua
Normal file
@@ -0,0 +1,253 @@
|
||||
--[[ NOTE:
|
||||
These standard library extensions are NOT used in Discordia. They are here as a
|
||||
convenience for those who wish to use them.
|
||||
|
||||
There are multiple ways to implement some of these commonly used functions.
|
||||
Please pay attention to the implementations used here and make sure that they
|
||||
match your expectations.
|
||||
|
||||
You may freely add to, remove, or edit any of the code here without any effect
|
||||
on the rest of the library. If you do make changes, do be careful when sharing
|
||||
your expectations with other users.
|
||||
|
||||
You can inject these extensions into the standard Lua global tables by
|
||||
calling either the main module (ex: discordia.extensions()) or each sub-module
|
||||
(ex: discordia.extensions.string())
|
||||
]]
|
||||
|
||||
local sort, concat = table.sort, table.concat
|
||||
local insert, remove = table.insert, table.remove
|
||||
local byte, char = string.byte, string.char
|
||||
local gmatch, match = string.gmatch, string.match
|
||||
local rep, find, sub = string.rep, string.find, string.sub
|
||||
local min, max, random = math.min, math.max, math.random
|
||||
local ceil, floor = math.ceil, math.floor
|
||||
|
||||
local table = {}
|
||||
|
||||
function table.count(tbl)
|
||||
local n = 0
|
||||
for _ in pairs(tbl) do
|
||||
n = n + 1
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
function table.deepcount(tbl)
|
||||
local n = 0
|
||||
for _, v in pairs(tbl) do
|
||||
n = type(v) == 'table' and n + table.deepcount(v) or n + 1
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
function table.copy(tbl)
|
||||
local ret = {}
|
||||
for k, v in pairs(tbl) do
|
||||
ret[k] = v
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.deepcopy(tbl)
|
||||
local ret = {}
|
||||
for k, v in pairs(tbl) do
|
||||
ret[k] = type(v) == 'table' and table.deepcopy(v) or v
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.reverse(tbl)
|
||||
for i = 1, #tbl do
|
||||
insert(tbl, i, remove(tbl))
|
||||
end
|
||||
end
|
||||
|
||||
function table.reversed(tbl)
|
||||
local ret = {}
|
||||
for i = #tbl, 1, -1 do
|
||||
insert(ret, tbl[i])
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.keys(tbl)
|
||||
local ret = {}
|
||||
for k in pairs(tbl) do
|
||||
insert(ret, k)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.values(tbl)
|
||||
local ret = {}
|
||||
for _, v in pairs(tbl) do
|
||||
insert(ret, v)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.randomipair(tbl)
|
||||
local i = random(#tbl)
|
||||
return i, tbl[i]
|
||||
end
|
||||
|
||||
function table.randompair(tbl)
|
||||
local rand = random(table.count(tbl))
|
||||
local n = 0
|
||||
for k, v in pairs(tbl) do
|
||||
n = n + 1
|
||||
if n == rand then
|
||||
return k, v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function table.sorted(tbl, fn)
|
||||
local ret = {}
|
||||
for i, v in ipairs(tbl) do
|
||||
ret[i] = v
|
||||
end
|
||||
sort(ret, fn)
|
||||
return ret
|
||||
end
|
||||
|
||||
function table.search(tbl, value)
|
||||
for k, v in pairs(tbl) do
|
||||
if v == value then
|
||||
return k
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function table.slice(tbl, start, stop, step)
|
||||
local ret = {}
|
||||
for i = start or 1, stop or #tbl, step or 1 do
|
||||
insert(ret, tbl[i])
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local string = {}
|
||||
|
||||
function string.split(str, delim)
|
||||
local ret = {}
|
||||
if not str then
|
||||
return ret
|
||||
end
|
||||
if not delim or delim == '' then
|
||||
for c in gmatch(str, '.') do
|
||||
insert(ret, c)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local n = 1
|
||||
while true do
|
||||
local i, j = find(str, delim, n)
|
||||
if not i then break end
|
||||
insert(ret, sub(str, n, i - 1))
|
||||
n = j + 1
|
||||
end
|
||||
insert(ret, sub(str, n))
|
||||
return ret
|
||||
end
|
||||
|
||||
function string.trim(str)
|
||||
return match(str, '^%s*(.-)%s*$')
|
||||
end
|
||||
|
||||
function string.pad(str, len, align, pattern)
|
||||
pattern = pattern or ' '
|
||||
if align == 'right' then
|
||||
return rep(pattern, (len - #str) / #pattern) .. str
|
||||
elseif align == 'center' then
|
||||
local pad = 0.5 * (len - #str) / #pattern
|
||||
return rep(pattern, floor(pad)) .. str .. rep(pattern, ceil(pad))
|
||||
else -- left
|
||||
return str .. rep(pattern, (len - #str) / #pattern)
|
||||
end
|
||||
end
|
||||
|
||||
function string.startswith(str, pattern, plain)
|
||||
local start = 1
|
||||
return find(str, pattern, start, plain) == start
|
||||
end
|
||||
|
||||
function string.endswith(str, pattern, plain)
|
||||
local start = #str - #pattern + 1
|
||||
return find(str, pattern, start, plain) == start
|
||||
end
|
||||
|
||||
function string.levenshtein(str1, str2)
|
||||
|
||||
if str1 == str2 then return 0 end
|
||||
|
||||
local len1 = #str1
|
||||
local len2 = #str2
|
||||
|
||||
if len1 == 0 then
|
||||
return len2
|
||||
elseif len2 == 0 then
|
||||
return len1
|
||||
end
|
||||
|
||||
local matrix = {}
|
||||
for i = 0, len1 do
|
||||
matrix[i] = {[0] = i}
|
||||
end
|
||||
for j = 0, len2 do
|
||||
matrix[0][j] = j
|
||||
end
|
||||
|
||||
for i = 1, len1 do
|
||||
for j = 1, len2 do
|
||||
local cost = byte(str1, i) == byte(str2, j) and 0 or 1
|
||||
matrix[i][j] = min(matrix[i-1][j] + 1, matrix[i][j-1] + 1, matrix[i-1][j-1] + cost)
|
||||
end
|
||||
end
|
||||
|
||||
return matrix[len1][len2]
|
||||
|
||||
end
|
||||
|
||||
function string.random(len, mn, mx)
|
||||
local ret = {}
|
||||
mn = mn or 0
|
||||
mx = mx or 255
|
||||
for _ = 1, len do
|
||||
insert(ret, char(random(mn, mx)))
|
||||
end
|
||||
return concat(ret)
|
||||
end
|
||||
|
||||
local math = {}
|
||||
|
||||
function math.clamp(n, minValue, maxValue)
|
||||
return min(max(n, minValue), maxValue)
|
||||
end
|
||||
|
||||
function math.round(n, i)
|
||||
local m = 10 ^ (i or 0)
|
||||
return floor(n * m + 0.5) / m
|
||||
end
|
||||
|
||||
local ext = setmetatable({
|
||||
table = table,
|
||||
string = string,
|
||||
math = math,
|
||||
}, {__call = function(self)
|
||||
for _, v in pairs(self) do
|
||||
v()
|
||||
end
|
||||
end})
|
||||
|
||||
for n, m in pairs(ext) do
|
||||
setmetatable(m, {__call = function(self)
|
||||
for k, v in pairs(self) do
|
||||
_G[n][k] = v
|
||||
end
|
||||
end})
|
||||
end
|
||||
|
||||
return ext
|
||||
103
Lua/rpbot/deps/discordia/libs/iterables/ArrayIterable.lua
Normal file
103
Lua/rpbot/deps/discordia/libs/iterables/ArrayIterable.lua
Normal file
@@ -0,0 +1,103 @@
|
||||
--[=[
|
||||
@c ArrayIterable x Iterable
|
||||
@d Iterable class that contains objects in a constant, ordered fashion, although
|
||||
the order may change if the internal array is modified. Some versions may use a
|
||||
map function to shape the objects before they are accessed.
|
||||
]=]
|
||||
|
||||
local wrap, yield = coroutine.wrap, coroutine.yield
|
||||
|
||||
local Iterable = require('iterables/Iterable')
|
||||
|
||||
local ArrayIterable, get = require('class')('ArrayIterable', Iterable)
|
||||
|
||||
function ArrayIterable:__init(array, map)
|
||||
self._array = array
|
||||
self._map = map
|
||||
end
|
||||
|
||||
function ArrayIterable:__len()
|
||||
local array = self._array
|
||||
if not array or #array == 0 then
|
||||
return 0
|
||||
end
|
||||
local map = self._map
|
||||
if map then -- map can return nil
|
||||
return Iterable.__len(self)
|
||||
else
|
||||
return #array
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p first * The first object in the array]=]
|
||||
function get.first(self)
|
||||
local array = self._array
|
||||
if not array or #array == 0 then
|
||||
return nil
|
||||
end
|
||||
local map = self._map
|
||||
if map then
|
||||
for i = 1, #array, 1 do
|
||||
local v = array[i]
|
||||
local obj = v and map(v)
|
||||
if obj then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
else
|
||||
return array[1]
|
||||
end
|
||||
end
|
||||
|
||||
--[=[@p last * The last object in the array]=]
|
||||
function get.last(self)
|
||||
local array = self._array
|
||||
if not array or #array == 0 then
|
||||
return nil
|
||||
end
|
||||
local map = self._map
|
||||
if map then
|
||||
for i = #array, 1, -1 do
|
||||
local v = array[i]
|
||||
local obj = v and map(v)
|
||||
if obj then
|
||||
return obj
|
||||
end
|
||||
end
|
||||
else
|
||||
return array[#array]
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m iter
|
||||
@r function
|
||||
@d Returns an iterator for all contained objects in a consistent order.
|
||||
]=]
|
||||
function ArrayIterable:iter()
|
||||
local array = self._array
|
||||
if not array or #array == 0 then
|
||||
return function() -- new closure for consistency
|
||||
return nil
|
||||
end
|
||||
end
|
||||
local map = self._map
|
||||
if map then
|
||||
return wrap(function()
|
||||
for _, v in ipairs(array) do
|
||||
local obj = map(v)
|
||||
if obj then
|
||||
yield(obj)
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
return array[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ArrayIterable
|
||||
139
Lua/rpbot/deps/discordia/libs/iterables/Cache.lua
Normal file
139
Lua/rpbot/deps/discordia/libs/iterables/Cache.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
--[=[
|
||||
@c Cache x Iterable
|
||||
@d description
|
||||
]=]
|
||||
|
||||
local json = require('json')
|
||||
local Iterable = require('iterables/Iterable')
|
||||
|
||||
local null = json.null
|
||||
|
||||
local Cache = require('class')('Cache', Iterable)
|
||||
|
||||
function Cache:__init(array, constructor, parent)
|
||||
local objects = {}
|
||||
for _, data in ipairs(array) do
|
||||
local obj = constructor(data, parent)
|
||||
objects[obj:__hash()] = obj
|
||||
end
|
||||
self._count = #array
|
||||
self._objects = objects
|
||||
self._constructor = constructor
|
||||
self._parent = parent
|
||||
end
|
||||
|
||||
function Cache:__pairs()
|
||||
return next, self._objects
|
||||
end
|
||||
|
||||
function Cache:__len()
|
||||
return self._count
|
||||
end
|
||||
|
||||
local function insert(self, k, obj)
|
||||
self._objects[k] = obj
|
||||
self._count = self._count + 1
|
||||
return obj
|
||||
end
|
||||
|
||||
local function remove(self, k, obj)
|
||||
self._objects[k] = nil
|
||||
self._count = self._count - 1
|
||||
return obj
|
||||
end
|
||||
|
||||
local function hash(data)
|
||||
-- local meta = getmetatable(data) -- debug
|
||||
-- assert(meta and meta.__jsontype == 'object') -- debug
|
||||
if data.id then -- snowflakes
|
||||
return data.id
|
||||
elseif data.user then -- members
|
||||
return data.user.id
|
||||
elseif data.emoji then -- reactions
|
||||
return data.emoji.id ~= null and data.emoji.id or data.emoji.name
|
||||
elseif data.code then -- invites
|
||||
return data.code
|
||||
else
|
||||
return nil, 'json data could not be hashed'
|
||||
end
|
||||
end
|
||||
|
||||
function Cache:_insert(data)
|
||||
local k = assert(hash(data))
|
||||
local old = self._objects[k]
|
||||
if old then
|
||||
old:_load(data)
|
||||
return old
|
||||
else
|
||||
local obj = self._constructor(data, self._parent)
|
||||
return insert(self, k, obj)
|
||||
end
|
||||
end
|
||||
|
||||
function Cache:_remove(data)
|
||||
local k = assert(hash(data))
|
||||
local old = self._objects[k]
|
||||
if old then
|
||||
old:_load(data)
|
||||
return remove(self, k, old)
|
||||
else
|
||||
return self._constructor(data, self._parent)
|
||||
end
|
||||
end
|
||||
|
||||
function Cache:_delete(k)
|
||||
local old = self._objects[k]
|
||||
if old then
|
||||
return remove(self, k, old)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function Cache:_load(array, update)
|
||||
if update then
|
||||
local updated = {}
|
||||
for _, data in ipairs(array) do
|
||||
local obj = self:_insert(data)
|
||||
updated[obj:__hash()] = true
|
||||
end
|
||||
for obj in self:iter() do
|
||||
local k = obj:__hash()
|
||||
if not updated[k] then
|
||||
self:_delete(k)
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, data in ipairs(array) do
|
||||
self:_insert(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m get
|
||||
@p k *
|
||||
@r *
|
||||
@d Returns an individual object by key, where the key should match the result of
|
||||
calling `__hash` on the contained objects. Unlike Iterable:get, this
|
||||
method operates with O(1) complexity.
|
||||
]=]
|
||||
function Cache:get(k)
|
||||
return self._objects[k]
|
||||
end
|
||||
|
||||
--[=[
|
||||
@m iter
|
||||
@r function
|
||||
@d Returns an iterator that returns all contained objects. The order of the objects
|
||||
is not guaranteed.
|
||||
]=]
|
||||
function Cache:iter()
|
||||
local objects, k, obj = self._objects
|
||||
return function()
|
||||
k, obj = next(objects, k)
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
return Cache
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user