mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Introduce HTTP API for plugins (#5383)
This commit is contained in:
parent
7dc80bc599
commit
c980162656
19 changed files with 1166 additions and 11 deletions
|
@ -14,6 +14,7 @@
|
|||
- Minor: Moderators can now see when users are warned. (#5441)
|
||||
- Minor: Added support for Brave & google-chrome-stable browsers. (#5452)
|
||||
- Minor: Added drop indicator line while dragging in tables. (#5256)
|
||||
- Minor: Introduce HTTP API for plugins. (#5383)
|
||||
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
|
||||
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)
|
||||
- Bugfix: Fixed restricted users usernames not being clickable. (#5405)
|
||||
|
|
32
docs/chatterino.d.ts
vendored
32
docs/chatterino.d.ts
vendored
|
@ -32,6 +32,8 @@ declare module c2 {
|
|||
is_valid(): boolean;
|
||||
}
|
||||
|
||||
interface ISharedResource {}
|
||||
|
||||
class RoomModes {
|
||||
unique_chat: boolean;
|
||||
subscriber_only: boolean;
|
||||
|
@ -69,6 +71,36 @@ declare module c2 {
|
|||
static by_twitch_id(id: string): null | Channel;
|
||||
}
|
||||
|
||||
enum HTTPMethod {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Patch,
|
||||
}
|
||||
|
||||
class HTTPResponse implements ISharedResource {
|
||||
data(): string;
|
||||
status(): number | null;
|
||||
error(): string;
|
||||
}
|
||||
|
||||
type HTTPCallback = (res: HTTPResponse) => void;
|
||||
class HTTPRequest implements ISharedResource {
|
||||
on_success(callback: HTTPCallback): void;
|
||||
on_error(callback: HTTPCallback): void;
|
||||
finally(callback: () => void): void;
|
||||
|
||||
set_timeout(millis: number): void;
|
||||
set_payload(data: string): void;
|
||||
set_header(name: string, value: string): void;
|
||||
|
||||
execute(): void;
|
||||
|
||||
// might error
|
||||
static create(method: HTTPMethod, url: string): HTTPRequest;
|
||||
}
|
||||
|
||||
function log(level: LogLevel, ...data: any[]): void;
|
||||
function register_command(
|
||||
name: String,
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
-- Add the folder this file is in to "Lua.workspace.library".
|
||||
|
||||
c2 = {}
|
||||
|
||||
---@alias c2.LogLevel integer
|
||||
---@type { Debug: c2.LogLevel, Info: c2.LogLevel, Warning: c2.LogLevel, Critical: c2.LogLevel }
|
||||
c2.LogLevel = {}
|
||||
|
@ -159,6 +158,89 @@ function c2.Channel.by_twitch_id(id) end
|
|||
|
||||
-- End src/controllers/plugins/api/ChannelRef.hpp
|
||||
|
||||
-- Begin src/controllers/plugins/api/HTTPRequest.hpp
|
||||
|
||||
---@class HTTPResponse
|
||||
---@field data string Data received from the server
|
||||
---@field status integer? HTTP Status code returned by the server
|
||||
---@field error string A somewhat human readable description of an error if such happened
|
||||
|
||||
---@alias HTTPCallback fun(result: HTTPResponse): nil
|
||||
---@class HTTPRequest
|
||||
HTTPRequest = {}
|
||||
|
||||
--- Sets the success callback
|
||||
---
|
||||
---@param callback HTTPCallback Function to call when the HTTP request succeeds
|
||||
function HTTPRequest:on_success(callback) end
|
||||
|
||||
--- Sets the failure callback
|
||||
---
|
||||
---@param callback HTTPCallback Function to call when the HTTP request fails or returns a non-ok status
|
||||
function HTTPRequest:on_error(callback) end
|
||||
|
||||
--- Sets the finally callback
|
||||
---
|
||||
---@param callback fun(): nil Function to call when the HTTP request finishes
|
||||
function HTTPRequest:finally(callback) end
|
||||
|
||||
--- Sets the timeout
|
||||
---
|
||||
---@param timeout integer How long in milliseconds until the times out
|
||||
function HTTPRequest:set_timeout(timeout) end
|
||||
|
||||
--- Sets the request payload
|
||||
---
|
||||
---@param data string
|
||||
function HTTPRequest:set_payload(data) end
|
||||
|
||||
--- Sets a header in the request
|
||||
---
|
||||
---@param name string
|
||||
---@param value string
|
||||
function HTTPRequest:set_header(name, value) end
|
||||
|
||||
--- Executes the HTTP request
|
||||
---
|
||||
function HTTPRequest:execute() end
|
||||
|
||||
--- Creates a new HTTPRequest
|
||||
---
|
||||
---@param method HTTPMethod Method to use
|
||||
---@param url string Where to send the request to
|
||||
---@return HTTPRequest
|
||||
function HTTPRequest.create(method, url) end
|
||||
|
||||
-- End src/controllers/plugins/api/HTTPRequest.hpp
|
||||
|
||||
-- Begin src/controllers/plugins/api/HTTPResponse.hpp
|
||||
|
||||
---@class HTTPResponse
|
||||
HTTPResponse = {}
|
||||
|
||||
--- Returns the data. This is not guaranteed to be encoded using any
|
||||
--- particular encoding scheme. It's just the bytes the server returned.
|
||||
---
|
||||
function HTTPResponse:data() end
|
||||
|
||||
--- Returns the status code.
|
||||
---
|
||||
function HTTPResponse:status() end
|
||||
|
||||
--- A somewhat human readable description of an error if such happened
|
||||
---
|
||||
function HTTPResponse:error() end
|
||||
|
||||
-- End src/controllers/plugins/api/HTTPResponse.hpp
|
||||
|
||||
-- Begin src/common/network/NetworkCommon.hpp
|
||||
|
||||
---@alias HTTPMethod integer
|
||||
---@type { Get: HTTPMethod, Post: HTTPMethod, Put: HTTPMethod, Delete: HTTPMethod, Patch: HTTPMethod }
|
||||
HTTPMethod = {}
|
||||
|
||||
-- End src/common/network/NetworkCommon.hpp
|
||||
|
||||
--- Registers a new command called `name` which when executed will call `handler`.
|
||||
---
|
||||
---@param name string The name of the command.
|
||||
|
|
|
@ -85,6 +85,24 @@ Example:
|
|||
}
|
||||
```
|
||||
|
||||
### Network
|
||||
|
||||
Allows the plugin to send HTTP requests.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
...,
|
||||
"permissions": [
|
||||
{
|
||||
"type": "Network"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Plugins with Typescript
|
||||
|
||||
If you prefer, you may use [TypescriptToLua](https://typescripttolua.github.io)
|
||||
|
@ -370,6 +388,138 @@ Returns `true` if the channel can be moderated by the current user.
|
|||
|
||||
Returns `true` if the current user is a VIP in the channel.
|
||||
|
||||
#### `HTTPMethod` enum
|
||||
|
||||
This table describes HTTP methods available to Lua Plugins. The values behind
|
||||
the names may change, do not count on them. It has the following keys:
|
||||
|
||||
- `Get`
|
||||
- `Post`
|
||||
- `Put`
|
||||
- `Delete`
|
||||
- `Patch`
|
||||
|
||||
#### `HTTPResponse`
|
||||
|
||||
An `HTTPResponse` is a table you receive in the callback after a completed `HTTPRequest`.
|
||||
|
||||
##### `HTTPResponse.data()`
|
||||
|
||||
This function returns the data received from the server as a string. Usually
|
||||
this will be UTF-8-encoded however that is not guaranteed, this could be any
|
||||
binary data.
|
||||
|
||||
##### `HTTPResponse.error()`
|
||||
|
||||
If an error happened this function returns a human readable description of it.
|
||||
|
||||
It returns something like: `"ConnectionRefusedError"`, `"401"`.
|
||||
|
||||
##### `HTTPResponse.status()`
|
||||
|
||||
This function returns the HTTP status code of the request or `nil` if there was
|
||||
an error before a status code could be received.
|
||||
|
||||
```lua
|
||||
{
|
||||
data = "This is the data received from the server as a string",
|
||||
status = 200, -- HTTP status code returned by the server or nil if no response was received because of an error
|
||||
error = "A somewhat human readable description of an error if such happened"
|
||||
}
|
||||
```
|
||||
|
||||
#### `HTTPRequest`
|
||||
|
||||
Allows you to send an HTTP request to a URL. Do not create requests that you
|
||||
don't want to call `execute()` on. For the time being that leaks callback
|
||||
functions and all their upvalues with them.
|
||||
|
||||
##### `HTTPRequest.create(method, url)`
|
||||
|
||||
Creates a new `HTTPRequest`. The `method` argument is an
|
||||
[`HTTPMethod`](#HTTPMethod-enum). The `url` argument must be a string
|
||||
containing a valid URL (ex. `https://example.com/path/to/api`).
|
||||
|
||||
```lua
|
||||
local req = c2.HTTPRequest.create(c2.HTTPMethod.Get, "https://example.com")
|
||||
req:on_success(function (res)
|
||||
print(res.data)
|
||||
end)
|
||||
req:execute()
|
||||
```
|
||||
|
||||
##### `HTTPRequest:on_success(callback)`
|
||||
|
||||
Sets the success callback. It accepts a function that takes a single parameter
|
||||
of type `HTTPResponse`. The callback will be called on success. This function
|
||||
returns nothing.
|
||||
|
||||
##### `HTTPRequest:on_error(callback)`
|
||||
|
||||
Sets the error callback. It accepts a function that takes a single parameter of
|
||||
type `HTTPResponse`. The callback will be called if the request fails. To see why
|
||||
it failed check the `error` field of the result. This function returns nothing.
|
||||
|
||||
##### `HTTPRequest:finally(callback)`
|
||||
|
||||
Sets the finally callback. It accepts a function that takes no parameters and
|
||||
returns nothing. It will be always called after `success` or `error`. This
|
||||
function returns nothing.
|
||||
|
||||
##### `HTTPRequest:set_timeout(timeout)`
|
||||
|
||||
Sets how long the request will take before it times out. The `timeout`
|
||||
parameter is in milliseconds. This function returns nothing.
|
||||
|
||||
##### `HTTPRequest:set_payload(data)`
|
||||
|
||||
Sets the data that will be sent with the request. The `data` parameter must be
|
||||
a string. This function returns nothing.
|
||||
|
||||
##### `HTTPRequest:set_header(name, value)`
|
||||
|
||||
Adds or overwrites a header in the request. Both `name` and `value` should be
|
||||
strings. If they are not strings they will be converted to strings. This
|
||||
function returns nothing.
|
||||
|
||||
##### `HTTPRequest:execute()`
|
||||
|
||||
Sends the request. This function returns nothing.
|
||||
|
||||
```lua
|
||||
local url = "http://localhost:8080/thing"
|
||||
local request = c2.HTTPRequest.create("Post", url)
|
||||
request:set_timeout(1000)
|
||||
request:set_payload("TEST!")
|
||||
request:set_header("X-Test", "Testing!")
|
||||
request:set_header("Content-Type", "text/plain")
|
||||
request:on_success(function (res)
|
||||
print('Success!')
|
||||
-- Data is in res.data
|
||||
print(res.status)
|
||||
end)
|
||||
request:on_error(function (res)
|
||||
print('Error!')
|
||||
print(res.status)
|
||||
print(res.error)
|
||||
end)
|
||||
request:finally(function ()
|
||||
print('Finally')
|
||||
end)
|
||||
request:execute()
|
||||
|
||||
-- This prints:
|
||||
-- Success!
|
||||
-- [content of /thing]
|
||||
-- 200
|
||||
-- Finally
|
||||
|
||||
-- Or:
|
||||
-- Error!
|
||||
-- nil
|
||||
-- ConnectionRefusedError
|
||||
```
|
||||
|
||||
### Input/Output API
|
||||
|
||||
These functions are wrappers for Lua's I/O library. Functions on file pointer
|
||||
|
|
|
@ -41,7 +41,6 @@ BOILERPLATE = """
|
|||
-- Add the folder this file is in to "Lua.workspace.library".
|
||||
|
||||
c2 = {}
|
||||
|
||||
"""
|
||||
|
||||
repo_root = Path(__file__).parent.parent
|
||||
|
|
|
@ -227,6 +227,10 @@ set(SOURCE_FILES
|
|||
controllers/plugins/api/ChannelRef.hpp
|
||||
controllers/plugins/api/IOWrapper.cpp
|
||||
controllers/plugins/api/IOWrapper.hpp
|
||||
controllers/plugins/api/HTTPRequest.cpp
|
||||
controllers/plugins/api/HTTPRequest.hpp
|
||||
controllers/plugins/api/HTTPResponse.cpp
|
||||
controllers/plugins/api/HTTPResponse.hpp
|
||||
controllers/plugins/LuaAPI.cpp
|
||||
controllers/plugins/LuaAPI.hpp
|
||||
controllers/plugins/PluginPermission.cpp
|
||||
|
|
|
@ -15,6 +15,9 @@ using NetworkSuccessCallback = std::function<void(NetworkResult)>;
|
|||
using NetworkErrorCallback = std::function<void(NetworkResult)>;
|
||||
using NetworkFinallyCallback = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @exposeenum HTTPMethod
|
||||
*/
|
||||
enum class NetworkRequestType {
|
||||
Get,
|
||||
Post,
|
||||
|
|
|
@ -98,6 +98,13 @@ NetworkRequest NetworkRequest::header(QNetworkRequest::KnownHeaders header,
|
|||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::header(const QByteArray &headerName,
|
||||
const QByteArray &value) &&
|
||||
{
|
||||
this->data->request.setRawHeader(headerName, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::headerList(
|
||||
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&
|
||||
{
|
||||
|
|
|
@ -57,6 +57,8 @@ public:
|
|||
NetworkRequest header(const char *headerName, const char *value) &&;
|
||||
NetworkRequest header(const char *headerName, const QByteArray &value) &&;
|
||||
NetworkRequest header(const char *headerName, const QString &value) &&;
|
||||
NetworkRequest header(const QByteArray &headerName,
|
||||
const QByteArray &value) &&;
|
||||
NetworkRequest header(QNetworkRequest::KnownHeaders header,
|
||||
const QVariant &value) &&;
|
||||
NetworkRequest headerList(
|
||||
|
|
|
@ -82,6 +82,9 @@ struct CompletionEvent {
|
|||
/**
|
||||
* @includefile common/Channel.hpp
|
||||
* @includefile controllers/plugins/api/ChannelRef.hpp
|
||||
* @includefile controllers/plugins/api/HTTPRequest.hpp
|
||||
* @includefile controllers/plugins/api/HTTPResponse.hpp
|
||||
* @includefile common/network/NetworkCommon.hpp
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -133,7 +136,11 @@ int searcherRelative(lua_State *L);
|
|||
// This is a fat pointer that allows us to type check values given to functions needing a userdata.
|
||||
// Ensure ALL userdata given to Lua are a subclass of this! Otherwise we garbage as a pointer!
|
||||
struct UserData {
|
||||
enum class Type { Channel };
|
||||
enum class Type {
|
||||
Channel,
|
||||
HTTPRequest,
|
||||
HTTPResponse,
|
||||
};
|
||||
Type type;
|
||||
bool isWeak;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/Plugin.hpp"
|
||||
|
||||
# include "common/network/NetworkCommon.hpp"
|
||||
# include "common/QLogging.hpp"
|
||||
# include "controllers/commands/CommandController.hpp"
|
||||
# include "controllers/plugins/PluginPermission.hpp"
|
||||
# include "util/QMagicEnum.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
@ -11,6 +13,8 @@ extern "C" {
|
|||
# include <magic_enum/magic_enum.hpp>
|
||||
# include <QJsonArray>
|
||||
# include <QJsonObject>
|
||||
# include <QLoggingCategory>
|
||||
# include <QUrl>
|
||||
|
||||
# include <algorithm>
|
||||
# include <unordered_map>
|
||||
|
@ -258,16 +262,25 @@ bool Plugin::hasFSPermissionFor(bool write, const QString &path)
|
|||
using PType = PluginPermission::Type;
|
||||
auto typ = write ? PType::FilesystemWrite : PType::FilesystemRead;
|
||||
|
||||
// XXX: Older compilers don't have support for std::ranges
|
||||
// NOLINTNEXTLINE(readability-use-anyofallof)
|
||||
for (const auto &p : this->meta.permissions)
|
||||
return std::ranges::any_of(this->meta.permissions, [=](const auto &p) {
|
||||
return p.type == typ;
|
||||
});
|
||||
}
|
||||
|
||||
bool Plugin::hasHTTPPermissionFor(const QUrl &url)
|
||||
{
|
||||
auto proto = url.scheme();
|
||||
if (proto != "http" && proto != "https")
|
||||
{
|
||||
if (p.type == typ)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
qCWarning(chatterinoLua).nospace()
|
||||
<< "Plugin " << this->id << " (" << this->meta.name
|
||||
<< ") is trying to use a non-http protocol";
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
return std::ranges::any_of(this->meta.permissions, [](const auto &p) {
|
||||
return p.type == PluginPermission::Type::Network;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "Application.hpp"
|
||||
# include "common/network/NetworkCommon.hpp"
|
||||
# include "controllers/plugins/LuaAPI.hpp"
|
||||
# include "controllers/plugins/LuaUtilities.hpp"
|
||||
# include "controllers/plugins/PluginPermission.hpp"
|
||||
|
||||
# include <QDir>
|
||||
# include <QString>
|
||||
# include <QUrl>
|
||||
# include <semver/semver.hpp>
|
||||
|
||||
# include <unordered_map>
|
||||
|
@ -139,6 +141,7 @@ public:
|
|||
void removeTimeout(QTimer *timer);
|
||||
|
||||
bool hasFSPermissionFor(bool write, const QString &path);
|
||||
bool hasHTTPPermissionFor(const QUrl &url);
|
||||
|
||||
private:
|
||||
QDir loadDirectory_;
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
# include "Application.hpp"
|
||||
# include "common/Args.hpp"
|
||||
# include "common/network/NetworkCommon.hpp"
|
||||
# include "common/QLogging.hpp"
|
||||
# include "controllers/commands/CommandContext.hpp"
|
||||
# include "controllers/commands/CommandController.hpp"
|
||||
# include "controllers/plugins/api/ChannelRef.hpp"
|
||||
# include "controllers/plugins/api/HTTPRequest.hpp"
|
||||
# include "controllers/plugins/api/HTTPResponse.hpp"
|
||||
# include "controllers/plugins/api/IOWrapper.hpp"
|
||||
# include "controllers/plugins/LuaAPI.hpp"
|
||||
# include "controllers/plugins/LuaUtilities.hpp"
|
||||
|
@ -174,10 +177,19 @@ void PluginController::openLibrariesFor(lua_State *L, const PluginMeta &meta,
|
|||
lua::pushEnumTable<Channel::Type>(L);
|
||||
lua_setfield(L, c2libIdx, "ChannelType");
|
||||
|
||||
lua::pushEnumTable<NetworkRequestType>(L);
|
||||
lua_setfield(L, c2libIdx, "HTTPMethod");
|
||||
|
||||
// Initialize metatables for objects
|
||||
lua::api::ChannelRef::createMetatable(L);
|
||||
lua_setfield(L, c2libIdx, "Channel");
|
||||
|
||||
lua::api::HTTPRequest::createMetatable(L);
|
||||
lua_setfield(L, c2libIdx, "HTTPRequest");
|
||||
|
||||
lua::api::HTTPResponse::createMetatable(L);
|
||||
lua_setfield(L, c2libIdx, "HTTPResponse");
|
||||
|
||||
lua_setfield(L, gtable, "c2");
|
||||
|
||||
// ban functions
|
||||
|
|
|
@ -37,6 +37,8 @@ QString PluginPermission::toHtml() const
|
|||
return "Read files in its data directory";
|
||||
case PluginPermission::Type::FilesystemWrite:
|
||||
return "Write to or create files in its data directory";
|
||||
case PluginPermission::Type::Network:
|
||||
return "Make requests over the internet to third party websites";
|
||||
default:
|
||||
assert(false && "invalid PluginPermission type in toHtml()");
|
||||
return "shut up compiler, this never happens";
|
||||
|
|
|
@ -14,6 +14,7 @@ struct PluginPermission {
|
|||
enum class Type {
|
||||
FilesystemRead,
|
||||
FilesystemWrite,
|
||||
Network,
|
||||
};
|
||||
Type type;
|
||||
std::vector<QString> errors;
|
||||
|
|
451
src/controllers/plugins/api/HTTPRequest.cpp
Normal file
451
src/controllers/plugins/api/HTTPRequest.cpp
Normal file
|
@ -0,0 +1,451 @@
|
|||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/api/HTTPRequest.hpp"
|
||||
|
||||
# include "Application.hpp"
|
||||
# include "common/network/NetworkCommon.hpp"
|
||||
# include "common/network/NetworkRequest.hpp"
|
||||
# include "common/network/NetworkResult.hpp"
|
||||
# include "controllers/plugins/api/HTTPResponse.hpp"
|
||||
# include "controllers/plugins/LuaAPI.hpp"
|
||||
# include "controllers/plugins/LuaUtilities.hpp"
|
||||
# include "util/DebugCount.hpp"
|
||||
|
||||
extern "C" {
|
||||
# include <lauxlib.h>
|
||||
# include <lua.h>
|
||||
}
|
||||
# include <QRandomGenerator>
|
||||
# include <QUrl>
|
||||
|
||||
# include <memory>
|
||||
# include <utility>
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
// NOLINTBEGIN(*vararg)
|
||||
// NOLINTNEXTLINE(*-avoid-c-arrays)
|
||||
static const luaL_Reg HTTP_REQUEST_METHODS[] = {
|
||||
{"on_success", &HTTPRequest::on_success_wrap},
|
||||
{"on_error", &HTTPRequest::on_error_wrap},
|
||||
{"finally", &HTTPRequest::finally_wrap},
|
||||
|
||||
{"execute", &HTTPRequest::execute_wrap},
|
||||
{"set_timeout", &HTTPRequest::set_timeout_wrap},
|
||||
{"set_payload", &HTTPRequest::set_payload_wrap},
|
||||
{"set_header", &HTTPRequest::set_header_wrap},
|
||||
// static
|
||||
{"create", &HTTPRequest::create},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
std::shared_ptr<HTTPRequest> HTTPRequest::getOrError(lua_State *L,
|
||||
StackIdx where)
|
||||
{
|
||||
if (lua_gettop(L) < 1)
|
||||
{
|
||||
// The nullptr is there just to appease the compiler, luaL_error is no return
|
||||
luaL_error(L, "Called c2.HTTPRequest method without a request object");
|
||||
return nullptr;
|
||||
}
|
||||
if (lua_isuserdata(L, where) == 0)
|
||||
{
|
||||
luaL_error(
|
||||
L,
|
||||
"Called c2.HTTPRequest method with a non-userdata 'self' argument");
|
||||
return nullptr;
|
||||
}
|
||||
// luaL_checkudata is no-return if check fails
|
||||
auto *checked = luaL_checkudata(L, where, "c2.HTTPRequest");
|
||||
auto *data =
|
||||
SharedPtrUserData<UserData::Type::HTTPRequest, HTTPRequest>::from(
|
||||
checked);
|
||||
if (data == nullptr)
|
||||
{
|
||||
luaL_error(L, "Called c2.HTTPRequest method with an invalid pointer");
|
||||
return nullptr;
|
||||
}
|
||||
lua_remove(L, where);
|
||||
if (data->target == nullptr)
|
||||
{
|
||||
luaL_error(
|
||||
L, "Internal error: SharedPtrUserData<UserData::Type::HTTPRequest, "
|
||||
"HTTPRequest>::target was null. This is a Chatterino bug!");
|
||||
return nullptr;
|
||||
}
|
||||
if (data->target->done)
|
||||
{
|
||||
luaL_error(L, "This c2.HTTPRequest has already been executed!");
|
||||
return nullptr;
|
||||
}
|
||||
return data->target;
|
||||
}
|
||||
|
||||
void HTTPRequest::createMetatable(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, 1);
|
||||
|
||||
luaL_newmetatable(L, "c2.HTTPRequest");
|
||||
lua_pushstring(L, "__index");
|
||||
lua_pushvalue(L, -2); // clone metatable
|
||||
lua_settable(L, -3); // metatable.__index = metatable
|
||||
|
||||
// Generic ISharedResource stuff
|
||||
lua_pushstring(L, "__gc");
|
||||
lua_pushcfunction(L, (&SharedPtrUserData<UserData::Type::HTTPRequest,
|
||||
HTTPRequest>::destroy));
|
||||
lua_settable(L, -3); // metatable.__gc = SharedPtrUserData<...>::destroy
|
||||
|
||||
luaL_setfuncs(L, HTTP_REQUEST_METHODS, 0);
|
||||
}
|
||||
|
||||
int HTTPRequest::on_success_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -2);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->on_success(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::on_success(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 1)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:on_success needs 1 argument (a callback "
|
||||
"that takes an HTTPResult and doesn't return anything)");
|
||||
}
|
||||
if (!lua_isfunction(L, top))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:on_success needs 1 argument (a callback "
|
||||
"that takes an HTTPResult and doesn't return anything)");
|
||||
}
|
||||
auto shared = this->pushPrivate(L);
|
||||
lua_pushvalue(L, -2);
|
||||
lua_setfield(L, shared, "success"); // this deletes the function copy
|
||||
lua_pop(L, 2); // delete the table and function original
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::on_error_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -2);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->on_error(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::on_error(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 1)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:on_error needs 1 argument (a callback "
|
||||
"that takes an HTTPResult and doesn't return anything)");
|
||||
}
|
||||
if (!lua_isfunction(L, top))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:on_error needs 1 argument (a callback "
|
||||
"that takes an HTTPResult and doesn't return anything)");
|
||||
}
|
||||
auto shared = this->pushPrivate(L);
|
||||
lua_pushvalue(L, -2);
|
||||
lua_setfield(L, shared, "error"); // this deletes the function copy
|
||||
lua_pop(L, 2); // delete the table and function original
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::set_timeout_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -2);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->set_timeout(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::set_timeout(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 1)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_timeout needs 1 argument (a number of "
|
||||
"milliseconds after which the request will time out)");
|
||||
}
|
||||
|
||||
int temporary = -1;
|
||||
if (!lua::pop(L, &temporary))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_timeout failed to get timeout, expected a "
|
||||
"positive integer");
|
||||
}
|
||||
if (temporary <= 0)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_timeout failed to get timeout, expected a "
|
||||
"positive integer");
|
||||
}
|
||||
this->timeout_ = temporary;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::finally_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -2);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->finally(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::finally(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 1)
|
||||
{
|
||||
return luaL_error(L, "HTTPRequest:finally needs 1 argument (a callback "
|
||||
"that takes nothing and doesn't return anything)");
|
||||
}
|
||||
if (!lua_isfunction(L, top))
|
||||
{
|
||||
return luaL_error(L, "HTTPRequest:finally needs 1 argument (a callback "
|
||||
"that takes nothing and doesn't return anything)");
|
||||
}
|
||||
auto shared = this->pushPrivate(L);
|
||||
lua_pushvalue(L, -2);
|
||||
lua_setfield(L, shared, "finally"); // this deletes the function copy
|
||||
lua_pop(L, 2); // delete the table and function original
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::set_payload_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -2);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->set_payload(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::set_payload(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 1)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_payload needs 1 argument (a string payload)");
|
||||
}
|
||||
|
||||
std::string temporary;
|
||||
if (!lua::pop(L, &temporary))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_payload failed to get payload, expected a "
|
||||
"string");
|
||||
}
|
||||
this->req_ =
|
||||
std::move(this->req_).payload(QByteArray::fromStdString(temporary));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::set_header_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -3);
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->set_header(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::set_header(lua_State *L)
|
||||
{
|
||||
auto top = lua_gettop(L);
|
||||
if (top != 2)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest:set_header needs 2 arguments (a header name "
|
||||
"and a value)");
|
||||
}
|
||||
|
||||
std::string value;
|
||||
if (!lua::pop(L, &value))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "cannot get value (2nd argument of HTTPRequest:set_header)");
|
||||
}
|
||||
std::string name;
|
||||
if (!lua::pop(L, &name))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "cannot get name (1st argument of HTTPRequest:set_header)");
|
||||
}
|
||||
this->req_ = std::move(this->req_)
|
||||
.header(QByteArray::fromStdString(name),
|
||||
QByteArray::fromStdString(value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HTTPRequest::create(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, -1);
|
||||
if (lua_gettop(L) != 2)
|
||||
{
|
||||
return luaL_error(
|
||||
L, "HTTPRequest.create needs exactly 2 arguments (method "
|
||||
"and url)");
|
||||
}
|
||||
QString url;
|
||||
if (!lua::pop(L, &url))
|
||||
{
|
||||
return luaL_error(L,
|
||||
"cannot get url (2nd argument of HTTPRequest.create, "
|
||||
"expected a string)");
|
||||
}
|
||||
auto parsedurl = QUrl(url);
|
||||
if (!parsedurl.isValid())
|
||||
{
|
||||
return luaL_error(
|
||||
L, "cannot parse url (2nd argument of HTTPRequest.create, "
|
||||
"got invalid url in argument)");
|
||||
}
|
||||
NetworkRequestType method{};
|
||||
if (!lua::pop(L, &method))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "cannot get method (1st argument of HTTPRequest.create, "
|
||||
"expected a string)");
|
||||
}
|
||||
auto *pl = getIApp()->getPlugins()->getPluginByStatePtr(L);
|
||||
if (!pl->hasHTTPPermissionFor(parsedurl))
|
||||
{
|
||||
return luaL_error(
|
||||
L, "Plugin does not have permission to send HTTP requests "
|
||||
"to this URL");
|
||||
}
|
||||
NetworkRequest r(parsedurl, method);
|
||||
lua::push(
|
||||
L, std::make_shared<HTTPRequest>(ConstructorAccessTag{}, std::move(r)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int HTTPRequest::execute_wrap(lua_State *L)
|
||||
{
|
||||
auto ptr = HTTPRequest::getOrError(L, 1);
|
||||
return ptr->execute(L);
|
||||
}
|
||||
|
||||
int HTTPRequest::execute(lua_State *L)
|
||||
{
|
||||
auto shared = this->shared_from_this();
|
||||
this->done = true;
|
||||
std::move(this->req_)
|
||||
.onSuccess([shared, L](const NetworkResult &res) {
|
||||
lua::StackGuard guard(L);
|
||||
auto *thread = lua_newthread(L);
|
||||
|
||||
auto priv = shared->pushPrivate(thread);
|
||||
lua_getfield(thread, priv, "success");
|
||||
auto cb = lua_gettop(thread);
|
||||
if (lua_isfunction(thread, cb))
|
||||
{
|
||||
lua::push(thread, std::make_shared<HTTPResponse>(res));
|
||||
// one arg, no return, no msgh
|
||||
lua_pcall(thread, 1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pop(thread, 1); // remove callback
|
||||
}
|
||||
lua_closethread(thread, nullptr);
|
||||
lua_pop(L, 1); // remove thread from L
|
||||
})
|
||||
.onError([shared, L](const NetworkResult &res) {
|
||||
lua::StackGuard guard(L);
|
||||
auto *thread = lua_newthread(L);
|
||||
|
||||
auto priv = shared->pushPrivate(thread);
|
||||
lua_getfield(thread, priv, "error");
|
||||
auto cb = lua_gettop(thread);
|
||||
if (lua_isfunction(thread, cb))
|
||||
{
|
||||
lua::push(thread, std::make_shared<HTTPResponse>(res));
|
||||
// one arg, no return, no msgh
|
||||
lua_pcall(thread, 1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pop(thread, 1); // remove callback
|
||||
}
|
||||
lua_closethread(thread, nullptr);
|
||||
lua_pop(L, 1); // remove thread from L
|
||||
})
|
||||
.finally([shared, L]() {
|
||||
lua::StackGuard guard(L);
|
||||
auto *thread = lua_newthread(L);
|
||||
|
||||
auto priv = shared->pushPrivate(thread);
|
||||
lua_getfield(thread, priv, "finally");
|
||||
auto cb = lua_gettop(thread);
|
||||
if (lua_isfunction(thread, cb))
|
||||
{
|
||||
// no args, no return, no msgh
|
||||
lua_pcall(thread, 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
lua_pop(thread, 1); // remove callback
|
||||
}
|
||||
// remove our private data
|
||||
lua_pushnil(thread);
|
||||
lua_setfield(thread, LUA_REGISTRYINDEX,
|
||||
shared->privateKey.toStdString().c_str());
|
||||
lua_closethread(thread, nullptr);
|
||||
lua_pop(L, 1); // remove thread from L
|
||||
|
||||
// we removed our private table, forget the key for it
|
||||
shared->privateKey = QString();
|
||||
})
|
||||
.timeout(this->timeout_)
|
||||
.execute();
|
||||
return 0;
|
||||
}
|
||||
|
||||
HTTPRequest::HTTPRequest(HTTPRequest::ConstructorAccessTag /*ignored*/,
|
||||
NetworkRequest req)
|
||||
: req_(std::move(req))
|
||||
{
|
||||
DebugCount::increase("lua::api::HTTPRequest");
|
||||
}
|
||||
|
||||
HTTPRequest::~HTTPRequest()
|
||||
{
|
||||
DebugCount::decrease("lua::api::HTTPRequest");
|
||||
// We might leak a Lua function or two here if the request isn't executed
|
||||
// but that's better than accessing a possibly invalid lua_State pointer.
|
||||
}
|
||||
|
||||
StackIdx HTTPRequest::pushPrivate(lua_State *L)
|
||||
{
|
||||
if (this->privateKey.isEmpty())
|
||||
{
|
||||
this->privateKey = QString("HTTPRequestPrivate%1")
|
||||
.arg(QRandomGenerator::system()->generate());
|
||||
pushEmptyTable(L, 4);
|
||||
lua_setfield(L, LUA_REGISTRYINDEX,
|
||||
this->privateKey.toStdString().c_str());
|
||||
}
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, this->privateKey.toStdString().c_str());
|
||||
return lua_gettop(L);
|
||||
}
|
||||
|
||||
// NOLINTEND(*vararg)
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
StackIdx push(lua_State *L, std::shared_ptr<api::HTTPRequest> request)
|
||||
{
|
||||
using namespace chatterino::lua::api;
|
||||
|
||||
SharedPtrUserData<UserData::Type::HTTPRequest, HTTPRequest>::create(
|
||||
L, std::move(request));
|
||||
luaL_getmetatable(L, "c2.HTTPRequest");
|
||||
lua_setmetatable(L, -2);
|
||||
return lua_gettop(L);
|
||||
}
|
||||
} // namespace chatterino::lua
|
||||
#endif
|
162
src/controllers/plugins/api/HTTPRequest.hpp
Normal file
162
src/controllers/plugins/api/HTTPRequest.hpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "common/network/NetworkRequest.hpp"
|
||||
# include "controllers/plugins/LuaUtilities.hpp"
|
||||
# include "controllers/plugins/PluginController.hpp"
|
||||
|
||||
# include <memory>
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
|
||||
/**
|
||||
* @lua@class HTTPResponse
|
||||
* @lua@field data string Data received from the server
|
||||
* @lua@field status integer? HTTP Status code returned by the server
|
||||
* @lua@field error string A somewhat human readable description of an error if such happened
|
||||
*/
|
||||
|
||||
/**
|
||||
* @lua@alias HTTPCallback fun(result: HTTPResponse): nil
|
||||
*/
|
||||
|
||||
/**
|
||||
* @lua@class HTTPRequest
|
||||
*/
|
||||
class HTTPRequest : public std::enable_shared_from_this<HTTPRequest>
|
||||
{
|
||||
// This type is private to prevent the accidental construction of HTTPRequest without a shared pointer
|
||||
struct ConstructorAccessTag {
|
||||
};
|
||||
|
||||
public:
|
||||
HTTPRequest(HTTPRequest::ConstructorAccessTag, NetworkRequest req);
|
||||
HTTPRequest(HTTPRequest &&other) = default;
|
||||
HTTPRequest &operator=(HTTPRequest &&) = default;
|
||||
HTTPRequest &operator=(HTTPRequest &) = delete;
|
||||
HTTPRequest(const HTTPRequest &other) = delete;
|
||||
~HTTPRequest();
|
||||
|
||||
private:
|
||||
NetworkRequest req_;
|
||||
|
||||
static void createMetatable(lua_State *L);
|
||||
friend class chatterino::PluginController;
|
||||
|
||||
/**
|
||||
* @brief Get the content of the top object on Lua stack, usually the first argument as an HTTPRequest
|
||||
*
|
||||
* If the object given is not a userdatum or the pointer inside that
|
||||
* userdatum doesn't point to a HTTPRequest, a lua error is thrown.
|
||||
*
|
||||
* This function always returns a non-null pointer.
|
||||
*/
|
||||
static std::shared_ptr<HTTPRequest> getOrError(lua_State *L,
|
||||
StackIdx where = -1);
|
||||
/**
|
||||
* Pushes the private table onto the lua stack.
|
||||
*
|
||||
* This might create it if it doesn't exist.
|
||||
*/
|
||||
StackIdx pushPrivate(lua_State *L);
|
||||
|
||||
// This is the key in the registry the private table it held at (if it exists)
|
||||
// This might be a null QString if the request has already been executed or
|
||||
// the table wasn't created yet.
|
||||
QString privateKey;
|
||||
int timeout_ = 10'000;
|
||||
bool done = false;
|
||||
|
||||
public:
|
||||
// These functions are wrapped so data can be accessed more easily. When a call from Lua comes in:
|
||||
// - the static wrapper function is called
|
||||
// - it calls getOrError
|
||||
// - and then the wrapped method
|
||||
|
||||
/**
|
||||
* Sets the success callback
|
||||
*
|
||||
* @lua@param callback HTTPCallback Function to call when the HTTP request succeeds
|
||||
* @exposed HTTPRequest:on_success
|
||||
*/
|
||||
static int on_success_wrap(lua_State *L);
|
||||
int on_success(lua_State *L);
|
||||
|
||||
/**
|
||||
* Sets the failure callback
|
||||
*
|
||||
* @lua@param callback HTTPCallback Function to call when the HTTP request fails or returns a non-ok status
|
||||
* @exposed HTTPRequest:on_error
|
||||
*/
|
||||
static int on_error_wrap(lua_State *L);
|
||||
int on_error(lua_State *L);
|
||||
|
||||
/**
|
||||
* Sets the finally callback
|
||||
*
|
||||
* @lua@param callback fun(): nil Function to call when the HTTP request finishes
|
||||
* @exposed HTTPRequest:finally
|
||||
*/
|
||||
static int finally_wrap(lua_State *L);
|
||||
int finally(lua_State *L);
|
||||
|
||||
/**
|
||||
* Sets the timeout
|
||||
*
|
||||
* @lua@param timeout integer How long in milliseconds until the times out
|
||||
* @exposed HTTPRequest:set_timeout
|
||||
*/
|
||||
static int set_timeout_wrap(lua_State *L);
|
||||
int set_timeout(lua_State *L);
|
||||
|
||||
/**
|
||||
* Sets the request payload
|
||||
*
|
||||
* @lua@param data string
|
||||
* @exposed HTTPRequest:set_payload
|
||||
*/
|
||||
static int set_payload_wrap(lua_State *L);
|
||||
int set_payload(lua_State *L);
|
||||
|
||||
/**
|
||||
* Sets a header in the request
|
||||
*
|
||||
* @lua@param name string
|
||||
* @lua@param value string
|
||||
* @exposed HTTPRequest:set_header
|
||||
*/
|
||||
static int set_header_wrap(lua_State *L);
|
||||
int set_header(lua_State *L);
|
||||
|
||||
/**
|
||||
* Executes the HTTP request
|
||||
*
|
||||
* @exposed HTTPRequest:execute
|
||||
*/
|
||||
static int execute_wrap(lua_State *L);
|
||||
int execute(lua_State *L);
|
||||
|
||||
/**
|
||||
* Static functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new HTTPRequest
|
||||
*
|
||||
* @lua@param method HTTPMethod Method to use
|
||||
* @lua@param url string Where to send the request to
|
||||
*
|
||||
* @lua@return HTTPRequest
|
||||
* @exposed HTTPRequest.create
|
||||
*/
|
||||
static int create(lua_State *L);
|
||||
};
|
||||
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
namespace chatterino::lua {
|
||||
StackIdx push(lua_State *L, std::shared_ptr<api::HTTPRequest> request);
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
144
src/controllers/plugins/api/HTTPResponse.cpp
Normal file
144
src/controllers/plugins/api/HTTPResponse.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/api/HTTPResponse.hpp"
|
||||
|
||||
# include "common/network/NetworkResult.hpp"
|
||||
# include "controllers/plugins/LuaAPI.hpp"
|
||||
# include "util/DebugCount.hpp"
|
||||
|
||||
extern "C" {
|
||||
# include <lauxlib.h>
|
||||
}
|
||||
# include <utility>
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
// NOLINTBEGIN(*vararg)
|
||||
// NOLINTNEXTLINE(*-avoid-c-arrays)
|
||||
static const luaL_Reg HTTP_RESPONSE_METHODS[] = {
|
||||
{"data", &HTTPResponse::data_wrap},
|
||||
{"status", &HTTPResponse::status_wrap},
|
||||
{"error", &HTTPResponse::error_wrap},
|
||||
{nullptr, nullptr},
|
||||
};
|
||||
|
||||
void HTTPResponse::createMetatable(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, 1);
|
||||
|
||||
luaL_newmetatable(L, "c2.HTTPResponse");
|
||||
lua_pushstring(L, "__index");
|
||||
lua_pushvalue(L, -2); // clone metatable
|
||||
lua_settable(L, -3); // metatable.__index = metatable
|
||||
|
||||
// Generic ISharedResource stuff
|
||||
lua_pushstring(L, "__gc");
|
||||
lua_pushcfunction(L, (&SharedPtrUserData<UserData::Type::HTTPResponse,
|
||||
HTTPResponse>::destroy));
|
||||
lua_settable(L, -3); // metatable.__gc = SharedPtrUserData<...>::destroy
|
||||
|
||||
luaL_setfuncs(L, HTTP_RESPONSE_METHODS, 0);
|
||||
}
|
||||
|
||||
std::shared_ptr<HTTPResponse> HTTPResponse::getOrError(lua_State *L,
|
||||
StackIdx where)
|
||||
{
|
||||
if (lua_gettop(L) < 1)
|
||||
{
|
||||
// The nullptr is there just to appease the compiler, luaL_error is no return
|
||||
luaL_error(L, "Called c2.HTTPResponse method without a request object");
|
||||
return nullptr;
|
||||
}
|
||||
if (lua_isuserdata(L, where) == 0)
|
||||
{
|
||||
luaL_error(L, "Called c2.HTTPResponse method with a non-userdata "
|
||||
"'self' argument");
|
||||
return nullptr;
|
||||
}
|
||||
// luaL_checkudata is no-return if check fails
|
||||
auto *checked = luaL_checkudata(L, where, "c2.HTTPResponse");
|
||||
auto *data =
|
||||
SharedPtrUserData<UserData::Type::HTTPResponse, HTTPResponse>::from(
|
||||
checked);
|
||||
if (data == nullptr)
|
||||
{
|
||||
luaL_error(L, "Called c2.HTTPResponse method with an invalid pointer");
|
||||
return nullptr;
|
||||
}
|
||||
lua_remove(L, where);
|
||||
if (data->target == nullptr)
|
||||
{
|
||||
luaL_error(
|
||||
L,
|
||||
"Internal error: SharedPtrUserData<UserData::Type::HTTPResponse, "
|
||||
"HTTPResponse>::target was null. This is a Chatterino bug!");
|
||||
return nullptr;
|
||||
}
|
||||
return data->target;
|
||||
}
|
||||
|
||||
HTTPResponse::HTTPResponse(NetworkResult res)
|
||||
: result_(std::move(res))
|
||||
{
|
||||
DebugCount::increase("lua::api::HTTPResponse");
|
||||
}
|
||||
HTTPResponse::~HTTPResponse()
|
||||
{
|
||||
DebugCount::decrease("lua::api::HTTPResponse");
|
||||
}
|
||||
|
||||
int HTTPResponse::data_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, 0); // 1 in, 1 out
|
||||
auto ptr = HTTPResponse::getOrError(L, 1);
|
||||
return ptr->data(L);
|
||||
}
|
||||
|
||||
int HTTPResponse::data(lua_State *L)
|
||||
{
|
||||
lua::push(L, this->result_.getData().toStdString());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int HTTPResponse::status_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, 0); // 1 in, 1 out
|
||||
auto ptr = HTTPResponse::getOrError(L, 1);
|
||||
return ptr->status(L);
|
||||
}
|
||||
|
||||
int HTTPResponse::status(lua_State *L)
|
||||
{
|
||||
lua::push(L, this->result_.status());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int HTTPResponse::error_wrap(lua_State *L)
|
||||
{
|
||||
lua::StackGuard guard(L, 0); // 1 in, 1 out
|
||||
auto ptr = HTTPResponse::getOrError(L, 1);
|
||||
return ptr->error(L);
|
||||
}
|
||||
|
||||
int HTTPResponse::error(lua_State *L)
|
||||
{
|
||||
lua::push(L, this->result_.formatError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// NOLINTEND(*vararg)
|
||||
} // namespace chatterino::lua::api
|
||||
|
||||
namespace chatterino::lua {
|
||||
StackIdx push(lua_State *L, std::shared_ptr<api::HTTPResponse> request)
|
||||
{
|
||||
using namespace chatterino::lua::api;
|
||||
|
||||
// Prepare table
|
||||
SharedPtrUserData<UserData::Type::HTTPResponse, HTTPResponse>::create(
|
||||
L, std::move(request));
|
||||
luaL_getmetatable(L, "c2.HTTPResponse");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
return lua_gettop(L);
|
||||
}
|
||||
} // namespace chatterino::lua
|
||||
#endif
|
80
src/controllers/plugins/api/HTTPResponse.hpp
Normal file
80
src/controllers/plugins/api/HTTPResponse.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#pragma once
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "common/network/NetworkResult.hpp"
|
||||
# include "controllers/plugins/LuaUtilities.hpp"
|
||||
|
||||
# include <memory>
|
||||
extern "C" {
|
||||
# include <lua.h>
|
||||
}
|
||||
|
||||
namespace chatterino {
|
||||
class PluginController;
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::lua::api {
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
|
||||
/**
|
||||
* @lua@class HTTPResponse
|
||||
*/
|
||||
class HTTPResponse : public std::enable_shared_from_this<HTTPResponse>
|
||||
{
|
||||
NetworkResult result_;
|
||||
|
||||
public:
|
||||
HTTPResponse(NetworkResult res);
|
||||
HTTPResponse(HTTPResponse &&other) = default;
|
||||
HTTPResponse &operator=(HTTPResponse &&) = default;
|
||||
HTTPResponse &operator=(HTTPResponse &) = delete;
|
||||
HTTPResponse(const HTTPResponse &other) = delete;
|
||||
~HTTPResponse();
|
||||
|
||||
private:
|
||||
static void createMetatable(lua_State *L);
|
||||
friend class chatterino::PluginController;
|
||||
|
||||
/**
|
||||
* @brief Get the content of the top object on Lua stack, usually the first argument as an HTTPResponse
|
||||
*
|
||||
* If the object given is not a userdatum or the pointer inside that
|
||||
* userdatum doesn't point to a HTTPResponse, a lua error is thrown.
|
||||
*
|
||||
* This function always returns a non-null pointer.
|
||||
*/
|
||||
static std::shared_ptr<HTTPResponse> getOrError(lua_State *L,
|
||||
StackIdx where = -1);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Returns the data. This is not guaranteed to be encoded using any
|
||||
* particular encoding scheme. It's just the bytes the server returned.
|
||||
*
|
||||
* @exposed HTTPResponse:data
|
||||
*/
|
||||
static int data_wrap(lua_State *L);
|
||||
int data(lua_State *L);
|
||||
|
||||
/**
|
||||
* Returns the status code.
|
||||
*
|
||||
* @exposed HTTPResponse:status
|
||||
*/
|
||||
static int status_wrap(lua_State *L);
|
||||
int status(lua_State *L);
|
||||
|
||||
/**
|
||||
* A somewhat human readable description of an error if such happened
|
||||
* @exposed HTTPResponse:error
|
||||
*/
|
||||
|
||||
static int error_wrap(lua_State *L);
|
||||
int error(lua_State *L);
|
||||
};
|
||||
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
} // namespace chatterino::lua::api
|
||||
namespace chatterino::lua {
|
||||
StackIdx push(lua_State *L, std::shared_ptr<api::HTTPResponse> request);
|
||||
} // namespace chatterino::lua
|
||||
#endif
|
Loading…
Reference in a new issue