Matterbridge (#68)

* doc cleanup

* working local matterbridge

* add token

* system messages

* support override config with secrets

* add some icons

* use discord username in example

* remote command api

* add some docs

* update message format

* send command back to proper channel

Co-authored-by: BuckarooBanzay <BuckarooBanzay@users.noreply.github.com>
master
Buckaroo Banzai 2022-08-23 09:09:59 +02:00 committed by GitHub
parent f193fc5b0a
commit 7844629183
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 249 additions and 252 deletions

View File

@ -15,7 +15,7 @@ Features
* Chat channels
* Whispering (`$ hello there`)
* Shortcommands (`#channel hello` `@player hello`)
* Web-integration (IRC/Discord) via the `beerchat_proxy`
* Matterbridge-integration (IRC/Discord/matrix/etc)
# Compatibility
@ -24,7 +24,7 @@ Features
# Documentation
* [Manual](./doc/manual.md)
* [Web-API](./doc/web-api.md)
* [Matterbridge](./doc/matterbridge.md)
# License

1
dev/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
docker-compose.override.yml

24
dev/docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
version: '3.7'
services:
matterbridge:
image: 42wim/matterbridge:1.25.2
volumes:
- ./matterbridge.toml:/etc/matterbridge/matterbridge.toml
ports:
- 4242:4242
minetest:
image: registry.gitlab.com/minetest/minetest/server:5.6.0
entrypoint: minetestserver --config /minetest.conf
user: root
volumes:
- "../:/root/.minetest/worlds/world/worldmods/beerchat/"
- "world_data:/root/.minetest/worlds/world"
- "./world.mt:/root/.minetest/worlds/world/world.mt"
- "./minetest.conf:/minetest.conf"
ports:
- "30000:30000/udp"
volumes:
world_data: {}

45
dev/matterbridge.toml Normal file
View File

@ -0,0 +1,45 @@
[irc]
[irc.Libera]
Server="irc.libera.chat:6667"
Nick="BeerchatTestBot"
RemoteNickFormat="[{BRIDGE}] <{NICK}> "
ColorNicks=true
ShowJoinPart=true
[discord]
[discord.Discord]
Token="<omitted>"
Server="839951944515715084"
RemoteNickFormat="[{BRIDGE}] <{NICK}> "
ShowJoinPart=true
UseUserName=true
[api.minetest]
BindAddress="0.0.0.0:4242"
Token="mytoken"
Buffer=1000
RemoteNickFormat="[{BRIDGE}] {NICK}"
ShowJoinPart=true
[[gateway]]
name="main"
enable=true
[[gateway.inout]]
account="irc.Libera"
channel="#beerchat"
[[gateway.inout]]
account = "discord.Discord"
channel="matterbridge"
[[gateway.inout]]
account="api.minetest"
channel="api"
[[gateway]]
name="main2"
enable=true
[[gateway.inout]]
account="irc.Libera"
channel="#beerchat2"
[[gateway.inout]]
account="api.minetest"
channel="api"

View File

@ -1,2 +1,6 @@
default_game = minetest_game
mg_name = v7
secure.http_mods = beerchat
beerchat.url = http://127.0.0.1:8080
beerchat.matterbridge_url = http://matterbridge:4242
beerchat.matterbridge_token = mytoken

View File

@ -1,9 +0,0 @@
#!/bin/sh
docker run --rm -it \
-u root:root \
-v $(pwd)/minetest.conf:/etc/minetest/minetest.conf \
-v $(pwd)/../:/root/.minetest/worlds/world/worldmods/beerchat \
-v beerchat_world:/root/.minetest/worlds/world/ \
--network host \
registry.gitlab.com/minetest/minetest/server:5.2.0

9
dev/world.mt Normal file
View File

@ -0,0 +1,9 @@
enable_damage = false
creative_mode = true
mod_storage_backend = sqlite3
auth_backend = sqlite3
player_backend = dummy
backend = dummy
gameid = minetest
world_name = beerchat
server_announce = false

73
doc/matterbridge.md Normal file
View File

@ -0,0 +1,73 @@
# Matterbridge relay
This manual describes how to set up a local matterbridge chat relay.
The `beerchat` mod communicates via http to the matterbridge api, for details see: https://github.com/42wim/matterbridge/wiki/Api
## Install matterbridge
Get the `matterbridge` binary from https://github.com/42wim/matterbridge or set it up with `docker`
A working example with `docker-compose` is located in the `/dev` folder of this repository
## Configure the matterbridge
This `matterbridge.toml` example connects the ingame `main` channel to libera `#beerchat` and a personal discord server:
```toml
[irc]
[irc.Libera]
Server="irc.libera.chat:6667"
Nick="BeerchatTestBot"
RemoteNickFormat="<{NICK}> "
ColorNicks=true
ShowJoinPart=true
[discord]
[discord.Discord]
Token="<omitted>"
Server="839951944515715084"
RemoteNickFormat="<{NICK}> "
ShowJoinPart=true
UseUserName=true
[api.minetest]
BindAddress="0.0.0.0:4242"
Token="mytoken"
Buffer=1000
RemoteNickFormat="{NICK}"
ShowJoinPart=true
[[gateway]]
name="main"
enable=true
[[gateway.inout]]
account="irc.Libera"
channel="#beerchat"
[[gateway.inout]]
account = "discord.Discord"
channel="matterbridge"
[[gateway.inout]]
account="api.minetest"
channel="api"
```
(tokens and passwords are omitted in this example)
For all settings see: https://github.com/42wim/matterbridge/wiki/Setting
## Configure the minetest server
Add the `beerchat` mod to the http-settings and configure the url and token:
```
secure.http_mods = beerchat
# your local matterbridge setup
beerchat.matterbridge_url = http://127.0.0.1:4242
# The token you set up in the previous `matterbridge.toml` file (under `[api.myapi]`/Token)
beerchat.matterbridge_token = mytoken
```
## Start the matterbridge and minetest server
Start the matterbridge server and minetest, there might be some errors in the minetest console if the matterbridge isn't available

View File

@ -1,67 +0,0 @@
Messages sent to/from the mod if the Web-API is configured (with `beerchat.url`)
# Web API Messages
## Ingame to Web
Messages sent via POST to `beerchat.url`
### Channel message
Sent if a player talks on a channel
```json
{
"type": "message",
"channel": "main",
"username": "somedude",
"message": "give me diamonds!"
}
```
*NOTE*: `username` and `channel` can be empty, in this
case the message is a system-message like "Player somedude joined the game"
or "Minetest started"
*NOTE2*: if the channel is `audit` then the `username` will be empty
and the `message` contanins audit-related infos, for example:
* `Player 'somedude' triggered anticheat: 'interact_while_dea' at position: 1,2,3`
### "me" message
Sent if a player uses the /me command
```json
{
"type": "me",
"channel": "main",
"username": "somedude",
"message": "is bored"
}
```
## Web to Ingame
Messages received via GET (and longpoll) to `beerchat.url`
### Normal chat message
```json
{
"username": "SomeDudeXXL",
"message": "hi, i'm on IRC",
"name": "IRC",
"channel": "main"
}
```
### Ingame command
```json
{
"username": "SomeDudeXXL",
"message": "status",
"name": "IRC",
"target_name": "minetest"
}
```

View File

@ -35,8 +35,8 @@ beerchat = {
currentPlayerChannel = {},
-- web settings
url = minetest.settings:get("beerchat.url") or "http://127.0.0.1:8080",
http = http, -- will be removed after init
url = minetest.settings:get("beerchat.matterbridge_url") or "http://127.0.0.1:4242",
token = minetest.settings:get("beerchat.matterbridge_token"),
-- mapped remote users (irc, discord)
-- data: local user => remote user
@ -54,25 +54,19 @@ dofile(MP.."/session.lua")
dofile(MP.."/message.lua")
dofile(MP.."/chatcommands.lua")
if beerchat.http then
if http and beerchat.token then
-- load web stuff
print("beerchat connects to proxy-endpoint at: " .. beerchat.url)
print("[beerchat] connecting to proxy-endpoint at: " .. beerchat.url)
dofile(MP.."/web/executor.lua")
dofile(MP.."/web/tx.lua")
dofile(MP.."/web/command.lua")
dofile(MP.."/web/register.lua")
dofile(MP.."/web/audit.lua")
dofile(MP.."/web/rx.lua")
dofile(MP.."/web/login.lua")
dofile(MP.."/web/logout.lua")
dofile(MP.."/web/common.lua")
dofile(MP.."/web/tan.lua")
dofile(MP.."/web/chatcommands.lua")
loadfile(MP.."/web/tx.lua")(http)
loadfile(MP.."/web/rx.lua")(http)
end
-- remove http ref
beerchat.http = nil
-- Load beerchat extensions
dofile(MP.."/plugin/init.lua")
print("[OK] beerchat")

View File

@ -1,2 +1,2 @@
name = beerchat
optional_depends = xban2,qos
optional_depends = qos

View File

@ -12,7 +12,7 @@ if beerchat.mod_storage:get_string("channels") == "" then
beerchat.mod_storage:set_string("channels", minetest.write_json(beerchat.channels))
end
beerchat.channels = minetest.parse_json(beerchat.mod_storage:get_string("channels"))
beerchat.channels = minetest.parse_json(beerchat.mod_storage:get_string("channels")) or {}
beerchat.channels[beerchat.main_channel_name] = {
owner = main_channel_owner,
color = main_channel_color

View File

@ -1,6 +1,6 @@
-- auth fail
minetest.register_on_auth_fail(function(name, ip)
beerchat.on_channel_message("audit", nil, "Player '" .. name ..
beerchat.on_channel_message("audit", "SYSTEM", "Player '" .. name ..
"' from ip " .. ip .. " tried to connect with wrong password")
end)
@ -8,7 +8,7 @@ end)
minetest.register_on_cheat(function(player, cheat)
local playername = player:get_player_name()
local type = cheat.type
beerchat.on_channel_message("audit", nil, "Player '" .. playername ..
beerchat.on_channel_message("audit", "SYSTEM", "Player '" .. playername ..
"' triggered anticheat: '" .. (type or "<unknown>") ..
"' at position: " .. minetest.pos_to_string(vector.floor(player:get_pos())))
end)

View File

@ -1,16 +0,0 @@
local http = beerchat.http
minetest.register_chatcommand("beerchat_proxy_shutdown", {
description = "triggers a shutdown in the beerchat-proxy app",
privs = { server = true },
func = function()
http.fetch({
url = beerchat.url .. "/shutdown",
extra_headers = { "Content-Type: application/json" },
timeout = 5,
method = "POST"
}, function()
-- ignore errors
end)
end
})

13
web/command.lua Normal file
View File

@ -0,0 +1,13 @@
-- commandname -> func() string
local commands = {}
function beerchat.register_relaycommand(name, fn)
assert(type(name) == "string")
assert(type(fn) == "function")
commands[name] = fn
end
function beerchat.get_relaycommand(name)
return commands[name]
end

View File

@ -1,42 +0,0 @@
local has_xban2_mod = minetest.get_modpath("xban2")
beerchat.executor = function(str, playername)
local mapped_playername = beerchat.get_mapped_username(playername)
if has_xban2_mod then
local xbanentry = xban.find_entry(mapped_playername)
if xbanentry and xbanentry.banned then
return false, "You are banned!"
end
end
minetest.log("action", "[beerchat] executing: '" .. str .. "' as " .. mapped_playername
.. " (mapped from '" .. playername .. "')")
local found, _, commandname, params = str:find("^([^%s]+)%s(.+)$")
if not found then
commandname = str
end
local command = minetest.chatcommands[commandname]
if not command then
return false, "Not a valid command: " .. commandname
end
if command.privs and not minetest.check_player_privs(mapped_playername, command.privs) then
return false, "Not enough privileges!"
end
local result, message
local status, err = pcall(function()
result, message = command.func(mapped_playername, (params or ""))
end)
if not status then
message = "Command crashed: " .. dump(err)
result = false
end
return result, message
end

17
web/register.lua Normal file
View File

@ -0,0 +1,17 @@
-- default relay commands
-- !players
beerchat.register_relaycommand("players", function()
if #minetest.get_connected_players() == 0 then
return "No players online"
end
local msg = "List of players: "
for _, player in ipairs(minetest.get_connected_players()) do
msg = msg .. player:get_player_name() .. ","
end
-- strip trailing comma
msg = string.sub(msg, 1, #msg-1)
return msg
end)

View File

@ -1,9 +1,8 @@
local http = beerchat.http
local http = ...
local recv_loop
local function handle_data(data)
if not data or not data.username or not data.message or not data.name then
if not data or not data.username or not data.text or not data.gateway or not data.protocol then
return
end
@ -11,52 +10,40 @@ local function handle_data(data)
return
end
local name = data.username .. "@" .. data.name
if data.channel and data.channel ~= "" then
-- channel message
beerchat.send_on_channel(name, data.channel, data.message)
elseif data.target_name == "minetest" then
-- direct message
local success, msg = beerchat.executor(data.message, name)
if not success and not msg then
-- failed without command
msg = "Command failed!"
if data.event == "user_action" then
-- "/me" message, TODO: use format and helper in "plugin/me.lua"
beerchat.send_on_channel(data.username, data.gateway, data.text)
elseif data.event == "join_leave" then
-- join/leave message, from irc for example
beerchat.send_on_channel(data.username, data.gateway, data.text)
else
-- regular text
if string.sub(data.text, 1, 1) == "!" then
-- user command
local cmd_name = string.sub(data.text, 2)
local fn = beerchat.get_relaycommand(cmd_name)
if not fn then
beerchat.on_channel_message(data.gateway, "SYSTEM", "command not found: '" .. cmd_name .. "'")
else
beerchat.on_channel_message(data.gateway, "SYSTEM", fn(data.username, data.text, data.protocol))
end
if not msg then
-- no result, ignore
return
end
local tx_data = {
target_name = data.name,
target_username = data.username,
message = msg
}
local json = minetest.write_json(tx_data)
http.fetch({
url = beerchat.url,
extra_headers = { "Content-Type: application/json" },
timeout = 5,
post_data = json
}, function()
-- ignore errors
end)
else
-- regular user message
beerchat.send_on_channel(data.username, data.gateway, data.text)
end
end
end
recv_loop = function()
http.fetch({
url = beerchat.url,
url = beerchat.url .. "/api/messages",
extra_headers = {
"Authorization: Bearer " .. beerchat.token
},
timeout = 30,
}, function(res)
if res.succeeded and res.code == 200 and res.data then
if res.succeeded and res.code == 200 and res.data and res.data ~= "" then
local data = minetest.parse_json(res.data)
if not data then
minetest.log("error", "[beerchat] content parsing error: " .. dump(res.data))
@ -69,9 +56,6 @@ recv_loop = function()
for _, item in ipairs(data) do
handle_data(item)
end
else
-- single item received
handle_data(data)
end
minetest.after(0.5, recv_loop)

View File

@ -1,16 +0,0 @@
beerchat.tan_map = {}
minetest.register_chatcommand("remote_tan", {
description = "creates a temporary access number for the /login command",
func = function(name)
local tan = "" .. math.random(1000, 9999)
beerchat.tan_map[name] = tan
return true, "Your tan is " .. tan .. ", it will expire upon leaving the game"
end
})
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
beerchat.tan_map[name] = nil
end)

View File

@ -1,23 +1,22 @@
local http = beerchat.http
local http = ...
-- normal message in chat channel
beerchat.on_channel_message = function(channel, playername, message)
local data = {
channel = channel,
username = playername,
message = message,
type = "message"
}
local json = minetest.write_json(data)
beerchat.on_channel_message = function(channel, playername, message, event)
http.fetch({
url = beerchat.url,
extra_headers = { "Content-Type: application/json" },
url = beerchat.url .. "/api/message",
method = "POST",
extra_headers = {
"Content-Type: application/json",
"Authorization: Bearer " .. beerchat.token
},
timeout = 5,
post_data = json
data = minetest.write_json({
gateway = channel,
username = playername,
text = message,
event = event
})
}, function()
-- ignore errors
end)
@ -26,23 +25,7 @@ end
-- /me message in chat channel
beerchat.on_me_message = function(channel, playername, message)
local data = {
channel = channel,
username = playername,
message = message,
type = "me"
}
local json = minetest.write_json(data)
http.fetch({
url = beerchat.url,
extra_headers = { "Content-Type: application/json" },
timeout = 5,
post_data = json
}, function()
-- ignore errors
end)
beerchat.on_channel_message(channel, playername, message, "user_action")
end
-- map to players -> new == true
@ -59,29 +42,29 @@ end)
minetest.register_on_joinplayer(function(player)
local playername = player:get_player_name()
local msg = "Player " .. playername .. " joined the game"
local msg = "Player " .. playername .. " joined the game"
if new_player_map[playername] then
msg = msg .. " (new player)"
-- clear new-player flag
new_player_map[playername] = nil
end
beerchat.on_channel_message(nil, nil, msg)
beerchat.on_channel_message("main", "SYSTEM", msg)
end)
-- leave player message
minetest.register_on_leaveplayer(function(player, timed_out)
local msg = player:get_player_name() .. " left the game"
local msg = "❰ Player " .. player:get_player_name() .. " left the game"
if timed_out then
msg = msg .. " (timed out)"
end
beerchat.on_channel_message(nil, nil, msg)
beerchat.on_channel_message("main", "SYSTEM", msg)
end)
-- initial message on start
beerchat.on_channel_message(nil, nil, "Minetest started!")
beerchat.on_channel_message("main", "SYSTEM", "Minetest started!")
minetest.register_on_shutdown(function()
beerchat.on_channel_message(nil, nil, "Minetest shutting down!")
beerchat.on_channel_message("main", "SYSTEM", "Minetest shutting down!")
end)