mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 12:57:36 +08:00
Merge pull request #21073 from smoogipoo/chat-silences
Remove chat messages from silenced users
This commit is contained in:
commit
f7913cbf1c
@ -23,6 +23,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
private int currentMessageId;
|
private int currentMessageId;
|
||||||
private List<Message> sentMessages;
|
private List<Message> sentMessages;
|
||||||
|
private List<int> silencedUserIds;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -39,6 +40,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
{
|
{
|
||||||
currentMessageId = 0;
|
currentMessageId = 0;
|
||||||
sentMessages = new List<Message>();
|
sentMessages = new List<Message>();
|
||||||
|
silencedUserIds = new List<int>();
|
||||||
|
|
||||||
((DummyAPIAccess)API).HandleRequest = req =>
|
((DummyAPIAccess)API).HandleRequest = req =>
|
||||||
{
|
{
|
||||||
@ -56,6 +58,11 @@ namespace osu.Game.Tests.Chat
|
|||||||
handleMarkChannelAsReadRequest(markRead);
|
handleMarkChannelAsReadRequest(markRead);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case ChatAckRequest ack:
|
||||||
|
ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() });
|
||||||
|
silencedUserIds.Clear();
|
||||||
|
return true;
|
||||||
|
|
||||||
case GetUpdatesRequest updatesRequest:
|
case GetUpdatesRequest updatesRequest:
|
||||||
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||||
{
|
{
|
||||||
@ -115,6 +122,28 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
|
AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSilencedUsersAreRemoved()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("post message", () => channelManager.PostMessage("Definitely something bad"));
|
||||||
|
|
||||||
|
AddStep("mark user as silenced and send ack request", () =>
|
||||||
|
{
|
||||||
|
silencedUserIds.Add(API.LocalUser.Value.OnlineID);
|
||||||
|
channelManager.SendAck();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("channel has no more messages", () => channel.Messages, () => Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePostMessageRequest(PostMessageRequest request)
|
private void handlePostMessageRequest(PostMessageRequest request)
|
||||||
{
|
{
|
||||||
var message = new Message(++currentMessageId)
|
var message = new Message(++currentMessageId)
|
||||||
|
@ -40,8 +40,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
|
|
||||||
private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 };
|
private readonly APIUser testUser = new APIUser { Username = "test user", Id = 5071479 };
|
||||||
|
private readonly APIUser testUser1 = new APIUser { Username = "test user", Id = 5071480 };
|
||||||
|
|
||||||
private Channel[] testChannels;
|
private Channel[] testChannels;
|
||||||
|
private Message[] initialMessages;
|
||||||
|
|
||||||
private Channel testChannel1 => testChannels[0];
|
private Channel testChannel1 => testChannels[0];
|
||||||
private Channel testChannel2 => testChannels[1];
|
private Channel testChannel2 => testChannels[1];
|
||||||
@ -49,10 +51,14 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; } = null!;
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
private int currentMessageId;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
currentMessageId = 0;
|
||||||
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
|
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
|
||||||
|
initialMessages = testChannels.SelectMany(createChannelMessages).ToArray();
|
||||||
|
|
||||||
Child = new DependencyProvidingContainer
|
Child = new DependencyProvidingContainer
|
||||||
{
|
{
|
||||||
@ -99,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GetMessagesRequest getMessages:
|
case GetMessagesRequest getMessages:
|
||||||
getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel));
|
getMessages.TriggerSuccess(initialMessages.ToList());
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case GetUserRequest getUser:
|
case GetUserRequest getUser:
|
||||||
@ -495,6 +501,35 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
waitForChannel1Visible();
|
waitForChannel1Visible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveMessages()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay with channel", () =>
|
||||||
|
{
|
||||||
|
chatOverlay.Show();
|
||||||
|
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
|
||||||
|
waitForChannel1Visible();
|
||||||
|
|
||||||
|
AddStep("Send message from another user", () =>
|
||||||
|
{
|
||||||
|
testChannel1.AddNewMessages(new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel1.Id,
|
||||||
|
Content = "Message from another user",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Remove messages from other user", () =>
|
||||||
|
{
|
||||||
|
testChannel1.RemoveMessagesFromUser(testUser.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void joinTestChannel(int i)
|
private void joinTestChannel(int i)
|
||||||
{
|
{
|
||||||
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
||||||
@ -546,7 +581,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private List<Message> createChannelMessages(Channel channel)
|
private List<Message> createChannelMessages(Channel channel)
|
||||||
{
|
{
|
||||||
var message = new Message
|
var message = new Message(currentMessageId++)
|
||||||
{
|
{
|
||||||
ChannelId = channel.Id,
|
ChannelId = channel.Id,
|
||||||
Content = $"Hello, this is a message in {channel.Name}",
|
Content = $"Hello, this is a message in {channel.Name}",
|
||||||
|
@ -7,12 +7,30 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
|
|
||||||
namespace osu.Game.Online.API.Requests
|
namespace osu.Game.Online.API.Requests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A request which should be sent occasionally while interested in chat and online state.
|
||||||
|
///
|
||||||
|
/// This will:
|
||||||
|
/// - Mark the user as "online" (for 10 minutes since the last invocation).
|
||||||
|
/// - Return any silences since the last invocation (if either <see cref="SinceMessageId"/> or <see cref="SinceSilenceId"/> is not null).
|
||||||
|
///
|
||||||
|
/// For silence handling, a <see cref="SinceMessageId"/> should be provided as soon as a message is received by the client.
|
||||||
|
/// From that point forward, <see cref="SinceSilenceId"/> should be preferred after the first <see cref="ChatSilence"/>
|
||||||
|
/// arrives in a response from the ack request. Specifying both parameters will prioritise the latter.
|
||||||
|
/// </summary>
|
||||||
public class ChatAckRequest : APIRequest<ChatAckResponse>
|
public class ChatAckRequest : APIRequest<ChatAckResponse>
|
||||||
{
|
{
|
||||||
|
public long? SinceMessageId;
|
||||||
|
public uint? SinceSilenceId;
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
{
|
{
|
||||||
var req = base.CreateWebRequest();
|
var req = base.CreateWebRequest();
|
||||||
req.Method = HttpMethod.Post;
|
req.Method = HttpMethod.Post;
|
||||||
|
if (SinceMessageId != null)
|
||||||
|
req.AddParameter(@"since", SinceMessageId.ToString());
|
||||||
|
if (SinceSilenceId != null)
|
||||||
|
req.AddParameter(@"history_since", SinceSilenceId.Value.ToString());
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,6 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public uint Id { get; set; }
|
public uint Id { get; set; }
|
||||||
|
|
||||||
[JsonProperty("user_id")]
|
[JsonProperty("user_id")]
|
||||||
public uint UserId { get; set; }
|
public int UserId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +157,20 @@ namespace osu.Game.Online.Chat
|
|||||||
NewMessagesArrived?.Invoke(messages);
|
NewMessagesArrived?.Invoke(messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RemoveMessagesFromUser(int userId)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Messages.Count; i++)
|
||||||
|
{
|
||||||
|
var message = Messages[i];
|
||||||
|
|
||||||
|
if (message.SenderId == userId)
|
||||||
|
{
|
||||||
|
Messages.RemoveAt(i--);
|
||||||
|
MessageRemoved?.Invoke(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replace or remove a message from the channel.
|
/// Replace or remove a message from the channel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -74,6 +74,9 @@ namespace osu.Game.Online.Chat
|
|||||||
private bool channelsInitialised;
|
private bool channelsInitialised;
|
||||||
private ScheduledDelegate scheduledAck;
|
private ScheduledDelegate scheduledAck;
|
||||||
|
|
||||||
|
private long? lastSilenceMessageId;
|
||||||
|
private uint? lastSilenceId;
|
||||||
|
|
||||||
public ChannelManager(IAPIProvider api)
|
public ChannelManager(IAPIProvider api)
|
||||||
{
|
{
|
||||||
this.api = api;
|
this.api = api;
|
||||||
@ -105,28 +108,7 @@ namespace osu.Game.Online.Chat
|
|||||||
connector.Start();
|
connector.Start();
|
||||||
|
|
||||||
apiState.BindTo(api.State);
|
apiState.BindTo(api.State);
|
||||||
apiState.BindValueChanged(_ => performChatAckRequest(), true);
|
apiState.BindValueChanged(_ => SendAck(), true);
|
||||||
}
|
|
||||||
|
|
||||||
private void performChatAckRequest()
|
|
||||||
{
|
|
||||||
if (apiState.Value != APIState.Online)
|
|
||||||
return;
|
|
||||||
|
|
||||||
scheduledAck?.Cancel();
|
|
||||||
|
|
||||||
var req = new ChatAckRequest();
|
|
||||||
req.Success += _ => scheduleNextRequest();
|
|
||||||
req.Failure += _ => scheduleNextRequest();
|
|
||||||
api.Queue(req);
|
|
||||||
|
|
||||||
// Todo: Handle silences.
|
|
||||||
|
|
||||||
void scheduleNextRequest()
|
|
||||||
{
|
|
||||||
scheduledAck?.Cancel();
|
|
||||||
scheduledAck = Scheduler.AddDelayed(performChatAckRequest, 60000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -349,6 +331,8 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
foreach (var group in messages.GroupBy(m => m.ChannelId))
|
foreach (var group in messages.GroupBy(m => m.ChannelId))
|
||||||
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||||
|
|
||||||
|
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeChannels()
|
private void initializeChannels()
|
||||||
@ -398,6 +382,44 @@ namespace osu.Game.Online.Chat
|
|||||||
api.Queue(fetchInitialMsgReq);
|
api.Queue(fetchInitialMsgReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends an acknowledgement request to the API.
|
||||||
|
/// This marks the user as online to receive messages from public channels, while also returning a list of silenced users.
|
||||||
|
/// It needs to be called at least once every 10 minutes to remain visibly marked as online.
|
||||||
|
/// </summary>
|
||||||
|
public void SendAck()
|
||||||
|
{
|
||||||
|
if (apiState.Value != APIState.Online)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var req = new ChatAckRequest
|
||||||
|
{
|
||||||
|
SinceMessageId = lastSilenceMessageId,
|
||||||
|
SinceSilenceId = lastSilenceId
|
||||||
|
};
|
||||||
|
|
||||||
|
req.Failure += _ => scheduleNextRequest();
|
||||||
|
req.Success += ack =>
|
||||||
|
{
|
||||||
|
foreach (var silence in ack.Silences)
|
||||||
|
{
|
||||||
|
foreach (var channel in JoinedChannels)
|
||||||
|
channel.RemoveMessagesFromUser(silence.UserId);
|
||||||
|
lastSilenceId = Math.Max(lastSilenceId ?? 0, silence.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleNextRequest();
|
||||||
|
};
|
||||||
|
|
||||||
|
api.Queue(req);
|
||||||
|
|
||||||
|
void scheduleNextRequest()
|
||||||
|
{
|
||||||
|
scheduledAck?.Cancel();
|
||||||
|
scheduledAck = Scheduler.AddDelayed(SendAck, 60000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Find an existing channel instance for the provided channel. Lookup is performed basd on ID.
|
/// Find an existing channel instance for the provided channel. Lookup is performed basd on ID.
|
||||||
/// The provided channel may be used if an existing instance is not found.
|
/// The provided channel may be used if an existing instance is not found.
|
||||||
|
@ -72,7 +72,6 @@ namespace osu.Game.Online.Notifications.WebSocket
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Log($"{GetType().ReadableName()} handling event: {message.Event}");
|
|
||||||
await onMessageReceivedAsync(message);
|
await onMessageReceivedAsync(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user