1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 21:02:55 +08:00

Merge pull request #21073 from smoogipoo/chat-silences

Remove chat messages from silenced users
This commit is contained in:
Dean Herbert 2022-11-13 09:51:52 +09:00 committed by GitHub
commit f7913cbf1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 26 deletions

View File

@ -23,6 +23,7 @@ namespace osu.Game.Tests.Chat
private ChannelManager channelManager;
private int currentMessageId;
private List<Message> sentMessages;
private List<int> silencedUserIds;
[SetUp]
public void Setup() => Schedule(() =>
@ -39,6 +40,7 @@ namespace osu.Game.Tests.Chat
{
currentMessageId = 0;
sentMessages = new List<Message>();
silencedUserIds = new List<int>();
((DummyAPIAccess)API).HandleRequest = req =>
{
@ -56,6 +58,11 @@ namespace osu.Game.Tests.Chat
handleMarkChannelAsReadRequest(markRead);
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:
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);
}
[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)
{
var message = new Message(++currentMessageId)

View File

@ -40,8 +40,10 @@ namespace osu.Game.Tests.Visual.Online
private ChannelManager channelManager;
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 Message[] initialMessages;
private Channel testChannel1 => testChannels[0];
private Channel testChannel2 => testChannels[1];
@ -49,10 +51,14 @@ namespace osu.Game.Tests.Visual.Online
[Resolved]
private OsuConfigManager config { get; set; } = null!;
private int currentMessageId;
[SetUp]
public void SetUp() => Schedule(() =>
{
currentMessageId = 0;
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
initialMessages = testChannels.SelectMany(createChannelMessages).ToArray();
Child = new DependencyProvidingContainer
{
@ -99,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
return true;
case GetMessagesRequest getMessages:
getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel));
getMessages.TriggerSuccess(initialMessages.ToList());
return true;
case GetUserRequest getUser:
@ -495,6 +501,35 @@ namespace osu.Game.Tests.Visual.Online
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)
{
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)
{
var message = new Message
var message = new Message(currentMessageId++)
{
ChannelId = channel.Id,
Content = $"Hello, this is a message in {channel.Name}",

View File

@ -7,12 +7,30 @@ using osu.Game.Online.API.Requests.Responses;
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 long? SinceMessageId;
public uint? SinceSilenceId;
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
if (SinceMessageId != null)
req.AddParameter(@"since", SinceMessageId.ToString());
if (SinceSilenceId != null)
req.AddParameter(@"history_since", SinceSilenceId.Value.ToString());
return req;
}

View File

@ -12,6 +12,6 @@ namespace osu.Game.Online.API.Requests.Responses
public uint Id { get; set; }
[JsonProperty("user_id")]
public uint UserId { get; set; }
public int UserId { get; set; }
}
}

View File

@ -157,6 +157,20 @@ namespace osu.Game.Online.Chat
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>
/// Replace or remove a message from the channel.
/// </summary>

View File

@ -74,6 +74,9 @@ namespace osu.Game.Online.Chat
private bool channelsInitialised;
private ScheduledDelegate scheduledAck;
private long? lastSilenceMessageId;
private uint? lastSilenceId;
public ChannelManager(IAPIProvider api)
{
this.api = api;
@ -105,28 +108,7 @@ namespace osu.Game.Online.Chat
connector.Start();
apiState.BindTo(api.State);
apiState.BindValueChanged(_ => performChatAckRequest(), 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);
}
apiState.BindValueChanged(_ => SendAck(), true);
}
/// <summary>
@ -349,6 +331,8 @@ namespace osu.Game.Online.Chat
foreach (var group in messages.GroupBy(m => m.ChannelId))
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
lastSilenceMessageId ??= messages.LastOrDefault()?.Id;
}
private void initializeChannels()
@ -398,6 +382,44 @@ namespace osu.Game.Online.Chat
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>
/// 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.

View File

@ -72,7 +72,6 @@ namespace osu.Game.Online.Notifications.WebSocket
break;
}
Logger.Log($"{GetType().ReadableName()} handling event: {message.Event}");
await onMessageReceivedAsync(message);
}