// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Users; namespace osu.Game.Online.Chat { /// /// Component that handles creating and posting notifications for incoming messages. /// public class MessageNotifier : Component { [Resolved(CanBeNull = true)] private NotificationOverlay notificationOverlay { get; set; } [Resolved(CanBeNull = true)] private ChatOverlay chatOverlay { get; set; } [Resolved(CanBeNull = true)] private ChannelManager channelManager { get; set; } private Bindable notifyOnMention; private Bindable notifyOnPM; private readonly IBindable localUser = new Bindable(); private readonly IBindableList joinedChannels = new BindableList(); [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { notifyOnMention = config.GetBindable(OsuSetting.ChatHighlightName); notifyOnPM = config.GetBindable(OsuSetting.ChatMessageNotification); localUser.BindTo(api.LocalUser); joinedChannels.BindCollectionChanged(channelsChanged); joinedChannels.BindTo(channelManager.JoinedChannels); } private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (var channel in e.NewItems.Cast()) channel.NewMessagesArrived += newMessagesArrived; break; case NotifyCollectionChangedAction.Remove: foreach (var channel in e.OldItems.Cast()) channel.NewMessagesArrived -= newMessagesArrived; break; } } private void newMessagesArrived(IEnumerable messages) { if (!messages.Any()) return; var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId); if (channel == null) { Logger.Log($"Couldn't resolve channel id {messages.First().ChannelId}", LoggingTarget.Information); return; } // Only send notifications, if ChatOverlay and the target channel aren't visible. if (chatOverlay?.IsPresent == true && channelManager.CurrentChannel.Value == channel) return; foreach (var message in messages.OrderByDescending(m => m.Id)) { // ignore messages that already have been read if (message.Id <= channel.LastReadId) return; if (message.Sender.Id == localUser.Value.Id) continue; // check for private messages first, // to avoid both posting two notifications about the same message if (checkForPMs(channel, message)) continue; _ = checkForMentions(channel, message, localUser.Value.Username); } } /// /// Checks whether the user enabled private message notifications and whether specified is a direct message. /// /// The channel associated to the /// The message to be checked private bool checkForPMs(Channel channel, Message message) { if (!notifyOnPM.Value || channel.Type != ChannelType.PM) return false; if (channel.Id != message.ChannelId) throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); var notification = new PrivateMessageNotification(message.Sender.Username, channel); notificationOverlay?.Post(notification); return true; } /// /// Checks whether the user enabled mention notifications and whether specified mentions the provided . /// /// The channel associated to the /// The message to be checked /// The username that will be checked for private bool checkForMentions(Channel channel, Message message, string username) { if (!notifyOnMention.Value || !isMentioning(message.Content, username)) return false; if (channel.Id != message.ChannelId) throw new ArgumentException("The provided channel doesn't match with the channel id provided by the message parameter.", nameof(channel)); var notification = new MentionNotification(message.Sender.Username, channel); notificationOverlay?.Post(notification); return true; } /// /// Checks if contains , if not, retries making spaces into underscores. /// /// If the mentions the private static bool isMentioning(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); public class OpenChannelNotification : SimpleNotification { public OpenChannelNotification(Channel channel) { this.channel = channel; } private readonly Channel channel; public override bool IsImportant => false; [BackgroundDependencyLoader] private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) { IconBackgound.Colour = colours.PurpleDark; Activated = delegate { notificationOverlay.Hide(); chatOverlay.Show(); channelManager.CurrentChannel.Value = channel; return true; }; } } public class PrivateMessageNotification : OpenChannelNotification { public PrivateMessageNotification(string username, Channel channel) : base(channel) { Icon = FontAwesome.Solid.Envelope; Text = $"You received a private message from '{username}'. Click to read it!"; } } public class MentionNotification : OpenChannelNotification { public MentionNotification(string username, Channel channel) : base(channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; } } } }