2019-12-17 14:04:55 +08: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 ;
2019-12-17 13:59:27 +08:00
using System.Collections.Generic ;
using System.Linq ;
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Sprites ;
2019-12-26 10:32:40 +08:00
using osu.Framework.Logging ;
2019-12-17 13:59:27 +08:00
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 < string > highlightWords ;
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 23:18:17 +08:00
private readonly List < PrivateMessageNotification > privateMessageNotifications = new List < PrivateMessageNotification > ( ) ;
2020-01-17 07:00:10 +08:00
2019-12-17 13:59:27 +08:00
[BackgroundDependencyLoader]
2019-12-26 10:32:40 +08:00
private void load ( OsuConfigManager config , IAPIProvider api )
2019-12-17 13:59:27 +08:00
{
notifyOnMention = config . GetBindable < bool > ( OsuSetting . ChatHighlightName ) ;
notifyOnChat = config . GetBindable < bool > ( OsuSetting . ChatMessageNotification ) ;
highlightWords = config . GetBindable < string > ( OsuSetting . HighlightWords ) ;
localUser = api . LocalUser ;
2019-12-26 10:32:40 +08:00
// Listen for new messages
2020-01-18 22:57:51 +08:00
channelManager . JoinedChannels . ItemsAdded + = joinedChannels = >
2019-12-26 10:32:40 +08:00
{
foreach ( var channel in joinedChannels )
channel . NewMessagesArrived + = channel_NewMessagesArrived ;
} ;
2020-01-18 22:57:51 +08:00
channelManager . JoinedChannels . ItemsRemoved + = leftChannels = >
2019-12-26 10:32:40 +08:00
{
foreach ( var channel in leftChannels )
channel . NewMessagesArrived - = channel_NewMessagesArrived ;
} ;
}
2020-01-17 06:15:30 +08:00
private void channel_NewMessagesArrived ( IEnumerable < Message > messages )
2019-12-26 10:32:40 +08: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 )
{
var channel = channelManager . JoinedChannels . FirstOrDefault ( c = > c . Id = = channelId ) ;
if ( channel = = null )
{
Logger . Log ( $"Couldn't resolve channel id {channelId}" , LoggingTarget . Information ) ;
return ;
}
HandleMessages ( channel , messages ) ;
2019-12-17 13:59:27 +08:00
}
public void HandleMessages ( Channel channel , IEnumerable < Message > messages )
{
2020-01-18 22:57:51 +08:00
// don't show if the ChatOverlay and the target channel is visible.
2019-12-17 13:59:27 +08:00
if ( IsActive & & channelManager . CurrentChannel . Value = = channel )
return ;
foreach ( var message in messages )
{
2020-01-17 07:00:10 +08:00
// ignore messages that already have been read
if ( message . Id < channel . LastReadId )
return ;
2019-12-17 13:59:27 +08:00
var localUsername = localUser . Value . Username ;
if ( message . Sender . Username = = localUsername )
continue ;
2020-01-17 07:00:10 +08:00
var words = getWords ( message . Content ) ;
2019-12-17 13:59:27 +08:00
void onClick ( )
{
2020-01-17 06:15:30 +08:00
notificationOverlay . Hide ( ) ;
2019-12-26 10:32:40 +08:00
chatOverlay . Show ( ) ;
2020-01-17 06:15:30 +08:00
channelManager . CurrentChannel . Value = channel ;
2019-12-17 13:59:27 +08:00
}
if ( notifyOnChat . Value & & channel . Type = = ChannelType . PM )
{
2020-01-18 22:57:51 +08:00
var existingNotification = privateMessageNotifications . FirstOrDefault ( n = > n . Username = = message . Sender . Username ) ;
2020-01-18 21:17:26 +08:00
if ( existingNotification = = null )
{
var notification = new PrivateMessageNotification ( message . Sender . Username , onClick ) ;
notificationOverlay ? . Post ( notification ) ;
privateMessageNotifications . Add ( notification ) ;
}
else
2019-12-17 13:59:27 +08:00
{
2020-01-18 21:17:26 +08:00
existingNotification . MessageCount + + ;
}
2019-12-17 13:59:27 +08:00
continue ;
}
2020-01-17 06:15:30 +08:00
2019-12-17 13:59:27 +08:00
if ( notifyOnMention . Value & & anyCaseInsensitive ( words , localUsername ) )
{
var notification = new MentionNotification ( message . Sender . Username , onClick ) ;
notificationOverlay ? . Post ( notification ) ;
continue ;
}
2020-01-17 06:15:30 +08:00
2019-12-17 13:59:27 +08:00
if ( ! string . IsNullOrWhiteSpace ( highlightWords . Value ) )
{
var matchedWord = hasCaseInsensitive ( words , getWords ( highlightWords . Value ) ) ;
if ( matchedWord ! = null )
{
var notification = new HighlightNotification ( message . Sender . Username , matchedWord , onClick ) ;
notificationOverlay ? . Post ( notification ) ;
}
}
}
}
private static string [ ] getWords ( string input ) = > input . Split ( new [ ] { ' ' } , StringSplitOptions . RemoveEmptyEntries ) ;
/// <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 ) ) ;
2020-01-17 07:00:10 +08:00
private static bool anyCaseInsensitive ( IEnumerable < string > x , string y ) = > x . Any ( x2 = > x2 . Equals ( y , StringComparison . OrdinalIgnoreCase ) ) ;
2019-12-17 13:59:27 +08:00
2020-01-17 07:00:10 +08:00
public class HighlightNotification : SimpleNotification
2019-12-17 13:59:27 +08:00
{
public HighlightNotification ( string highlighter , string word , Action onClick )
{
Icon = FontAwesome . Solid . Highlighter ;
Text = $"'{word}' was mentioned in chat by '{highlighter}'. Click to find out why!" ;
this . onClick = onClick ;
}
private readonly Action onClick ;
public override bool IsImportant = > false ;
[BackgroundDependencyLoader]
2020-01-17 06:15:30 +08:00
private void load ( OsuColour colours )
2019-12-17 13:59:27 +08:00
{
IconBackgound . Colour = colours . PurpleDark ;
Activated = delegate
{
onClick ? . Invoke ( ) ;
return true ;
} ;
}
}
2020-01-17 07:00:10 +08:00
public class PrivateMessageNotification : SimpleNotification
2019-12-17 13:59:27 +08:00
{
public PrivateMessageNotification ( string username , Action onClick )
{
Icon = FontAwesome . Solid . Envelope ;
Username = username ;
MessageCount = 1 ;
this . onClick = onClick ;
}
2020-01-18 22:57:51 +08:00
private int messageCount ;
2019-12-17 13:59:27 +08:00
public int MessageCount
{
get = > messageCount ;
2020-01-18 23:18:17 +08:00
set = > Text = ( messageCount = value ) > 1 ? $"You received {messageCount} private messages from '{Username}'. Click to read it!" : $"You received a private message from '{Username}'. Click to read it!" ;
2019-12-17 13:59:27 +08:00
}
public string Username { get ; set ; }
private readonly Action onClick ;
public override bool IsImportant = > false ;
[BackgroundDependencyLoader]
2020-01-17 07:00:10 +08:00
private void load ( OsuColour colours , MessageNotifier notifier )
2019-12-17 13:59:27 +08:00
{
IconBackgound . Colour = colours . PurpleDark ;
Activated = delegate
{
onClick ? . Invoke ( ) ;
2020-01-17 07:00:10 +08:00
if ( notifier . privateMessageNotifications . Contains ( this ) )
notifier . privateMessageNotifications . Remove ( this ) ;
2019-12-17 13:59:27 +08:00
return true ;
} ;
}
}
2020-01-17 07:00:10 +08:00
public class MentionNotification : SimpleNotification
2019-12-17 13:59:27 +08:00
{
public MentionNotification ( string username , Action onClick )
{
Icon = FontAwesome . Solid . At ;
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!" ;
this . onClick = onClick ;
}
private readonly Action onClick ;
public override bool IsImportant = > false ;
[BackgroundDependencyLoader]
2020-01-17 06:15:30 +08:00
private void load ( OsuColour colours )
2019-12-17 13:59:27 +08:00
{
IconBackgound . Colour = colours . PurpleDark ;
Activated = delegate
{
onClick ? . Invoke ( ) ;
return true ;
} ;
}
}
}
}