2019-12-18 05:07:53 +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.
|
|
|
|
|
|
2022-06-17 07:37:17 +00:00
|
|
|
|
#nullable disable
|
|
|
|
|
|
2019-12-23 10:34:12 +00:00
|
|
|
|
using System;
|
2019-12-21 14:48:15 +00:00
|
|
|
|
using System.Text;
|
2019-12-18 05:07:53 +00:00
|
|
|
|
using DiscordRPC;
|
|
|
|
|
using DiscordRPC.Message;
|
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
|
using osu.Framework.Logging;
|
2022-06-14 17:25:06 +00:00
|
|
|
|
using osu.Game.Beatmaps;
|
2020-12-30 05:29:51 +00:00
|
|
|
|
using osu.Game.Configuration;
|
2022-03-03 05:15:25 +00:00
|
|
|
|
using osu.Game.Extensions;
|
2019-12-18 05:07:53 +00:00
|
|
|
|
using osu.Game.Online.API;
|
2021-11-04 09:02:44 +00:00
|
|
|
|
using osu.Game.Online.API.Requests.Responses;
|
2019-12-18 05:07:53 +00:00
|
|
|
|
using osu.Game.Rulesets;
|
|
|
|
|
using osu.Game.Users;
|
|
|
|
|
using LogLevel = osu.Framework.Logging.LogLevel;
|
|
|
|
|
|
|
|
|
|
namespace osu.Desktop
|
|
|
|
|
{
|
|
|
|
|
internal class DiscordRichPresence : Component
|
|
|
|
|
{
|
|
|
|
|
private const string client_id = "367827983903490050";
|
|
|
|
|
|
|
|
|
|
private DiscordRpcClient client;
|
|
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
|
private IBindable<RulesetInfo> ruleset { get; set; }
|
|
|
|
|
|
2021-11-04 09:02:44 +00:00
|
|
|
|
private IBindable<APIUser> user;
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
2022-05-30 20:38:47 +00:00
|
|
|
|
[Resolved]
|
2022-06-09 03:32:30 +00:00
|
|
|
|
private IAPIProvider api { get; set; }
|
2022-05-30 20:38:47 +00:00
|
|
|
|
|
2019-12-18 05:07:53 +00:00
|
|
|
|
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
|
|
|
|
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
2021-01-06 15:05:12 +00:00
|
|
|
|
|
|
|
|
|
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
|
|
|
|
private readonly RichPresence presence = new RichPresence
|
|
|
|
|
{
|
|
|
|
|
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
2022-05-30 21:32:55 +00:00
|
|
|
|
private void load(OsuConfigManager config)
|
2019-12-18 05:07:53 +00:00
|
|
|
|
{
|
|
|
|
|
client = new DiscordRpcClient(client_id)
|
|
|
|
|
{
|
|
|
|
|
SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
client.OnReady += onReady;
|
2019-12-23 10:50:35 +00:00
|
|
|
|
|
|
|
|
|
// safety measure for now, until we performance test / improve backoff for failed connections.
|
2022-06-24 12:25:23 +00:00
|
|
|
|
client.OnConnectionFailed += (_, _) => client.Deinitialize();
|
2019-12-23 10:50:35 +00:00
|
|
|
|
|
2019-12-18 13:56:00 +00:00
|
|
|
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
2021-01-06 15:05:12 +00:00
|
|
|
|
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
|
|
|
|
|
2022-06-09 03:32:30 +00:00
|
|
|
|
user = api.LocalUser.GetBoundCopy();
|
|
|
|
|
user.BindValueChanged(u =>
|
2019-12-18 05:07:53 +00:00
|
|
|
|
{
|
|
|
|
|
status.UnbindBindings();
|
|
|
|
|
status.BindTo(u.NewValue.Status);
|
|
|
|
|
|
|
|
|
|
activity.UnbindBindings();
|
|
|
|
|
activity.BindTo(u.NewValue.Activity);
|
|
|
|
|
}, true);
|
|
|
|
|
|
|
|
|
|
ruleset.BindValueChanged(_ => updateStatus());
|
|
|
|
|
status.BindValueChanged(_ => updateStatus());
|
|
|
|
|
activity.BindValueChanged(_ => updateStatus());
|
2021-01-06 15:05:12 +00:00
|
|
|
|
privacyMode.BindValueChanged(_ => updateStatus());
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
|
|
|
|
client.Initialize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void onReady(object _, ReadyMessage __)
|
|
|
|
|
{
|
|
|
|
|
Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
|
|
|
|
|
updateStatus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updateStatus()
|
|
|
|
|
{
|
2020-01-11 15:03:00 +00:00
|
|
|
|
if (!client.IsInitialized)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-01-06 15:05:12 +00:00
|
|
|
|
if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
|
2019-12-18 05:07:53 +00:00
|
|
|
|
{
|
|
|
|
|
client.ClearPresence();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-20 12:41:31 +00:00
|
|
|
|
if (status.Value is UserStatusOnline && activity.Value != null)
|
2019-12-18 05:07:53 +00:00
|
|
|
|
{
|
2019-12-21 14:48:15 +00:00
|
|
|
|
presence.State = truncate(activity.Value.Status);
|
|
|
|
|
presence.Details = truncate(getDetails(activity.Value));
|
2021-11-20 12:41:01 +00:00
|
|
|
|
|
2022-06-14 17:25:06 +00:00
|
|
|
|
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
|
2021-11-20 12:41:01 +00:00
|
|
|
|
{
|
2022-06-14 17:25:06 +00:00
|
|
|
|
presence.Buttons = new[]
|
2021-11-20 12:41:01 +00:00
|
|
|
|
{
|
2022-06-14 17:25:06 +00:00
|
|
|
|
new Button
|
|
|
|
|
{
|
|
|
|
|
Label = "View beatmap",
|
2022-06-14 17:38:44 +00:00
|
|
|
|
Url = $@"{api.WebsiteRootUrl}/beatmapsets/{beatmap.BeatmapSet?.OnlineID}#{ruleset.Value.ShortName}/{beatmap.OnlineID}"
|
2022-06-14 17:25:06 +00:00
|
|
|
|
}
|
2021-11-20 12:41:01 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
presence.Buttons = null;
|
|
|
|
|
}
|
2019-12-18 05:07:53 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
presence.State = "Idle";
|
|
|
|
|
presence.Details = string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update user information
|
2021-01-06 15:05:12 +00:00
|
|
|
|
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
2020-12-30 05:29:51 +00:00
|
|
|
|
presence.Assets.LargeImageText = string.Empty;
|
|
|
|
|
else
|
2022-05-30 20:38:47 +00:00
|
|
|
|
{
|
|
|
|
|
if (user.Value.RulesetsStatistics != null && user.Value.RulesetsStatistics.TryGetValue(ruleset.Value.ShortName, out UserStatistics statistics))
|
|
|
|
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (statistics.GlobalRank > 0 ? $" (rank #{statistics.GlobalRank:N0})" : string.Empty);
|
|
|
|
|
else
|
|
|
|
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
|
|
|
|
}
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
|
|
|
|
// update ruleset
|
2022-03-03 05:15:25 +00:00
|
|
|
|
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";
|
2019-12-18 05:07:53 +00:00
|
|
|
|
presence.Assets.SmallImageText = ruleset.Value.Name;
|
|
|
|
|
|
|
|
|
|
client.SetPresence(presence);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 02:14:40 +00:00
|
|
|
|
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
|
|
|
|
|
|
2019-12-25 03:04:28 +00:00
|
|
|
|
private string truncate(string str)
|
2019-12-23 09:55:44 +00:00
|
|
|
|
{
|
2019-12-23 10:56:05 +00:00
|
|
|
|
if (Encoding.UTF8.GetByteCount(str) <= 128)
|
2019-12-25 03:04:28 +00:00
|
|
|
|
return str;
|
|
|
|
|
|
|
|
|
|
ReadOnlyMemory<char> strMem = str.AsMemory();
|
2019-12-23 09:55:44 +00:00
|
|
|
|
|
2019-12-23 10:34:12 +00:00
|
|
|
|
do
|
|
|
|
|
{
|
2019-12-25 03:04:28 +00:00
|
|
|
|
strMem = strMem[..^1];
|
|
|
|
|
} while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
|
2019-12-23 10:34:12 +00:00
|
|
|
|
|
2019-12-25 03:04:28 +00:00
|
|
|
|
return string.Create(strMem.Length + 1, strMem, (span, mem) =>
|
|
|
|
|
{
|
|
|
|
|
mem.Span.CopyTo(span);
|
|
|
|
|
span[^1] = '…';
|
|
|
|
|
});
|
2019-12-23 09:55:44 +00:00
|
|
|
|
}
|
2019-12-21 14:48:15 +00:00
|
|
|
|
|
2022-06-14 17:25:06 +00:00
|
|
|
|
private IBeatmapInfo getBeatmap(UserActivity activity)
|
|
|
|
|
{
|
|
|
|
|
switch (activity)
|
|
|
|
|
{
|
|
|
|
|
case UserActivity.InGame game:
|
|
|
|
|
return game.BeatmapInfo;
|
|
|
|
|
|
|
|
|
|
case UserActivity.Editing edit:
|
|
|
|
|
return edit.BeatmapInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-18 05:07:53 +00:00
|
|
|
|
private string getDetails(UserActivity activity)
|
|
|
|
|
{
|
|
|
|
|
switch (activity)
|
|
|
|
|
{
|
2021-08-15 22:32:33 +00:00
|
|
|
|
case UserActivity.InGame game:
|
2021-10-02 15:55:29 +00:00
|
|
|
|
return game.BeatmapInfo.ToString();
|
2019-12-18 05:07:53 +00:00
|
|
|
|
|
|
|
|
|
case UserActivity.Editing edit:
|
2021-10-02 15:55:29 +00:00
|
|
|
|
return edit.BeatmapInfo.ToString();
|
2020-11-08 17:42:24 +00:00
|
|
|
|
|
|
|
|
|
case UserActivity.InLobby lobby:
|
2021-01-06 15:05:12 +00:00
|
|
|
|
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
2019-12-18 05:07:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
|
|
|
{
|
|
|
|
|
client.Dispose();
|
|
|
|
|
base.Dispose(isDisposing);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|