Co-authored-by: Mm2PL <mm2pl+gh@kotmisia.pl>
7.2 KiB
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.
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 compliant. The general idea of info.json
will not change however the exact contents probably will, for example with
permission system ideas.
Example file:
{
"$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
Plugins with Typescript
If you prefer, you may use TypescriptToLua
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.
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:
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:
function cmdWords(ctx)
-- ctx contains:
-- words - table of words supplied to the command including the trigger
-- channel_name - name of the channel the command is being run in
c2.system_msg(ctx.channel_name, "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).
register_callback("CompletionRequested", handler)
Registers a callback (handler
) to process completions. The callback gets the following parameters:
query
: The queried word.full_text_content
: The whole input.cursor_position
: The position of the cursor in the input.is_first_word
: Flag whetherquery
is the first word in the input.
Example:
Input | query |
full_text_content |
cursor_position |
is_first_word |
---|---|---|---|---|
foo│ |
foo |
foo |
3 | true |
fo│o |
fo |
foo |
2 | true |
foo bar│ |
bar |
foo bar |
7 | false |
foo │bar |
foo |
foo bar |
4 | false |
function string.startswith(s, other)
return string.sub(s, 1, string.len(other)) == other
end
c2.register_callback(
"CompletionRequested",
function(query, full_text_content, cursor_position, is_first_word)
if ("!join"):startswith(query) then
---@type CompletionList
return { hide_others = true, values = { "!join" } }
end
---@type CompletionList
return { hide_others = false, values = {} }
end
)
send_msg(channel, text)
Sends a message to channel
with the specified text. Also executes commands.
Example:
function cmdShout(ctx)
table.remove(ctx.words, 1)
local output = table.concat(ctx.words, " ")
c2.send_msg(ctx.channel_name, 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:
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
.
require(modname)
This is Lua's require()
function.
However, the searcher and load configuration is notably different from the default:
- Lua's built-in dynamic library searcher is removed,
package.path
is not used, in its place are two searchers,- when
require()
is used, first a file relative to the currently executing file will be checked, then a file relative to the plugin directory, - binary chunks are never loaded
As in normal Lua, dots are converted to the path separators ('/'
on Linux and Mac, '\'
on Windows).
Example:
require("stuff") -- executes Plugins/name/stuff.lua or $(dirname $CURR_FILE)/stuff.lua
require("dir.name") -- executes Plugins/name/dir/name.lua or $(dirname $CURR_FILE)/dir/name.lua
require("binary") -- tried to load Plugins/name/binary.lua and errors because binary is not a text file
print(Args...)
The print
global function is equivalent to calling c2.log(c2.LogLevel.Debug, Args...)