Solved Ignore chat

Discussion in 'Plugin Requests' started by Togoshige, Apr 2, 2017.

  1. Request: I want players to have the ability to ignore the global chat of other players.
    Reason: Some players are very toxic in chat and cause other players to leave server, I want players to have freedom of speech, but I want other players to have freedom to walk away from / turn off others speech

    Chat Commands:
    /ignore add/remove playerName/SteamID
    /ignore list

    Resources:
    BetterChat I believe used to have it -----> Better Chat | Oxide
    Ignore API ----> Ignore API for Rust | Oxide
    BetterChat Mute, but admin tool ------> BetterChat Mute | Oxide

    Previous Ignore Requests:
    Better Chat | Page 203 | Oxide

    Better Chat | Page 206 | Oxide

    Better Chat | Page 212 | Oxide

    Better Chat | Page 229 | Oxide
     
  2. Wulf

    Wulf Community Admin

  3. Oh snap, I didnt see that, thats awesome! Thanks Wulf!
    (now time to download each one and look at diffs, oh lordy LOL)
     
  4. Dear,

    I'm looking for a module to add to the latest version of BetterChat to give players the ability to ignore / unignore players and manage their list of ignored players.

    I'm very much aware that this is a frequently asked feature and it was available in one of the previous versions of BetterChat, however, it feels very counter-intuitive to use a version that is outdated. This is why I was hoping someone could come up with a module for the current BetterChat.


    With kind regards,

    iShot
     
  5. I found last Ignore capability in ----> BetterChat Version 4.2.12, Nov 3 2016

    Logic:
    A. for each player, keep a list of who they are ignoring
    B. hook into OnUserChat(IPlayer player, string message) function
    C. when playerA broadcasts chat message to every other playerB, you check if playerB has playerA ignored and skip sending message to playerB

    Question:
    How does hook priority work? If multiple plugins all use same hook, is it random on who gets the hook call first? or is there an order? (I think I remember readings its alphabetical based on plugin name, not sure)
    [DOUBLEPOST=1492281598][/DOUBLEPOST]Ignore Code stripped from BetterChat v4.2.12

    Each player is stored as a Player object,
    (on plugin Loaded() all connected players are looped through and call OnUserInit())
    (I believe newly connected players thereafter call OnUserInit() when they connect in? or is that old?)

    each Player object has an Ignored list with steam IDs of other players they are ignoring

    looks like player data is stored in a file per player?
    Player LoadOrCreate() ----> Plugin.LoadData(ref pl, $"BetterChat/Players/{id}");
    Updated() => Plugin.SaveData(this, $"BetterChat/Players/{steamID}");

    Code:
    ////// LINE 18
    public class Player
    {
        public string steamID = "0";
        public string name = "unknown";
        public List<string> ignoring = new List<string>();    internal bool Ignored(IPlayer player) => ignoring.Contains(player.Id);
        internal bool Ignored(string steamID) => ignoring.Contains(steamID);    internal void Update(IPlayer player)
        {
            if (steamID != player.Id || name != player.Name)
            {
                name = player.Name;
                steamID = player.Id;
                Updated();
            }
        }    internal static Player Find(IPlayer player) => Plugin.Players.Find((p) => p.steamID == player.Id);
        internal static Player Find(string steamID) => Plugin.Players.Find((p) => p.steamID == steamID);    internal static Player Create(IPlayer player) => Create(player.Id, player.Name);    internal static Player Create(string id, string name = "unknown")
        {
            Player pl = new Player();        pl.steamID = id;
            pl.name = name;        pl.Updated();        return pl;
        }    internal static Player LoadOrCreate(IPlayer player) => LoadOrCreate(player.Id, player.Name);    internal static Player LoadOrCreate(string id, string name = "unknown")
        {
            Player pl = null;
            Plugin.LoadData(ref pl, $"BetterChat/Players/{id}");        if (pl == null)
                return Create(id, name);        Plugin.Players.Add(pl);        return pl;
        }    internal static Player FindOrCreate(IPlayer player)
        {
            Player pl = Find(player);        if (pl == null)
                return LoadOrCreate(player);        return pl;
        }    internal static Player FindOrCreate(string id)
        {
            Player pl = Find(id);        if (pl == null)
                return LoadOrCreate(id);        return pl;
        }    public Player()
        {    }    internal void Updated() => Plugin.SaveData(this, $"BetterChat/Players/{steamID}");    public override int GetHashCode() => steamID.GetHashCode();
    }////// LINE 636
    void Loaded()
    {
        LoadMessages();
       
        foreach (IPlayer player in players.Connected)
                    OnUserInit(player);
    }////// LINE 670
    void OnUserInit(IPlayer player)
    {
        NextTick(() =>
        {
            Player pl = Player.FindOrCreate(player);
            pl.Update(player);
        });
    }
           ////// LINE 724
    void LoadMessages()
    {
        lang.RegisterMessages(new Dictionary<string, string>
        {
            {"Ignoring Player", "You are now ignoring {player}."},
            {"No Longer Ignoring Player", "You are no longer ignoring {player}."},
            {"Not Ignoring Player", "You are not ignoring {player}."},
            {"Already Ignoring Player", "You are already ignoring {player}."}
        }
    }////// LINE 831
    [Command("ignore")]
    void cmdIgnore(IPlayer player, string cmd, string[] args)
    {
        if (args.Length == 0)
        {
            SendChatMessage(player, "Syntax: /ignore <player|steamid>");
            return;
        }    IPlayer target = GetPlayer(args[0], player);    if (target == null)
            return;    Player targetPl = Player.FindOrCreate(target);    if (targetPl.Ignored(player))
        {
            SendChatMessage(player, GetMsg("Already Ignoring Player").Replace("{player}", target.Name));
            return;
        }    targetPl.ignoring.Add(player.Id);    SendChatMessage(player, GetMsg("Ignoring Player").Replace("{player}", target.Name));
    }////// LINE 858
    [Command("unignore")]
    void cmdUnignore(IPlayer player, string cmd, string[] args)
    {
        if (args.Length == 0)
        {
            SendChatMessage(player, "Syntax: /unignore <player|steamid>");
            return;
        }    IPlayer target = GetPlayer(args[0], player);    if (target == null)
            return;    Player targetPl = Player.FindOrCreate(target);    if (!targetPl.Ignored(player))
        {
            SendChatMessage(player, GetMsg("Not Ignoring Player").Replace("{player}", target.Name));
            return;
        }    targetPl.ignoring.Remove(player.Id);    SendChatMessage(player, GetMsg("No Longer Ignoring Player").Replace("{player}", target.Name));
    }////// LINE 1350
    object OnUserChat(IPlayer player, string message)
    {
        Player pl = Player.FindOrCreate(player);
       
        //  Is message invalid?
        if (message == string.Empty || message.Length < General_MinimalChars)
            return false;
       
        var hookResult = plugins.CallHook("OnBetterChat", player, message);
        if (hookResult != null)
        {
            if (hookResult is string)
                message = (string)hookResult;
            else
                return false;
        }
               
        //  Send message to all players who do not ignore the player
        foreach (IPlayer current in players.Connected)
            if (!pl.Ignored(current))
            {
    #if RUST
                BasePlayer currentPlayer = current.GetBasePlayer();            if (currentPlayer != null)
                    rust.SendChatMessage(currentPlayer, Group.Format(player, message), null, player.Id);
    #else
                current.Reply(Group.Format(player, message));
    #endif
            }
    }#region Finding Helper
    ////// LINE 1470
    IPlayer GetPlayer(string nameOrID, IPlayer player)
    {
        if (IsParseableTo<ulong>(nameOrID))
        {
            IPlayer result = covalence.Players.All.ToList().Find((p) => p.Id == nameOrID);        if (result == null)
                SendChatMessage(player, $"Could not find player with ID '{nameOrID}'");        return result;
        }    foreach (IPlayer current in players.Connected)
            if (current.Name.ToLower() == nameOrID.ToLower())
                return current;    List<IPlayer> foundPlayers =
            (from IPlayer current in players.Connected
             where current.Name.ToLower().Contains(nameOrID.ToLower())
             select current).ToList();    switch (foundPlayers.Count)
        {
            case 0:
                SendChatMessage(player, $"Could not find player with name '{nameOrID}'");
                break;        case 1:
                return foundPlayers[0];        default:
                List<string> playerNames = (from current in foundPlayers select current.Name).ToList();
                string players = ListToString(playerNames, 0, ", ");
                SendChatMessage(player, "Multiple matching players found: \n" + players);
                break;
        }    return null;
    }#endregion
    [DOUBLEPOST=1492281779][/DOUBLEPOST]Code for BetterChat Version 5.0.11 ----> OnUserChat() hook

    How to build something that coexists with the current BetterChat OnUserChat() hook?

    Code:
    object OnUserChat(IPlayer player, string message)
    {
        BetterChatMessage chatMessage = ChatGroup.FormatMessage(player, message);
        var chatMessageDict = chatMessage.ToDictionary();    foreach (Plugin plugin in plugins.GetAll())
        {
            object hookResult = plugin.CallHook("OnBetterChat", chatMessageDict);        if (hookResult is Dictionary<string, object>)
            {
                try
                {
                    chatMessageDict = (Dictionary<string, object>)hookResult;
                }
                catch (Exception e)
                {
                    PrintError($"Failed to load modified OnBetterChat data from plugin '{plugin.Title} ({plugin.Version})':{Environment.NewLine}{e}");
                    continue;
                }
            }
            else if (hookResult != null)
                return null;
        }    chatMessage = BetterChatMessage.FromDictionary(chatMessageDict);
        var output = chatMessage.GetOutput();#if RUST
        ConsoleNetwork.BroadcastToAllClients("chat.add", new object[] { player.Id, output.Chat });
    #else
        server.Broadcast(output.Chat);
    #endif    Puts(output.Console);    return true;
    }
     
  6. Private Message ----> Private Messaging for Rust | Oxide, uses Ignore API -----> Ignore API for Rust | Oxide

    From looking at Ignore API plugin,
    1. unlike the older BetterChat code above which uses List for storing ignores,
    Ignore API uses hashsets which should be instant look up time, List Contains() function does a linear search ---> List(T).Contains Method (T) (System.Collections.Generic) so this should be faster
    2. has /ignore list command
    3. stores data in one file using Dictionary object

    How Private Message plugin uses Ignore API:
    Code:
    class PrivateMessage : RustPlugin
    {
        [PluginReference]
        private Plugin Ignore;
      
        //// LINE 58, 96 - chat command functions "pm" and "r"
        var hasIgnore = Ignore?.CallHook("HasIgnored", p.userID, player.userID);
        if (hasIgnore != null && (bool) hasIgnore)
        {
            PrintMessage(player, "IgnoreYou", p.displayName);
            return;
        }
      
        //// LINE 27 - lang.RegisterMessages() in Init()
        {"IgnoreYou", "<color=red>{0} is ignoring you and cant recieve your PMs</color>"},
    So looking at this, Ideally we'd want to use the Ignore API, to manage the ignores and just hook it into a Chat plugin
     
    Last edited by a moderator: Apr 15, 2017
  7. Last edited by a moderator: Apr 15, 2017
  8. So I looked at BetterChatMute some more, I thought it was weird it had a OnBetterChat() function that wasnt called from anywhere,

    Well in v5.0.11 of BetterChat - OnUserChat() hook looks like it calls OnBetterChat() hook in all other plugins, and if it gets a non-null hookResult from on of those calls it stops the OnUserChat() function dead in its tracks

    BetterChat v5.0.11
    Code:
    object OnUserChat(IPlayer player, string message)
            {
                BetterChatMessage chatMessage = ChatGroup.FormatMessage(player, message);
                var chatMessageDict = chatMessage.ToDictionary();            foreach (Plugin plugin in plugins.GetAll())
                {
                    object hookResult = plugin.CallHook("OnBetterChat", chatMessageDict);                if (hookResult is Dictionary<string, object>)
                    {
                        try
                        {
                            chatMessageDict = (Dictionary<string, object>)hookResult;
                        }
                        catch (Exception e)
                        {
                            PrintError($"Failed to load modified OnBetterChat data from plugin '{plugin.Title} ({plugin.Version})':{Environment.NewLine}{e}");
                            continue;
                        }
                    }
                    else if (hookResult != null)
                        return null;
                }            chatMessage = BetterChatMessage.FromDictionary(chatMessageDict);
                var output = chatMessage.GetOutput();#if RUST
                ConsoleNetwork.BroadcastToAllClients("chat.add", new object[] { player.Id, output.Chat });
    #else
                server.Broadcast(output.Chat);
    #endif            Puts(output.Console);            return true;
            }
    BetterChatMute v1.0.7 - OnBetterChat()
    Code:
            private object OnBetterChat(Dictionary<string, object> messageData)
            {
                IPlayer player = (IPlayer) messageData["Player"];
                object result = HandleChat(player);            if (result is bool && !(bool)result)
                {
                    if (mutes[player.Id].Timed)
                        player.Reply(lang.GetMessage("Time Muted Player Chat", this, player.Id).Replace("{time}", FormatTime(mutes[player.Id].ExpireDate - DateTime.UtcNow)));
                    else
                        player.Reply(lang.GetMessage("Muted Player Chat", this, player.Id));
                }            return result;
            }
    So I could have an Ignore Chat plugin have a OnBetterChat() function and have it handle the messageData Dictionary that gets passed in,

    Looks like this is format of message data dictionary


    Code:
    public Dictionary<string, object> ToDictionary() => new Dictionary<string, object>
                {
                    ["Player"] = Player,
                    ["Text"] = Text,
                    ["Titles"] = Titles,
                    ["PrimaryGroup"] = PrimaryGroup,
                    ["Username"] = new Dictionary<string, object>
                    {
                        ["Color"] = Username.Color,
                        ["Size"] = Username.Size
                    },
                    ["Message"] = new Dictionary<string, object>
                    {
                        ["Color"] = Message.Color,
                        ["Size"] = Message.Size
                    },
                    ["Format"] = new Dictionary<string, object>
                    {
                        ["Chat"] = Format.Chat,
                        ["Console"] = Format.Console
                    },
                };
    and it looks like this data gets put together with the GetOutput() function, before being broadcast to everyone,

    So if my understanding is correct, I could somewhat override BetterChats OnUserChat() hook and substitute my own, but then Id have to also convert the message data to the correct output format
     
  9. I need help on how to format the message the way BetterChat formats the message but without having to duplicate/copy the releveant BetterChat code, is there some way I could hook into the BetterChat code?

    Ignore Plugin code thus far,

    Code:
    namespace Oxide.Plugins
    {
        [Info("IgnoreChat", "togoshige", 0.11)]
        class IgnoreChat : RustPlugin
        {
            [PluginReference]
            private Plugin Ignore;
            //private Plugin BetterChat;        private object OnBetterChat(Dictionary<string, object> messageData)
            {
                IPlayer playerSending = (IPlayer)messageData["Player"];
                ulong playerSendingUserID = Convert.ToUInt64(playerSending.Id);            foreach (BasePlayer playerReceiving in BasePlayer.activePlayerList)
                {
                    var hasIgnored = Ignore?.CallHook("HasIgnored", playerReceiving.userID, playerSendingUserID);
                    if (hasIgnored != null && (bool)hasIgnored)
                    {
                        // Dont send Message - playerReceiving is Ignoring playerSending
                    }
                    else
                    {
                        // Send Message
                        //rust.SendChatMessage(playerReceiving, "?", (string)messageData["Text"], playerSending.Id);
                        //SendReply(playerReceiving, (string)messageData["Text"]);
                        //BetterChatMessage formattedMessage = BetterChat?.CallHook("FormatMessage", playerSending, (string)messageData["Text"]);
                        // Note:gif figure out how to format message, move it to above foreach loop
                    }
                }            return true; // Tell BetterChat to not send its own messages
            }
        }
    }
    
     
  10. Anyone know what Im doing wrong?, the OnBetterChat function never fires, weird
     
  11. Dear Oxide Plugin Developers,
    I am stuck trying to build a separate "ignore chat" plugin to work with BetterChat :(,
    I will gladly pay money/donate for a working ignore plugin! :), any help at all would be greatly appreciated!
    (abusive/toxic global chat is one of the biggest issues facing my server, and its definitely a problem on other servers)

    ===

    My attempt to build ignore chat plugin:
    Universal - Ignore chat | Oxide

    What needs to be changed in BetterChat to add ignore:
    Better Chat | Page 233 | Oxide

    ===

    More reference/interest:
    BetterChat Mute | Page 7 | Oxide
    Better Chat | Page 232 | Oxide
    Better Chat | Page 230 | Oxide
    Better Chat | Page 229 | Oxide

    ===

    I've been told by @k1lly0u that BetterChat would need to be modified for a separate Ignore plugin to work with it

    @LaserHydra
    @Wulf @PaiN @Hirsty @Norn @Ankawi @Alphawar
    @DylanSMR @Marat 2 @azalea` @virobeast @Tyran @Kinesis @sami37 @theconezone @mcnovinho08 @Frenk92

    Does ignore functionality belong in BetterChat? or should it be separate?
    Is k1lly0u correct that BetterChat would need to be modified for separate plugin to work?
    With my code/research/pseudocode am I missing something?, am I doing something wrong?

    Thanks, Togoshige
     
  12. An ignore chat plugin looks possible, given that BetterChat, before sending a message to everyone, will loop through all other plugins and run their OnBetterChat() function, which gives other plugins the chance to change the message data, BetterChat can also return early in this loop (and not send any messages) giving another plugin the opportunity to hijack the message and send it to everyone themselves.

    The problems I ran into were:
    1. Getting the OnBetterChat() function in my own plugin to even execute/fire off, Why is it not ever executing?
    2. How to format the message once I have all the message data?

    Any help?
     
  13. blegh mustve missed this I recently altered my plugin to hook differently to onBetterChat but surely if uyou wanted this surely you would need a whitelist of some sort
     
  14. LaserHydra said he would try and add ignore functionality, but said hes busy ---> Better Chat | Page 237 | Oxide

    Im almost to the point where I may ditch BetterChat and just make an Ignore plugin that doesnt work with BetterChat.
    (But I would need to look into how to support Clan tags without BetterChat, Im okay with losing support to Player Challenge titles)

    If you know what Im doing wrong above, please let me know! :D Ill checkout your plugins and see how you got BetterChat to call your plugin.
     
  15. Ok, so I gave up waiting on BetterChat update to add /ignore functionality,

    Here is my current IgnoreChat Covalence plugin, please let me know any improvements

    Uses Ignore API as dependency ----> Ignore API for Rust | Oxide

    Code:
    using Oxide.Core;
    using Oxide.Core.Libraries.Covalence;
    using Oxide.Core.Plugins;
    using System;namespace Oxide.Plugins
    {
        [Info("IgnoreChat", "togoshige", 0.55)]
        class IgnoreChat : CovalencePlugin
        {
            [PluginReference]
            private Plugin Ignore;        object OnUserChat(IPlayer player, string message)
            {
                foreach (IPlayer playerReceiving in players.Connected)
                {
                    var hasIgnored = Ignore?.CallHook("HasIgnoredS", playerReceiving.Id, player.Id);
                    if (hasIgnored != null && (bool)hasIgnored == false)
                    {
                        playerReceiving.Message($"<color=#55aaff>{player.Name}</color>: " + message);
                    }
                }            return true;
            }
        }
    }
     
    Last edited by a moderator: May 23, 2017
  16. Wulf

    Wulf Community Admin

    What's the issue?
     
  17. Is there a built in function or way to make the message look like it came from a user?
    in the image above, those were multiple players talking in global chat, and everyone had no name and image
    [DOUBLEPOST=1495507721][/DOUBLEPOST]
    Code:
    playerReceiving.Message($"{player.Name}: " + message);
    This worked to add the username, do I have to build the color and image too? I bet Im missing something LOL
     
  18. Wulf

    Wulf Community Admin

    Yes, you'd have to format it how you'd like it to look.