Skip to content

Commit

Permalink
Add basic lua scripting capabilities (#4341)
Browse files Browse the repository at this point in the history
The scripting capabilities is locked behind a cmake flag, and is not enabled by default.

Co-authored-by: nerix <[email protected]>
Co-authored-by: pajlada <[email protected]>
  • Loading branch information
3 people authored Apr 2, 2023
1 parent 5836073 commit 5ba8098
Show file tree
Hide file tree
Showing 37 changed files with 2,087 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ CheckOptions:
value: camelBack
- key: readability-implicit-bool-conversion.AllowPointerConditions
value: true

# Lua state
- key: readability-identifier-naming.LocalPointerIgnoredRegexp
value: ^L$
15 changes: 13 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
qt-version: [5.15.2, 5.12.12]
pch: [true]
force-lto: [false]
plugins: [false]
skip_artifact: ["no"]
crashpad: [true]
include:
Expand All @@ -45,19 +46,21 @@ jobs:
qt-version: 6.2.4
pch: false
force-lto: false
# Test for disabling Precompiled Headers & enabling link-time optimization
# Test for disabling Precompiled Headers & enabling link-time optimization and plugins
- os: ubuntu-22.04
qt-version: 5.15.2
pch: false
force-lto: true
skip_artifact: "yes"
plugins: true
# Test for disabling crashpad on Windows
- os: windows-latest
qt-version: 5.15.2
pch: false
force-lto: true
skip_artifact: "yes"
crashpad: false

fail-fast: false

steps:
Expand All @@ -67,6 +70,12 @@ jobs:
echo "C2_ENABLE_LTO=ON" >> "$GITHUB_ENV"
shell: bash

- name: Enable plugin support
if: matrix.plugins == true
run: |
echo "C2_PLUGINS=ON" >> "$GITHUB_ENV"
shell: bash

- name: Set Crashpad
if: matrix.crashpad == true
run: |
Expand Down Expand Up @@ -160,6 +169,7 @@ jobs:
-DUSE_PRECOMPILED_HEADERS=${{ matrix.pch }} `
-DBUILD_WITH_CRASHPAD="$Env:C2_ENABLE_CRASHPAD" `
-DCHATTERINO_LTO="$Env:C2_ENABLE_LTO" `
-DCHATTERINO_PLUGINS="$Env:C2_PLUGINS" `
-DBUILD_WITH_QT6="$Env:C2_BUILD_WITH_QT6" `
..
set cl=/MP
Expand Down Expand Up @@ -246,6 +256,7 @@ jobs:
-DUSE_PRECOMPILED_HEADERS=${{ matrix.pch }} \
-DCMAKE_EXPORT_COMPILE_COMMANDS=On \
-DCHATTERINO_LTO="$C2_ENABLE_LTO" \
-DCHATTERINO_PLUGINS="$C2_PLUGINS" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
..
make -j"$(nproc)"
Expand Down Expand Up @@ -310,6 +321,7 @@ jobs:
-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl \
-DUSE_PRECOMPILED_HEADERS=${{ matrix.pch }} \
-DCHATTERINO_LTO="$C2_ENABLE_LTO" \
-DCHATTERINO_PLUGINS="$C2_PLUGINS" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
..
make -j"$(sysctl -n hw.logicalcpu)"
Expand All @@ -331,7 +343,6 @@ jobs:
with:
name: chatterino-osx-${{ matrix.qt-version }}.dmg
path: build/chatterino-osx.dmg

create-release:
needs: build
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
[submodule "lib/miniaudio"]
path = lib/miniaudio
url = https://github.com/mackron/miniaudio.git
[submodule "lib/lua/src"]
path = lib/lua/src
url = https://github.com/lua/lua
[submodule "lib/crashpad"]
path = lib/crashpad
url = https://github.com/getsentry/crashpad
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Dev: Only log debug messages when NDEBUG is not defined. (#4442)
- Dev: Cleaned up theme related code. (#4450)
- Dev: Ensure tests have default-initialized settings. (#4498)
- Dev: Add scripting capabilities with Lua (#4341)
- Dev: Conan 2.0 is now used instead of Conan 1.0. (#4417)

## 2.4.2
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ option(CHATTERINO_GENERATE_COVERAGE "Generate coverage files" OFF)
option(BUILD_TRANSLATIONS "" OFF)
option(BUILD_SHARED_LIBS "" OFF)
option(CHATTERINO_LTO "Enable LTO for all targets" OFF)
option(CHATTERINO_PLUGINS "Enable EXPERIMENTAL plugin support in Chatterino" OFF)

if(CHATTERINO_LTO)
include(CheckIPOSupported)
Expand Down Expand Up @@ -156,6 +157,11 @@ else()
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/settings" EXCLUDE_FROM_ALL)
endif()

if (CHATTERINO_PLUGINS)
set(LUA_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/lib/lua/src")
add_subdirectory(lib/lua)
endif()

if (BUILD_WITH_CRASHPAD)
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/crashpad" EXCLUDE_FROM_ALL)
endif()
Expand Down
22 changes: 22 additions & 0 deletions docs/chatterino.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** @noSelfInFile */

declare module c2 {
enum LogLevel {
Debug,
Info,
Warning,
Critical,
}
class CommandContext {
words: String[];
channel_name: String;
}

function log(level: LogLevel, ...data: any[]): void;
function register_command(
name: String,
handler: (ctx: CommandContext) => void
): boolean;
function send_msg(channel: String, text: String): boolean;
function system_msg(channel: String, text: String): boolean;
}
49 changes: 49 additions & 0 deletions docs/plugin-info.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "https://raw.githubusercontent.com/Chatterino/chatterino2/master/docs/plugin-info.schema.json",
"title": "Plugin info",
"description": "Describes a Chatterino2 plugin (draft)",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Plugin name shown to the user."
},
"description": {
"type": "string",
"description": "Plugin description shown to the user."
},
"authors": {
"type": "array",
"description": "An array of authors of this Plugin.",
"items": {
"type": "string"
}
},
"homepage": {
"type": "string",
"description": "Optional URL to your Plugin's homepage. This could be your GitHub repo for example."
},
"tags": {
"description": "Something that could in the future be used to find your plugin.",
"type": "array",
"items": {
"type": "string",
"examples": ["moderation", "utility", "commands"]
},
"uniqueItems": true
},
"version": {
"type": "string",
"description": "Semver version string, for more info see https://semver.org.",
"examples": ["0.0.1", "1.0.0-rc.1"]
},
"license": {
"type": "string",
"description": "A small description of your license.",
"examples": ["MIT", "GPL-2.0-or-later"]
}
},
"required": ["name", "description", "authors", "version", "license"]
}
177 changes: 177 additions & 0 deletions docs/wip-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Plugins

If Chatterino is compiled with the `CHATTERINO_PLUGINS` CMake option, it can
load and execute Lua files. Note that while there are attempts at making this
decently safe, we cannot guarantee safety.

## Plugin structure

Chatterino searches for plugins in the `Plugins` directory in the app data, right next to `Settings` and `Logs`.

Each plugin should have its own directory.

```
Chatterino Plugins dir/
└── plugin_name/
├── init.lua
└── info.json
```

`init.lua` will be the file loaded when the plugin is enabled. You may load other files using [`import` global function](#importfilename=).

`info.json` contains metadata about the plugin, like its name, description,
authors, homepage link, tags, version, license name. The version field **must**
be [semver 2.0](https://semver.org/) compliant. The general idea of `info.json`
will not change however the exact contents probably will, for example with
permission system ideas.
Example file:

```json
{
"$schema": "https://raw.githubusercontent.com/Chatterino/chatterino2/master/docs/plugin-info.schema.json",
"name": "Test plugin",
"description": "This plugin is for testing stuff.",
"authors": "Mm2PL",
"homepage": "https://github.com/Chatterino/Chatterino2",
"tags": ["test"],
"version": "0.0.0",
"license": "MIT"
}
```

An example plugin is available at [https://github.com/Mm2PL/Chatterino-test-plugin](https://github.com/Mm2PL/Chatterino-test-plugin)

## Plugins with Typescript

If you prefer, you may use [TypescriptToLua](https://typescripttolua.github.io)
to typecheck your plugins. There is a `chatterino.d.ts` file describing the API
in this directory. However this has several drawbacks like harder debugging at
runtime.

## API

The following parts of the Lua standard library are loaded:

- `_G` (most globals)
- `table`
- `string`
- `math`
- `utf8`

The official manual for them is available [here](https://www.lua.org/manual/5.4/manual.html#6).

### Chatterino API

All Chatterino functions are exposed in a global table called `c2`. The following members are available:

#### `log(level, args...)`

Writes a message to the Chatterino log. The `level` argument should be a
`LogLevel` member. All `args` should be convertible to a string with
`tostring()`.

Example:

```lua
c2.log(c2.LogLevel.Warning, "Hello, this should show up in the Chatterino log by default")

c2.log(c2.LogLevel.Debug, "Hello world")
-- Equivalent to doing qCDebug(chatterinoLua) << "[pluginDirectory:Plugin Name]" << "Hello, world"; from C++
```

#### `LogLevel` enum

This table describes log levels available to Lua Plugins. The values behind the names may change, do not count on them. It has the following keys:

- `Debug`
- `Info`
- `Warning`
- `Critical`

#### `register_command(name, handler)`

Registers a new command called `name` which when executed will call `handler`.
Returns `true` if everything went ok, `false` if there already exists another
command with this name.

Example:

```lua
function cmdWords(ctx)
-- ctx contains:
-- words - table of words supplied to the command including the trigger
-- channelName - name of the channel the command is being run in
c2.system_msg(ctx.channelName, "Words are: " .. table.concat(ctx.words, " "))
end

c2.register_command("/words", cmdWords)
```

Limitations/known issues:

- Commands registered in functions, not in the global scope might not show up in the settings UI,
rebuilding the window content caused by reloading another plugin will solve this.
- Spaces in command names aren't handled very well (https://github.com/Chatterino/chatterino2/issues/1517).

#### `send_msg(channel, text)`

Sends a message to `channel` with the specified text. Also executes commands.

Example:

```lua
function cmdShout(ctx)
table.remove(ctx.words, 1)
local output = table.concat(ctx.words, " ")
c2.send_msg(ctx.channelName, string.upper(output))
end
c2.register_command("/shout", cmdShout)
```

Limitations/Known issues:

- It is possible to trigger your own Lua command with this causing a potentially infinite loop.

#### `system_msg(channel, text)`

Creates a system message and adds it to the twitch channel specified by
`channel`. Returns `true` if everything went ok, `false` otherwise. It will
throw an error if the number of arguments received doesn't match what it
expects.

Example:

```lua
local ok = c2.system_msg("pajlada", "test")
if (not ok)
-- channel not found
end
```

### Changed globals

#### `load(chunk [, chunkname [, mode [, env]]])`

This function is only available if Chatterino is compiled in debug mode. It is meant for debugging with little exception.
This function behaves really similarity to Lua's `load`, however it does not allow for bytecode to be executed.
It achieves this by forcing all inputs to be encoded with `UTF-8`.

See [official documentation](https://www.lua.org/manual/5.4/manual.html#pdf-load)

#### `import(filename)`

This function mimics Lua's `dofile` however relative paths are relative to your plugin's directory.
You are restricted to loading files in your plugin's directory. You cannot load files with bytecode inside.

Example:

```lua
import("stuff.lua") -- executes Plugins/name/stuff.lua
import("./stuff.lua") -- executes Plugins/name/stuff.lua
import("../stuff.lua") -- tries to load Plugins/stuff.lua and errors
import("luac.out") -- tried to load Plugins/name/luac.out and errors because it contains non-utf8 data
```

#### `print(Args...)`

The `print` global function is equivalent to calling `c2.log(c2.LogLevel.Debug, Args...)`
Loading

0 comments on commit 5ba8098

Please sign in to comment.