osu/osu.Game/Online/Chat/MessageNotifier.cs

238 lines
8.3 KiB
C#
Raw Normal View History

2019-12-17 06:04:55 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Linq;
2020-01-19 16:36:38 +00:00
using System.Text.RegularExpressions;
2020-01-19 17:23:12 +00:00
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
2019-12-26 02:32:40 +00:00
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
{
/// <summary>
/// Component that handles creating and posting notifications for incoming messages.
/// </summary>
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<bool> notifyOnMention;
private Bindable<bool> notifyOnChat;
private Bindable<User> localUser;
/// <summary>
/// Determines if the user is able to see incoming messages.
/// </summary>
public bool IsActive => chatOverlay?.IsPresent == true;
2020-01-18 15:18:17 +00:00
private readonly List<PrivateMessageNotification> privateMessageNotifications = new List<PrivateMessageNotification>();
[BackgroundDependencyLoader]
2019-12-26 02:32:40 +00:00
private void load(OsuConfigManager config, IAPIProvider api)
{
notifyOnMention = config.GetBindable<bool>(OsuSetting.ChatHighlightName);
notifyOnChat = config.GetBindable<bool>(OsuSetting.ChatMessageNotification);
localUser = api.LocalUser;
2019-12-26 02:32:40 +00:00
// Listen for new messages
2020-01-18 14:57:51 +00:00
channelManager.JoinedChannels.ItemsAdded += joinedChannels =>
2019-12-26 02:32:40 +00:00
{
foreach (var channel in joinedChannels)
channel.NewMessagesArrived += channel_NewMessagesArrived;
};
2020-01-18 14:57:51 +00:00
channelManager.JoinedChannels.ItemsRemoved += leftChannels =>
2019-12-26 02:32:40 +00:00
{
foreach (var channel in leftChannels)
channel.NewMessagesArrived -= channel_NewMessagesArrived;
};
}
private void channel_NewMessagesArrived(IEnumerable<Message> messages)
2019-12-26 02:32:40 +00:00
{
if (messages == null || !messages.Any())
return;
HandleMessages(messages.First().ChannelId, messages);
}
/// <remarks>
/// Resolves the channel id
/// </remarks>
public void HandleMessages(long channelId, IEnumerable<Message> messages)
{
2020-01-21 23:29:12 +00:00
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == channelId);
2019-12-26 02:32:40 +00:00
if (channel == null)
{
Logger.Log($"Couldn't resolve channel id {channelId}", LoggingTarget.Information);
return;
}
HandleMessages(channel, messages);
}
public void HandleMessages(Channel channel, IEnumerable<Message> messages)
{
2020-01-21 23:13:07 +00:00
// Only send notifications, if ChatOverlay and the target channel aren't visible.
if (IsActive && channelManager.CurrentChannel.Value == channel)
return;
foreach (var message in messages.OrderByDescending(m => m.Id))
{
// ignore messages that already have been read
2020-01-21 23:28:08 +00:00
if (message.Id <= channel.LastReadId)
return;
if (message.Sender.Id == localUser.Value.Id)
continue;
2020-01-19 16:55:17 +00:00
if (checkForPMs(channel, message))
continue;
2020-01-19 16:55:17 +00:00
// change output to bool again if another "message processor" is added.
checkForMentions(channel, message, localUser.Value.Username);
2020-01-19 16:55:17 +00:00
}
}
2020-01-19 16:55:17 +00:00
private bool checkForPMs(Channel channel, Message message)
{
if (!notifyOnChat.Value || channel.Type != ChannelType.PM)
return false;
2020-01-19 16:55:17 +00:00
var existingNotification = privateMessageNotifications.FirstOrDefault(n => n.Username == message.Sender.Username);
2020-01-19 16:55:17 +00:00
if (existingNotification == null)
{
var notification = new PrivateMessageNotification(message.Sender.Username, channel, (n) => privateMessageNotifications.Remove(n));
2020-01-19 16:55:17 +00:00
notificationOverlay?.Post(notification);
privateMessageNotifications.Add(notification);
}
2020-01-19 16:55:17 +00:00
else
{
existingNotification.MessageCount++;
}
return true;
}
private void checkForMentions(Channel channel, Message message, string username)
{
if (!notifyOnMention.Value || !anyCaseInsensitive(getWords(message.Content), username))
2020-01-19 16:55:17 +00:00
return;
var notification = new MentionNotification(message.Sender.Username, channel);
notificationOverlay?.Post(notification);
}
2020-01-21 23:31:44 +00:00
private static IEnumerable<string> getWords(string input) => Regex.Matches(input, @"\w+", RegexOptions.Compiled).Select(c => c.Value);
/// <summary>
/// Finds the first matching string/word in both <paramref name="x"/> and <paramref name="y"/> (case-insensitive)
/// </summary>
private static string hasCaseInsensitive(IEnumerable<string> x, IEnumerable<string> y) => x.FirstOrDefault(x2 => anyCaseInsensitive(y, x2));
private static bool anyCaseInsensitive(IEnumerable<string> x, string y) => x.Any(x2 => x2.Equals(y, StringComparison.OrdinalIgnoreCase));
public class PrivateMessageNotification : SimpleNotification
{
public PrivateMessageNotification(string username, Channel channel, Action<PrivateMessageNotification> onRemove)
{
Icon = FontAwesome.Solid.Envelope;
Username = username;
MessageCount = 1;
2020-01-19 16:55:17 +00:00
Channel = channel;
OnRemove = onRemove;
}
2020-01-18 14:57:51 +00:00
private int messageCount;
public int MessageCount
{
get => messageCount;
2020-01-19 17:23:12 +00:00
set
{
messageCount = value;
Text = $"You received {"private message".ToQuantity(messageCount)} from '{Username}'. Click to read it!";
}
}
public string Username { get; set; }
2020-01-19 16:55:17 +00:00
public Channel Channel { get; set; }
public Action<PrivateMessageNotification> OnRemove { get; set; }
public override bool IsImportant => false;
[BackgroundDependencyLoader]
private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager)
{
IconBackgound.Colour = colours.PurpleDark;
Activated = delegate
{
2020-01-19 16:55:17 +00:00
notificationOverlay.Hide();
chatOverlay.Show();
channelManager.CurrentChannel.Value = Channel;
return true;
};
Closed += delegate
{
OnRemove.Invoke(this);
};
}
}
public class MentionNotification : SimpleNotification
{
2020-01-19 16:55:17 +00:00
public MentionNotification(string username, Channel channel)
{
Icon = FontAwesome.Solid.At;
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!";
2020-01-19 16:55:17 +00:00
Channel = channel;
}
2020-01-19 16:55:17 +00:00
public Channel Channel { get; set; }
public override bool IsImportant => false;
[BackgroundDependencyLoader]
2020-01-19 16:55:17 +00:00
private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager)
{
IconBackgound.Colour = colours.PurpleDark;
Activated = delegate
{
2020-01-19 16:55:17 +00:00
notificationOverlay.Hide();
chatOverlay.Show();
channelManager.CurrentChannel.Value = Channel;
return true;
};
}
}
}
}