Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce HTTP API for plugins #5383

Merged
merged 46 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
55b30c9
Expose NetworkRequestType as c2.HTTPMethod
Mm2PL May 6, 2024
4c289af
Add a basic HTTP API
Mm2PL May 6, 2024
5dca552
Add basicest permission for http
Mm2PL May 6, 2024
0d9eeac
Give Lua a more proper NetworkResult
Mm2PL May 6, 2024
4a24f80
Count lua::api::HTTPRequests in the debug dialog
Mm2PL May 6, 2024
b7563ae
Add set_timeout
Mm2PL May 6, 2024
0cdec11
Add set_payload
Mm2PL May 6, 2024
7f7f1a5
Add set_header
Mm2PL May 6, 2024
a0dea6e
Properly generate luals meta
Mm2PL May 6, 2024
68e0746
Add HTTP API typescript definitions
Mm2PL May 6, 2024
66adb35
Document HTTP permission
Mm2PL May 6, 2024
156ea97
Fix up command block problem
Mm2PL May 6, 2024
6b71e4a
This is an optional
Mm2PL May 6, 2024
1428695
Add documentation for HTTP api
Mm2PL May 6, 2024
d9d4126
changelog
Mm2PL May 6, 2024
1d850d1
Apply ihateprettier.patch
Mm2PL May 6, 2024
184d69f
Restrict to only http and https
Mm2PL May 7, 2024
7dd1279
Fix typo in src/controllers/plugins/api/HTTP.cpp
Mm2PL May 8, 2024
bef56e3
Rename HTTP permission to Network
Mm2PL May 8, 2024
1cca807
Replace for loop with std::ranges::any_of
Mm2PL May 8, 2024
231fe4b
Merge branch 'feature/chatterino-lua-http-api' of github.com:Chatteri…
Mm2PL May 8, 2024
fe8d561
Add NetworkRequest::header() overload for QByteArray
Mm2PL May 8, 2024
d92a74a
Use the new NetworkRequest::header() overload
Mm2PL May 8, 2024
a92f37c
Merge branch 'master' of github.com:Chatterino/chatterino2 into featu…
Mm2PL Jun 15, 2024
c4064d2
Add stack guards and eliminate the bug
Mm2PL Jun 16, 2024
d1667c3
Update permission name in documentation
Mm2PL Jun 16, 2024
aa06111
Always return after luaL_error
Mm2PL Jun 16, 2024
ad5c6a9
Fix typo in documentation
Mm2PL Jun 16, 2024
77e4a04
Add a check for HTTPRequest null pointer
Mm2PL Jun 16, 2024
be927d4
Rename HTTPResult to HTTPResponse
Mm2PL Jun 17, 2024
65ff6b7
Forget private key when we execute the request
Mm2PL Jun 17, 2024
3cb61ef
Add more comments to HTTP.hpp
Mm2PL Jun 17, 2024
5a4e64c
Rename HTTP.{c,h}pp to HTTPRequest.{c,h}pp
Mm2PL Jun 17, 2024
03248bd
fixup! Add a check for HTTPRequest null pointer
Mm2PL Jun 18, 2024
1d1ee2b
Correct StackGuards in HTTPRequest
Mm2PL Jun 18, 2024
c00d769
Make HTTPResponse a full shared resource
Mm2PL Jun 18, 2024
d820f78
Update documentation
Mm2PL Jun 18, 2024
1d198ff
Update luals meta
Mm2PL Jun 18, 2024
d635ff5
Merge branch 'master' of github.com:Chatterino/chatterino2 into featu…
Mm2PL Jun 19, 2024
2437d70
new line
Mm2PL Jun 19, 2024
bd8dff3
Update typescript type definitions
Mm2PL Jun 19, 2024
1b0e8ee
HTTPResponse.status() returns an integer or nil
Mm2PL Jun 19, 2024
deee7fc
Clean up includes
Mm2PL Jun 19, 2024
55c33d8
add missing pragma once
pajlada Jun 22, 2024
8a26d28
Merge remote-tracking branch 'origin/master' into feature/chatterino-…
pajlada Jun 22, 2024
f36a5a6
run prettier
pajlada Jun 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions docs/chatterino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ declare module c2 {
is_valid(): boolean;
}

interface ISharedResource {}

class RoomModes {
unique_chat: boolean;
subscriber_only: boolean;
Expand Down Expand Up @@ -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,
Expand Down
84 changes: 83 additions & 1 deletion docs/plugin-meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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
pajlada marked this conversation as resolved.
Show resolved Hide resolved
---@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.
Expand Down
150 changes: 150 additions & 0 deletions docs/wip-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Mm2PL marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
1 change: 0 additions & 1 deletion scripts/make_luals_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
-- Add the folder this file is in to "Lua.workspace.library".

c2 = {}

"""

repo_root = Path(__file__).parent.parent
Expand Down
4 changes: 4 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/common/network/NetworkCommon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions src/common/network/NetworkRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) &&
{
Expand Down
2 changes: 2 additions & 0 deletions src/common/network/NetworkRequest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class NetworkRequest final
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(
Expand Down
9 changes: 8 additions & 1 deletion src/controllers/plugins/LuaAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

/**
Expand Down Expand Up @@ -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 {
pajlada marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
Channel,
HTTPRequest,
HTTPResponse,
};
Type type;
bool isWeak;
};
Expand Down
Loading
Loading