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 ;
2019-12-17 05:59:27 +00:00
using System.Collections.Generic ;
2021-05-26 06:59:29 +00:00
using System.Collections.Specialized ;
2019-12-17 05:59:27 +00:00
using System.Linq ;
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 ;
2019-12-17 05:59:27 +00: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 ;
2020-02-07 15:50:22 +00:00
private Bindable < bool > notifyOnPM ;
2021-05-27 19:58:54 +00:00
private readonly IBindable < User > localUser = new Bindable < User > ( ) ;
2021-06-05 09:03:49 +00:00
private readonly IBindableList < Channel > joinedChannels = new BindableList < Channel > ( ) ;
2019-12-17 05:59:27 +00:00
[BackgroundDependencyLoader]
2019-12-26 02:32:40 +00:00
private void load ( OsuConfigManager config , IAPIProvider api )
2019-12-17 05:59:27 +00:00
{
notifyOnMention = config . GetBindable < bool > ( OsuSetting . ChatHighlightName ) ;
2020-02-07 15:50:22 +00:00
notifyOnPM = config . GetBindable < bool > ( OsuSetting . ChatMessageNotification ) ;
2021-06-05 09:03:49 +00:00
localUser . BindTo ( api . LocalUser ) ;
2020-01-25 15:43:51 +00:00
2021-06-11 07:27:31 +00:00
joinedChannels . BindCollectionChanged ( channelsChanged ) ;
2021-06-05 09:03:49 +00:00
joinedChannels . BindTo ( channelManager . JoinedChannels ) ;
2021-05-26 06:59:29 +00:00
}
2019-12-26 02:32:40 +00:00
2021-05-26 06:59:29 +00:00
private void channelsChanged ( object sender , NotifyCollectionChangedEventArgs e )
{
switch ( e . Action )
2019-12-26 02:32:40 +00:00
{
2021-05-26 06:59:29 +00:00
case NotifyCollectionChangedAction . Add :
foreach ( var channel in e . NewItems . Cast < Channel > ( ) )
channel . NewMessagesArrived + = newMessagesArrived ;
break ;
case NotifyCollectionChangedAction . Remove :
foreach ( var channel in e . OldItems . Cast < Channel > ( ) )
channel . NewMessagesArrived - = newMessagesArrived ;
break ;
}
2019-12-26 02:32:40 +00:00
}
2020-02-05 18:20:16 +00:00
private void newMessagesArrived ( IEnumerable < Message > messages )
2019-12-26 02:32:40 +00:00
{
2021-06-11 07:27:31 +00:00
if ( ! messages . Any ( ) )
2019-12-26 02:32:40 +00:00
return ;
2021-06-11 07:27:31 +00:00
var channel = channelManager . JoinedChannels . SingleOrDefault ( c = > c . Id = = messages . First ( ) . ChannelId ) ;
2019-12-26 02:32:40 +00:00
if ( channel = = null )
{
2021-06-11 07:27:31 +00:00
Logger . Log ( $"Couldn't resolve channel id {messages.First().ChannelId}" , LoggingTarget . Information ) ;
2019-12-26 02:32:40 +00:00
return ;
}
2020-01-21 23:13:07 +00:00
// Only send notifications, if ChatOverlay and the target channel aren't visible.
2020-01-22 09:47:51 +00:00
if ( chatOverlay ? . IsPresent = = true & & channelManager . CurrentChannel . Value = = channel )
2019-12-17 05:59:27 +00:00
return ;
2020-01-21 23:27:46 +00:00
foreach ( var message in messages . OrderByDescending ( m = > m . Id ) )
2019-12-17 05:59:27 +00:00
{
2020-01-16 23:00:10 +00:00
// ignore messages that already have been read
2020-01-21 23:28:08 +00:00
if ( message . Id < = channel . LastReadId )
2020-01-16 23:00:10 +00:00
return ;
2020-01-21 22:42:15 +00:00
if ( message . Sender . Id = = localUser . Value . Id )
2019-12-17 05:59:27 +00:00
continue ;
2020-02-07 15:52:53 +00:00
// check for private messages first,
// to avoid both posting two notifications about the same message
2020-01-19 16:55:17 +00:00
if ( checkForPMs ( channel , message ) )
continue ;
2019-12-17 05:59:27 +00:00
2021-06-05 13:57:14 +00:00
_ = checkForMentions ( channel , message , localUser . Value . Username ) ;
2020-01-19 16:55:17 +00:00
}
}
2020-01-18 13:17:26 +00:00
2021-06-05 13:57:14 +00:00
/// <summary>
/// Checks whether the user enabled private message notifications and whether specified <paramref name="message"/> is a direct message.
/// </summary>
/// <param name="channel">The channel associated to the <paramref name="message"/></param>
/// <param name="message">The message to be checked</param>
2020-01-19 16:55:17 +00:00
private bool checkForPMs ( Channel channel , Message message )
{
2020-02-07 15:50:22 +00:00
if ( ! notifyOnPM . Value | | channel . Type ! = ChannelType . PM )
2020-01-19 16:55:17 +00:00
return false ;
2020-01-16 22:15:30 +00:00
2021-06-05 13:57:14 +00:00
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 ) ) ;
2020-01-21 23:28:59 +00:00
2021-06-05 13:57:14 +00:00
var notification = new PrivateMessageNotification ( message . Sender . Username , channel ) ;
2020-01-22 09:48:55 +00:00
notificationOverlay ? . Post ( notification ) ;
2020-01-19 16:55:17 +00:00
return true ;
}
2021-06-05 13:57:14 +00:00
/// <summary>
/// Checks whether the user enabled mention notifications and whether specified <paramref name="message"/> mentions the provided <paramref name="username"/>.
/// </summary>
/// <param name="channel">The channel associated to the <paramref name="message"/></param>
/// <param name="message">The message to be checked</param>
/// <param name="username">The username that will be checked for</param>
private bool checkForMentions ( Channel channel , Message message , string username )
2020-01-19 16:55:17 +00:00
{
2020-01-29 01:07:08 +00:00
if ( ! notifyOnMention . Value | | ! isMentioning ( message . Content , username ) )
2021-06-05 13:57:14 +00:00
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 ) ) ;
2020-01-19 16:55:17 +00:00
var notification = new MentionNotification ( message . Sender . Username , channel ) ;
notificationOverlay ? . Post ( notification ) ;
2021-06-05 13:57:14 +00:00
return true ;
2019-12-17 05:59:27 +00:00
}
/// <summary>
2020-01-25 13:40:53 +00:00
/// Checks if <paramref name="message"/> contains <paramref name="username"/>, if not, retries making spaces into underscores.
2019-12-17 05:59:27 +00:00
/// </summary>
2020-01-25 13:40:53 +00:00
/// <returns>If the <paramref name="message"/> mentions the <paramref name="username"/></returns>
2021-05-27 09:48:30 +00:00
private static bool isMentioning ( string message , string username ) = > message . Contains ( username , StringComparison . OrdinalIgnoreCase ) | | message . Contains ( username . Replace ( ' ' , '_' ) , StringComparison . OrdinalIgnoreCase ) ;
2019-12-17 05:59:27 +00:00
2021-05-26 23:00:26 +00:00
public class OpenChannelNotification : SimpleNotification
2019-12-17 05:59:27 +00:00
{
2021-05-26 23:00:26 +00:00
public OpenChannelNotification ( Channel channel )
2019-12-17 05:59:27 +00:00
{
2020-02-03 22:03:27 +00:00
this . channel = channel ;
2019-12-17 05:59:27 +00:00
}
2020-02-03 22:03:27 +00:00
private readonly Channel channel ;
2020-01-21 23:28:59 +00:00
2019-12-17 05:59:27 +00:00
public override bool IsImportant = > false ;
[BackgroundDependencyLoader]
2020-01-21 23:28:59 +00:00
private void load ( OsuColour colours , ChatOverlay chatOverlay , NotificationOverlay notificationOverlay , ChannelManager channelManager )
2019-12-17 05:59:27 +00:00
{
IconBackgound . Colour = colours . PurpleDark ;
2020-01-21 23:28:59 +00:00
2019-12-17 05:59:27 +00:00
Activated = delegate
{
2020-01-19 16:55:17 +00:00
notificationOverlay . Hide ( ) ;
chatOverlay . Show ( ) ;
2020-02-03 22:03:27 +00:00
channelManager . CurrentChannel . Value = channel ;
2020-01-16 23:00:10 +00:00
2019-12-17 05:59:27 +00:00
return true ;
} ;
}
}
2021-05-26 23:00:26 +00:00
public class PrivateMessageNotification : OpenChannelNotification
2019-12-17 05:59:27 +00:00
{
2021-05-27 19:58:54 +00:00
public PrivateMessageNotification ( string username , Channel channel )
: base ( channel )
2019-12-17 05:59:27 +00:00
{
2021-05-26 23:00:26 +00:00
Icon = FontAwesome . Solid . Envelope ;
Text = $"You received a private message from '{username}'. Click to read it!" ;
2019-12-17 05:59:27 +00:00
}
2021-05-26 23:00:26 +00:00
}
2019-12-17 05:59:27 +00:00
2021-05-26 23:00:26 +00:00
public class MentionNotification : OpenChannelNotification
{
2021-05-27 19:58:54 +00:00
public MentionNotification ( string username , Channel channel )
: base ( channel )
2019-12-17 05:59:27 +00:00
{
2021-05-26 23:00:26 +00:00
Icon = FontAwesome . Solid . At ;
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!" ;
2019-12-17 05:59:27 +00:00
}
}
}
}