Skip to content

Commit

Permalink
Channel based bans (#75)
Browse files Browse the repository at this point in the history
* Channel based bans

Write storage, restructure, add info messages

Fix all bugs

Fix all documentation

Add crash prevention feature

dos2unix

* Basic tests for channel ban

Load auth for tests

Disable invalid player name smoke test
  • Loading branch information
S-S-X authored Feb 21, 2023
1 parent 9b4cd67 commit 03500f9
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 0 deletions.
136 changes: 136 additions & 0 deletions plugin/ban.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
--luacheck: no_unused_args

--
-- Data storage and helpers to persist channel bans through server restarts
--

local channels = setmetatable(minetest.deserialize(beerchat.mod_storage:get("ban.channels")) or {}, {
__index = function(p,k)
return setmetatable({}, {
__newindex = function(t, n, v)
if not rawget(p,k) then rawset(p,k,{}) end
p[k][n] = v
end
})
end
})

local function write_storage()
local data = minetest.serialize(channels)
beerchat.mod_storage:set_string("ban.channels", data)
end

--
-- Register basic API for channel bans
--

beerchat.ban = {
priv = minetest.settings:get("beerchat.ban.priv") or "ban"
}

beerchat.ban.is_player_banned = function(channel, player_name)
return not not channels[channel][player_name]
end

beerchat.ban.ban_player = function(channel, player_name)
-- Can store timestamp or table for temporary ban or extended ban configuration
channels[channel][player_name] = true
end

beerchat.ban.unban_player = function(channel, player_name)
channels[channel][player_name] = nil
end

--
-- Register beerchat event handlers for channel bans
--

beerchat.register_callback('before_send', function(name, message, channel)
if beerchat.ban.is_player_banned(channel, name) then
return false, "Sorry but you are banned on #"..channel..", you are not allowed to send messages there."
end
end)

beerchat.register_callback('before_send_me', function(name, message, channel)
if beerchat.ban.is_player_banned(channel, name) then
return false, "Sorry but you are banned on #"..channel..", you are not allowed to send messages there."
end
end)

--
-- Register chat commands for channel bans
--

local function channel_ban(name, param)
if not param or param == "" then
return false, "ERROR: Invalid number of arguments. Please supply the player name(s)."
end

local channel = beerchat.get_player_channel(name)
if not channel then
beerchat.fix_player_channel(name, true)
return true
end

local player_names = string.gmatch(param, "[^%s,]+")
for player_name in player_names do
if not beerchat.ban.is_player_banned(channel, player_name) then
if not minetest.player_exists(player_name) then
-- Inform that player does not exist in database but add record anyway to allow banning external users
minetest.chat_send_player(name, "WARNING: " .. player_name .. " is external user or does not exist.")
end
beerchat.ban.ban_player(channel, player_name)
else
minetest.chat_send_player(name, "Channel ban: " .. player_name .. " is already banned on #"..channel)
end
end
write_storage()
return true
end

minetest.register_chatcommand("channel_ban", {
params = "<Player Name> [<Player Name> ...]",
description = ("Ban players <Player Name> on current channel. Requires %s privilege."):format(beerchat.ban.priv),
privs = { [beerchat.ban.priv] = true },
func = channel_ban
})

local function channel_unban(name, param)
local channel = beerchat.get_player_channel(name)
if not channel then
beerchat.fix_player_channel(name, true)
return true
end

if not param or param == "" then
-- List banned names
local names = {}
for player_name,_ in pairs(channels[channel]) do
table.insert(names, player_name)
end
if #names > 0 then
minetest.chat_send_player(name, "Players banned on #"..channel..": " .. table.concat(names, ", "))
else
minetest.chat_send_player(name, "Nobody is banned on #"..channel)
end
else
-- Unban players
local player_names = string.gmatch(param, "[^%s,]+")
for player_name in player_names do
if beerchat.ban.is_player_banned(channel, player_name) then
beerchat.ban.unban_player(channel, player_name)
else
minetest.chat_send_player(name, "WARNING: " .. player_name .. " is not banned on #"..channel)
end
end
end
write_storage()
return true
end

minetest.register_chatcommand("channel_unban", {
params = "[<Player Name> [<Player Name> ...]]",
description = ("Unban players <Player Name> on current channel. Requires %s privilege."):format(beerchat.ban.priv),
privs = { [beerchat.ban.priv] = true },
func = channel_unban
})
3 changes: 3 additions & 0 deletions plugin/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ load_plugin("whisper", true)
-- Adds "/chat_jail playername" and "/chat_unjail playername" commands
load_plugin("jail", false)

-- Adds "/channel_ban [#channel] playername" and "/channel_unban [#channel] playername" commands
load_plugin("ban", false)

-- Allow muting remote users
load_plugin("remote_mute", false)

Expand Down
2 changes: 2 additions & 0 deletions spec/fixtures/minetest.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ beerchat.enable_cleaner = true

beerchat.enable_announce = true

beerchat.enable_ban = true

beerchat.moderator_channel_name = mod

# Web relay
Expand Down
92 changes: 92 additions & 0 deletions spec/plugin_ban_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require("mineunit")

mineunit("core")
mineunit("player")
mineunit("server")
mineunit("auth")

sourcefile("init")

-- Players, initialized in test environment setup functions
local SX, XX, XX_channel, XX_name
local XX_count = 0

local function do_setup()
SX = Player("SX", { shout = 1, ban = 1 })
mineunit:execute_on_joinplayer(SX)
assert.equals("main", beerchat.get_player_channel("SX"))
end

local function do_teardown()
mineunit:execute_on_leaveplayer(SX)
end

local function do_before_each()
XX_count = XX_count + 1
XX_name = "XX"..XX_count
XX = Player(XX_name, { shout = 1 })
mineunit:execute_on_joinplayer(XX)
XX_channel = beerchat.get_player_channel(XX_name)
end

local function do_after_each()
mineunit:execute_on_leaveplayer(XX)
XX = nil
XX_name = nil
XX_channel = nil
end

describe("channel_ban command", function()

setup(do_setup)
teardown(do_teardown)
before_each(do_before_each)
after_each(do_after_each)

it("records channel ban", function()
assert.is_false(beerchat.ban.is_player_banned(XX_channel, XX_name))
SX:send_chat_message("/channel_ban "..XX_name)
assert.is_true(beerchat.ban.is_player_banned(XX_channel, XX_name))
end)

it("handles players that are already banned", function()
SX:send_chat_message("/channel_ban "..XX_name)
SX:send_chat_message("/channel_ban "..XX_name)
assert.is_true(beerchat.ban.is_player_banned(XX_channel, XX_name))
end)

it("handles invalid player name", function()
pending("Mineunit auth handler raises exception for invalid names, this test wont work")
SX:send_chat_message("/channel_ban ***")
end)

it("handles empty player name", function()
SX:send_chat_message("/channel_ban")
assert.equals("main", beerchat.get_player_channel("SX"))
assert.is_false(beerchat.ban.is_player_banned("main", "SX"))
end)

end)

describe("channel_unban command", function()

setup(do_setup)
teardown(do_teardown)
before_each(do_before_each)
after_each(do_after_each)

it("handles invalid player name", function()
SX:send_chat_message("/channel_unban ***")
end)

it("handles empty player name", function()
SX:send_chat_message("/channel_unban")
end)

it("handles players that are not banned", function()
SX:send_chat_message("/channel_unban "..XX_name)
assert.equals(XX_channel, beerchat.get_player_channel(XX_name))
assert.is_false(beerchat.ban.is_player_banned(XX_channel, XX_name))
end)

end)

0 comments on commit 03500f9

Please sign in to comment.