diff --git a/common.lua b/common.lua index 8a269b2..9a012d4 100644 --- a/common.lua +++ b/common.lua @@ -63,7 +63,7 @@ beerchat.allow_private_message = function(name, target) end beerchat.has_player_muted_player = function(name, other_name) - return not beerchat.execute_callbacks("before_check_muted", name, other_name) + return not beerchat.allow_private_message(other_name, name) end beerchat.is_player_subscribed_to_channel = function(name, channel) diff --git a/hooks.lua b/hooks.lua index 6b2001c..fca6f21 100644 --- a/hooks.lua +++ b/hooks.lua @@ -73,6 +73,13 @@ beerchat.execute_callbacks = function(trigger, ...) return true end +-- Log notification for bad event definition, use before_send_pm if possible + +beerchat.register_callback("before_check_muted", function() + minetest.log("warning", "Beerchat 'before_check_muted' event fired: this event has issues and should probably be " + .. "either deprecated or updated. Use 'before_send_pm' or beerchat.has_player_muted_player(a,b) if possible.") +end, -1) + -- TODO: harmonize callbacks -- called on every channel message diff --git a/plugin/mute.lua b/plugin/mute.lua index 59cec87..6d63071 100644 --- a/plugin/mute.lua +++ b/plugin/mute.lua @@ -1,12 +1,12 @@ -local is_muted = function(name, target) - assert(type(target) == "string", "is_muted(name, target): target should be a string, got "..type(target)..".") - local player = minetest.get_player_by_name(target) +-- Test if message from one player to another would get muted based on recipient ignore list +local is_muted = function(from, to) + assert(type(to) == "string", "is_muted(from, to): 'to' should be a string, got "..type(to)..".") + local player = minetest.get_player_by_name(to) if player then local meta = player:get_meta() - return meta:get("beerchat:muted:" .. name) == "true" + return meta:get("beerchat:muted:" .. (from or "")) == "true" end - return true end -- Events @@ -17,6 +17,7 @@ beerchat.register_callback("before_send_pm", function(name, _, target) end end) +-- Ambiguous arguments for before_check_muted event, TBD if event should be deprecated or reworked. beerchat.register_callback("before_check_muted", function(name, target) if is_muted(name, target) then return false @@ -24,7 +25,7 @@ beerchat.register_callback("before_check_muted", function(name, target) end) beerchat.register_callback("on_send_on_channel", function(name, _, target) - if is_muted(name, target) then + if is_muted(name, target) ~= false then return false end end) @@ -36,21 +37,18 @@ local mute_player = { description = "Mute a player. After muting a player, you will no longer see chat " .. "messages of this user, regardless of what channel his user sends messages to.", func = function(name, param) - if not beerchat.execute_callbacks("before_mute", name, param) then return false end if not param or param == "" then - return false, "ERROR: Invalid number of arguments. Please supply the name " - .. "of the user to mute." + return false, "ERROR: Invalid number of arguments. Please supply the name of the user to mute." end if beerchat.has_player_muted_player(name, param) then minetest.chat_send_player(name, "Player " .. param .. " is already muted.") else - minetest.get_player_by_name(name):get_meta():set_string( - "beerchat:muted:" .. param, "true") + minetest.get_player_by_name(name):get_meta():set_string("beerchat:muted:" .. param, "true") minetest.chat_send_player(name, "Muted player " .. param .. ".") end return true @@ -59,17 +57,14 @@ local mute_player = { local unmute_player = { params = "", - description = "Unmute a player. After unmuting a player, you will again see chat " - .. "messages of this user", + description = "Unmute a player. After unmuting a player, you will again see chat messages of this user", func = function(name, param) if not param or param == "" then - return false, "ERROR: Invalid number of arguments. Please supply the " - .. "name of the user to mute." + return false, "ERROR: Invalid number of arguments. Please supply the name of the user to mute." end if beerchat.has_player_muted_player(name, param) then - minetest.get_player_by_name(name):get_meta():set_string( - "beerchat:muted:" .. param, "") + minetest.get_player_by_name(name):get_meta():set_string("beerchat:muted:" .. param, "") minetest.chat_send_player(name, "Unmuted player " .. param .. ".") else minetest.chat_send_player(name, "Player " .. param .. " was not muted.") @@ -82,27 +77,26 @@ local list_muted = { params = "", description = "Show list of muted players.", func = function(name) - local player = minetest.get_player_by_name(name) - local tMeta = player:get_meta():to_table() - - if nil == tMeta or nil == tMeta.fields then return false end + local meta = player and player:get_meta():to_table() + + if meta and meta.fields then + local muted = {} + for field, _ in pairs(meta.fields) do + if field:sub(1, 15) == "beerchat:muted:" then + table.insert(muted, field:sub(16, -1)) + end + end - local sOut = "" - for sKey, _ in pairs(tMeta.fields) do - if "beerchat:muted:" == sKey:sub(1, 15) then - sOut = sOut .. sKey:sub(16, -1) .. ", " + if #muted > 0 then + minetest.chat_send_player(name, table.concat(muted, ", ")) + else + minetest.chat_send_player(name, "You have not muted any players.") end - end - if 0 == #sOut then - sOut = "You have not muted any players." - else - -- remove trailing comma and space - sOut = sOut:sub(1, -3) + return true end - minetest.chat_send_player(name, sOut) - return true + return false end } diff --git a/spec/hooks_spec.lua b/spec/hooks_spec.lua index fb4a949..fdec18d 100644 --- a/spec/hooks_spec.lua +++ b/spec/hooks_spec.lua @@ -97,12 +97,6 @@ describe("Hooks", function() describe("invalid input", function() - it("throws before_send_pm nil target", function() - assert.has_error(function() - beerchat.execute_callbacks('before_send_pm', "Sam", "test message", nil) - end) - end) - it("handles before_send_pm empty message and recipient", function() beerchat.execute_callbacks('before_send_pm', "Sam", "", "") end) diff --git a/spec/plugin_mute_spec.lua b/spec/plugin_mute_spec.lua new file mode 100644 index 0000000..f63956d --- /dev/null +++ b/spec/plugin_mute_spec.lua @@ -0,0 +1,329 @@ +require("mineunit") + +mineunit("core") +mineunit("player") +mineunit("server") + +sourcefile("init") + +describe("mute plugin", function() + + local M = function(s) return require("luassert.match").matches(s) end + local Sam = nil + local Doe = nil + local Dummy = nil + + local function create_and_join_players() + Sam = Player("Sam", { shout = 1 }) + mineunit:execute_on_joinplayer(Sam) + Doe = Player("Doe", { shout = 1 }) + mineunit:execute_on_joinplayer(Doe) + Dummy = Player("Dummy", { shout = 1 }) + mineunit:execute_on_joinplayer(Dummy) + end + + local function part_and_remove_players() + mineunit:execute_on_leaveplayer(Dummy) + Dummy = nil + mineunit:execute_on_leaveplayer(Doe) + Doe = nil + mineunit:execute_on_leaveplayer(Sam) + Sam = nil + end + + local function assert_defaults_has_player_muted_player(name1, name2) + assert.is_string(name1) + assert.is_string(name2) + assert.is_false(beerchat.has_player_muted_player(name1, name2), name2.." is muted but shouldn't be") + assert.is_false(beerchat.has_player_muted_player(name2, name1), name1.." is muted but shouldn't be") + end + + local function assert_defaults_allow_private_message(name1, name2) + assert.is_string(name1) + assert.is_string(name2) + assert.is_true(beerchat.allow_private_message(name1, name2), name2.." blocking pm without mute") + assert.is_true(beerchat.allow_private_message(name2, name1), name1.." blocking pm without mute") + end + + describe("default behavior", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("does not mute other players", function() + assert.is_false(beerchat.has_player_muted_player("Sam", "Doe"), "Doe is muted by default") + end) + + it("allows pm from other players", function() + assert.is_true(beerchat.allow_private_message("Sam", "Doe"), "Doe blocking pm by default") + end) + + it("does not mute self", function() + assert.is_false(beerchat.has_player_muted_player("Sam", "Sam"), "Sam is muted by default") + end) + + it("allows pm from self", function() + assert.is_true(beerchat.allow_private_message("Sam", "Sam"), "Sam blocking pm by default") + end) + + it("not muted from local to remote", function() + assert.is_false(beerchat.has_player_muted_player("[user]", "Sam"), "Sam is muted by default") + end) + + it("not muted from remote to local", function() + assert.is_false(beerchat.has_player_muted_player("Sam", "[user]"), "[user] is muted by default") + end) + + it("allows private message from local to remote", function() + assert.is_true(beerchat.allow_private_message("Sam", "[user]"), "[user] blocking pm by default") + end) + + it("allows private message from remote to local", function() + assert.is_true(beerchat.allow_private_message("[user]", "Sam"), "Sam blocking pm by default") + end) + + it("does not mute remote self", function() + assert.is_false(beerchat.has_player_muted_player("[user]", "[user]"), "[user] is muted by default") + end) + + it("allows pm from remote self", function() + assert.is_true(beerchat.allow_private_message("[user]", "[user]"), "[user] blocking pm by default") + end) + + end) + + describe("beerchat.has_player_muted_player", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("/mute & /unmute player", function() + -- Sam mutes Doe, Doe is muted for Sam but Sam is not muted for Doe + Sam:send_chat_message("/mute Doe") + assert.is_true(beerchat.has_player_muted_player("Sam", "Doe"), "Doe isn't muted but should be") + assert.is_false(beerchat.has_player_muted_player("Doe", "Sam"), "Sam is muted but shouldn't be") + + -- Sam unmutes Doe, Doe is not muted for Sam anymore and defaults apply + Sam:send_chat_message("/unmute Doe") + assert_defaults_has_player_muted_player("Doe", "Sam") + end) + + it("/mute & /unmute remote user", function() + -- Sam mutes [user], [user] is muted for Sam + Sam:send_chat_message("/mute [user]") + assert.is_true(beerchat.has_player_muted_player("Sam", "[user]"), "[user] isn't muted but should be") + assert.is_false(beerchat.has_player_muted_player("[user]", "Sam"), "Sam is muted but shouldn't be") + + -- Sam unmutes [user], defaults apply + Sam:send_chat_message("/unmute [user]") + assert_defaults_has_player_muted_player("Doe", "Sam") + end) + + it("/mute & /unmute both players", function() + -- Sam and Doe mutes each other, both are muted + Sam:send_chat_message("/mute Doe") + Doe:send_chat_message("/mute Sam") + assert.is_true(beerchat.has_player_muted_player("Sam", "Doe"), "Doe isn't muted but should be") + assert.is_true(beerchat.has_player_muted_player("Doe", "Sam"), "Sam isn't muted but should be") + + -- Sam and Doe unmutes each other, both are unmuted and defaults apply + Sam:send_chat_message("/unmute Doe") + Doe:send_chat_message("/unmute Sam") + assert_defaults_has_player_muted_player("Doe", "Sam") + end) + + end) + + describe("beerchat.allow_private_message", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("/mute & /unmute player", function() + -- Sam mutes Doe, Doe is muted for Sam but Sam is not muted for Doe + Sam:send_chat_message("/mute Doe") + assert.is_true(beerchat.allow_private_message("Sam", "Doe"), "Doe blocking pm without mute") + assert.is_false(beerchat.allow_private_message("Doe", "Sam"), "Sam allows pm after mute") + + -- Sam unmutes Doe, Doe is not muted for Sam anymore and defaults apply + Sam:send_chat_message("/unmute Doe") + assert_defaults_allow_private_message("Doe", "Sam") + end) + + it("/mute & /unmute remote user", function() + -- Sam mutes [user], [user] is muted for Sam + Sam:send_chat_message("/mute [user]") + assert.is_true(beerchat.allow_private_message("Sam", "[user]"), "[user] blocking pm without mute") + assert.is_false(beerchat.allow_private_message("[user]", "Sam"), "Sam allows pm after mute") + + -- Sam unmutes [user], defaults apply + Sam:send_chat_message("/unmute [user]") + assert_defaults_allow_private_message("Doe", "Sam") + end) + + it("/mute & /unmute both players", function() + -- Sam and Doe mutes each other, both are muted + Sam:send_chat_message("/mute Doe") + Doe:send_chat_message("/mute Sam") + assert.is_false(beerchat.allow_private_message("Sam", "Doe"), "Mute not blocking pm from Sam") + assert.is_false(beerchat.allow_private_message("Doe", "Sam"), "Mute not blocking pm from Doe") + + -- Sam and Doe unmutes each other, both are unmuted and defaults apply + Sam:send_chat_message("/unmute Doe") + Doe:send_chat_message("/unmute Sam") + assert_defaults_allow_private_message("Doe", "Sam") + end) + + end) + + describe("integrations", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("whisper is muted & unmuted", function() + local message = "This is a pm mute test" + -- Sam mutes Doe, Sam does not receive whispers from Doe + Sam:send_chat_message("/mute Doe") + spy.on(minetest, "chat_send_player") + Doe:send_chat_message("$ " .. message) + assert.spy(minetest.chat_send_player).called_with("Dummy", M(message)) + assert.spy(minetest.chat_send_player).not_called_with("Sam", M(message)) + + -- Sam unmutes Doe, Sam receives whispers from Doe + Sam:send_chat_message("/unmute Doe") + spy.on(minetest, "chat_send_player") + Doe:send_chat_message("$ " .. message) + assert.spy(minetest.chat_send_player).called_with("Dummy", M(message)) + assert.spy(minetest.chat_send_player).called_with("Sam", M(message)) + end) + + it("pm is muted & unmuted", function() + local message = "This is a private message mute test" + -- Sam mutes Doe, Sam does not receive private message from Doe + Sam:send_chat_message("/mute Doe") + spy.on(minetest, "chat_send_player") + Doe:send_chat_message("@Sam " .. message) + Doe:send_chat_message("@Dummy " .. message) + assert.spy(minetest.chat_send_player).not_called_with("Sam", M(message)) + assert.spy(minetest.chat_send_player).called_with("Dummy", M(message)) + + -- Sam unmutes Doe, Sam receives private message from Doe + Sam:send_chat_message("/unmute Doe") + spy.on(minetest, "chat_send_player") + Doe:send_chat_message("@Sam " .. message) + Doe:send_chat_message("@Dummy " .. message) + assert.spy(minetest.chat_send_player).called_with("Sam", M(message)) + assert.spy(minetest.chat_send_player).called_with("Dummy", M(message)) + end) + + end) + + describe("invalid input", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("allows before_send_pm nil source", function() + assert.not_has_error(function() + beerchat.execute_callbacks('before_send_pm', nil, "test message", "Sam") + end) + end) + + it("throws before_send_pm nil destination", function() + assert.has_error(function() + beerchat.execute_callbacks('before_send_pm', "Sam", "test message", nil) + end) + end) + + end) + + describe("other chatcommands", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + it("allows /list_muted", function() + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/list_muted") + assert.spy(minetest.chat_send_player).called_with("Sam", M("not muted any players")) + end) + + it("lists names with /list_muted", function() + Sam:send_chat_message("/mute Doe") + Sam:send_chat_message("/mute SomeoneWhoIsntThere") + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/list_muted") + assert.spy(minetest.chat_send_player).called_with("Sam", M("Doe.*SomeoneWhoIsntThere")) + end) + + it("repeat /mute", function() + Sam:send_chat_message("/mute Doe") + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/mute Doe") + assert.spy(minetest.chat_send_player).called_with("Sam", M("already muted")) + end) + + it("repeat /unmute", function() + Sam:send_chat_message("/unmute Doe") + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/unmute Doe") + assert.spy(minetest.chat_send_player).called_with("Sam", M("not muted")) + end) + + it("empty /mute", function() + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/mute") + assert.spy(minetest.chat_send_player).called_with("Sam", M("nvalid.*argument")) + end) + + it("empty /unmute", function() + spy.on(minetest, "chat_send_player") + Sam:send_chat_message("/unmute") + assert.spy(minetest.chat_send_player).called_with("Sam", M("nvalid.*argument")) + end) + + end) + + describe("chat", function() + before_each(create_and_join_players) + after_each(part_and_remove_players) + + local function assert_chat_delivery(player, message, test) + spy.on(minetest, "chat_send_player") + player:send_chat_message(message) + test(assert.spy(minetest.chat_send_player), message) + end + + it("not blocking by default", function() + assert_chat_delivery(Sam, "not blocking by default", function(spy, msg) + spy.called_with("Doe", M(msg)) + end) + end) + + it("not blocking others if muted self", function() + Sam:send_chat_message("/mute Sam") + assert_chat_delivery(Sam, "not blocking others if muted self", function(spy, msg) + spy.called_with("Doe", M(msg)) + end) + end) + + it("not blocking if muted by sender", function() + Sam:send_chat_message("/mute Doe") + assert_chat_delivery(Sam, "not blocking if muted by sender", function(spy, msg) + spy.called_with("Doe", M(msg)) + end) + end) + + it("not blocking if unmuted", function() + Doe:send_chat_message("/mute Sam") + Doe:send_chat_message("/unmute Sam") + assert_chat_delivery(Sam, "not blocking if unmuted", function(spy, msg) + spy.called_with("Doe", M(msg)) + end) + end) + + it("blocking if muted by recipient", function() + Doe:send_chat_message("/mute Sam") + assert_chat_delivery(Sam, "blocking if muted by recipient", function(spy, msg) + spy.not_called_with("Doe", M(msg)) + end) + end) + + end) + +end)