mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 23:53:51 +09:00
Merge remote-tracking branch 'origin/master' into Private_Messages
This commit is contained in:
@ -1,339 +1,334 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class APIAccess : Component, IAPIProvider
|
||||
{
|
||||
private readonly OsuConfigManager config;
|
||||
private readonly OAuth authentication;
|
||||
|
||||
public string Endpoint = @"https://osu.ppy.sh";
|
||||
private const string client_id = @"5";
|
||||
private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
|
||||
private ConcurrentQueue<APIRequest> queue = new ConcurrentQueue<APIRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// The username/email provided by the user when initiating a login.
|
||||
/// </summary>
|
||||
public string ProvidedUsername { get; private set; }
|
||||
|
||||
private string password;
|
||||
|
||||
public Bindable<User> LocalUser { get; } = new Bindable<User>(createGuestUser());
|
||||
|
||||
public string Token
|
||||
{
|
||||
get { return authentication.Token?.ToString(); }
|
||||
set { authentication.Token = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value); }
|
||||
}
|
||||
|
||||
protected bool HasLogin => Token != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password);
|
||||
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
private readonly Logger log;
|
||||
|
||||
public APIAccess(OsuConfigManager config)
|
||||
{
|
||||
this.config = config;
|
||||
|
||||
authentication = new OAuth(client_id, client_secret, Endpoint);
|
||||
log = Logger.GetLogger(LoggingTarget.Network);
|
||||
|
||||
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
||||
Token = config.Get<string>(OsuSetting.Token);
|
||||
|
||||
Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
|
||||
|
||||
internal new void Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Add(component);
|
||||
component.APIStateChanged(this, state);
|
||||
});
|
||||
}
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Remove(component);
|
||||
});
|
||||
}
|
||||
|
||||
public string AccessToken => authentication.RequestAccessToken();
|
||||
|
||||
/// <summary>
|
||||
/// Number of consecutive requests which failed due to network issues.
|
||||
/// </summary>
|
||||
private int failureCount;
|
||||
|
||||
private void run()
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case APIState.Failing:
|
||||
//todo: replace this with a ping request.
|
||||
log.Add(@"In a failing state, waiting a bit before we try again...");
|
||||
Thread.Sleep(5000);
|
||||
if (queue.Count == 0)
|
||||
{
|
||||
log.Add(@"Queueing a ping request");
|
||||
Queue(new ListChannelsRequest { Timeout = 5000 });
|
||||
}
|
||||
break;
|
||||
case APIState.Offline:
|
||||
case APIState.Connecting:
|
||||
//work to restore a connection...
|
||||
if (!HasLogin)
|
||||
{
|
||||
State = APIState.Offline;
|
||||
Thread.Sleep(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
State = APIState.Connecting;
|
||||
|
||||
// save the username at this point, if the user requested for it to be.
|
||||
config.Set(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||
|
||||
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password))
|
||||
{
|
||||
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||
//NotificationOverlay.ShowMessage("Login failed!");
|
||||
log.Add(@"Login failed!");
|
||||
password = null;
|
||||
authentication.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
var userReq = new GetUserRequest();
|
||||
userReq.Success += u =>
|
||||
{
|
||||
LocalUser.Value = u;
|
||||
failureCount = 0;
|
||||
|
||||
//we're connected!
|
||||
State = APIState.Online;
|
||||
};
|
||||
|
||||
if (!handleRequest(userReq))
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||
// before actually going online.
|
||||
while (State != APIState.Online)
|
||||
Thread.Sleep(500);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//hard bail if we can't get a valid access token.
|
||||
if (authentication.RequestAccessToken() == null)
|
||||
{
|
||||
Logout(false);
|
||||
State = APIState.Offline;
|
||||
continue;
|
||||
}
|
||||
|
||||
//process the request queue.
|
||||
APIRequest req;
|
||||
while (queue.TryPeek(out req))
|
||||
{
|
||||
if (handleRequest(req))
|
||||
{
|
||||
//we have succeeded, so let's unqueue.
|
||||
queue.TryDequeue(out req);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Login(string username, string password)
|
||||
{
|
||||
Debug.Assert(State == APIState.Offline);
|
||||
|
||||
ProvidedUsername = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a single API request.
|
||||
/// </summary>
|
||||
/// <param name="req">The request.</param>
|
||||
/// <returns>true if we should remove this request from the queue.</returns>
|
||||
private bool handleRequest(APIRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
|
||||
req.Perform(this);
|
||||
|
||||
//we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
if (IsLoggedIn)
|
||||
State = APIState.Online;
|
||||
|
||||
failureCount = 0;
|
||||
return true;
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
case HttpStatusCode.Unauthorized:
|
||||
Logout(false);
|
||||
return true;
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
failureCount++;
|
||||
log.Add($@"API failure count is now {failureCount}");
|
||||
|
||||
if (failureCount < 3)
|
||||
//we might try again at an api level.
|
||||
return false;
|
||||
|
||||
State = APIState.Failing;
|
||||
flushQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
req.Fail(we);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is TimeoutException)
|
||||
log.Add(@"API level timeout exception was hit");
|
||||
|
||||
req.Fail(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private APIState state;
|
||||
public APIState State
|
||||
{
|
||||
get { return state; }
|
||||
private set
|
||||
{
|
||||
APIState oldState = state;
|
||||
APIState newState = value;
|
||||
|
||||
state = value;
|
||||
|
||||
if (oldState != newState)
|
||||
{
|
||||
log.Add($@"We just went {newState}!");
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.ForEach(c => c.APIStateChanged(this, newState));
|
||||
OnStateChange?.Invoke(oldState, newState);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => LocalUser.Value.Id > 1;
|
||||
|
||||
public void Queue(APIRequest request) => queue.Enqueue(request);
|
||||
|
||||
public event StateChangeDelegate OnStateChange;
|
||||
|
||||
public delegate void StateChangeDelegate(APIState oldState, APIState newState);
|
||||
|
||||
private void flushQueue(bool failOldRequests = true)
|
||||
{
|
||||
var oldQueue = queue;
|
||||
|
||||
//flush the queue.
|
||||
queue = new ConcurrentQueue<APIRequest>();
|
||||
|
||||
if (failOldRequests)
|
||||
{
|
||||
APIRequest req;
|
||||
while (oldQueue.TryDequeue(out req))
|
||||
req.Fail(new WebException(@"Disconnected from server"));
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout(bool clearUsername = true)
|
||||
{
|
||||
flushQueue();
|
||||
if (clearUsername) ProvidedUsername = null;
|
||||
password = null;
|
||||
authentication.Clear();
|
||||
LocalUser.Value = createGuestUser();
|
||||
}
|
||||
|
||||
private static User createGuestUser() => new User
|
||||
{
|
||||
Username = @"Guest",
|
||||
Id = 1,
|
||||
};
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? Token : string.Empty);
|
||||
config.Save();
|
||||
|
||||
flushQueue();
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public enum APIState
|
||||
{
|
||||
/// <summary>
|
||||
/// We cannot login (not enough credentials).
|
||||
/// </summary>
|
||||
Offline,
|
||||
|
||||
/// <summary>
|
||||
/// We are having connectivity issues.
|
||||
/// </summary>
|
||||
Failing,
|
||||
|
||||
/// <summary>
|
||||
/// We are in the process of (re-)connecting.
|
||||
/// </summary>
|
||||
Connecting,
|
||||
|
||||
/// <summary>
|
||||
/// We are online.
|
||||
/// </summary>
|
||||
Online
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class APIAccess : Component, IAPIProvider
|
||||
{
|
||||
private readonly OsuConfigManager config;
|
||||
private readonly OAuth authentication;
|
||||
|
||||
public string Endpoint = @"https://osu.ppy.sh";
|
||||
private const string client_id = @"5";
|
||||
private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
|
||||
|
||||
private ConcurrentQueue<APIRequest> queue = new ConcurrentQueue<APIRequest>();
|
||||
|
||||
/// <summary>
|
||||
/// The username/email provided by the user when initiating a login.
|
||||
/// </summary>
|
||||
public string ProvidedUsername { get; private set; }
|
||||
|
||||
private string password;
|
||||
|
||||
public Bindable<User> LocalUser { get; } = new Bindable<User>(createGuestUser());
|
||||
|
||||
protected bool HasLogin => authentication.Token.Value != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password);
|
||||
|
||||
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
|
||||
|
||||
private readonly Logger log;
|
||||
|
||||
public APIAccess(OsuConfigManager config)
|
||||
{
|
||||
this.config = config;
|
||||
|
||||
authentication = new OAuth(client_id, client_secret, Endpoint);
|
||||
log = Logger.GetLogger(LoggingTarget.Network);
|
||||
|
||||
ProvidedUsername = config.Get<string>(OsuSetting.Username);
|
||||
|
||||
authentication.TokenString = config.Get<string>(OsuSetting.Token);
|
||||
authentication.Token.ValueChanged += onTokenChanged;
|
||||
|
||||
Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private void onTokenChanged(OAuthToken token) => config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
||||
|
||||
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
|
||||
|
||||
internal new void Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Add(component);
|
||||
component.APIStateChanged(this, state);
|
||||
});
|
||||
}
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.Remove(component);
|
||||
});
|
||||
}
|
||||
|
||||
public string AccessToken => authentication.RequestAccessToken();
|
||||
|
||||
/// <summary>
|
||||
/// Number of consecutive requests which failed due to network issues.
|
||||
/// </summary>
|
||||
private int failureCount;
|
||||
|
||||
private void run()
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case APIState.Failing:
|
||||
//todo: replace this with a ping request.
|
||||
log.Add(@"In a failing state, waiting a bit before we try again...");
|
||||
Thread.Sleep(5000);
|
||||
if (queue.Count == 0)
|
||||
{
|
||||
log.Add(@"Queueing a ping request");
|
||||
Queue(new ListChannelsRequest { Timeout = 5000 });
|
||||
}
|
||||
break;
|
||||
case APIState.Offline:
|
||||
case APIState.Connecting:
|
||||
//work to restore a connection...
|
||||
if (!HasLogin)
|
||||
{
|
||||
State = APIState.Offline;
|
||||
Thread.Sleep(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
State = APIState.Connecting;
|
||||
|
||||
// save the username at this point, if the user requested for it to be.
|
||||
config.Set(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||
|
||||
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password))
|
||||
{
|
||||
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||
//NotificationOverlay.ShowMessage("Login failed!");
|
||||
log.Add(@"Login failed!");
|
||||
password = null;
|
||||
authentication.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
var userReq = new GetUserRequest();
|
||||
userReq.Success += u =>
|
||||
{
|
||||
LocalUser.Value = u;
|
||||
failureCount = 0;
|
||||
|
||||
//we're connected!
|
||||
State = APIState.Online;
|
||||
};
|
||||
|
||||
if (!handleRequest(userReq))
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||
// before actually going online.
|
||||
while (State != APIState.Online)
|
||||
Thread.Sleep(500);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//hard bail if we can't get a valid access token.
|
||||
if (authentication.RequestAccessToken() == null)
|
||||
{
|
||||
Logout(false);
|
||||
State = APIState.Offline;
|
||||
continue;
|
||||
}
|
||||
|
||||
//process the request queue.
|
||||
APIRequest req;
|
||||
while (queue.TryPeek(out req))
|
||||
{
|
||||
if (handleRequest(req))
|
||||
{
|
||||
//we have succeeded, so let's unqueue.
|
||||
queue.TryDequeue(out req);
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Login(string username, string password)
|
||||
{
|
||||
Debug.Assert(State == APIState.Offline);
|
||||
|
||||
ProvidedUsername = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a single API request.
|
||||
/// </summary>
|
||||
/// <param name="req">The request.</param>
|
||||
/// <returns>true if we should remove this request from the queue.</returns>
|
||||
private bool handleRequest(APIRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
|
||||
req.Perform(this);
|
||||
|
||||
//we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
if (IsLoggedIn)
|
||||
State = APIState.Online;
|
||||
|
||||
failureCount = 0;
|
||||
return true;
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
|
||||
|
||||
switch (statusCode)
|
||||
{
|
||||
case HttpStatusCode.Unauthorized:
|
||||
Logout(false);
|
||||
return true;
|
||||
case HttpStatusCode.RequestTimeout:
|
||||
failureCount++;
|
||||
log.Add($@"API failure count is now {failureCount}");
|
||||
|
||||
if (failureCount < 3)
|
||||
//we might try again at an api level.
|
||||
return false;
|
||||
|
||||
State = APIState.Failing;
|
||||
flushQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
req.Fail(we);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is TimeoutException)
|
||||
log.Add(@"API level timeout exception was hit");
|
||||
|
||||
req.Fail(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private APIState state;
|
||||
public APIState State
|
||||
{
|
||||
get { return state; }
|
||||
private set
|
||||
{
|
||||
APIState oldState = state;
|
||||
APIState newState = value;
|
||||
|
||||
state = value;
|
||||
|
||||
if (oldState != newState)
|
||||
{
|
||||
log.Add($@"We just went {newState}!");
|
||||
Scheduler.Add(delegate
|
||||
{
|
||||
components.ForEach(c => c.APIStateChanged(this, newState));
|
||||
OnStateChange?.Invoke(oldState, newState);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoggedIn => LocalUser.Value.Id > 1;
|
||||
|
||||
public void Queue(APIRequest request) => queue.Enqueue(request);
|
||||
|
||||
public event StateChangeDelegate OnStateChange;
|
||||
|
||||
public delegate void StateChangeDelegate(APIState oldState, APIState newState);
|
||||
|
||||
private void flushQueue(bool failOldRequests = true)
|
||||
{
|
||||
var oldQueue = queue;
|
||||
|
||||
//flush the queue.
|
||||
queue = new ConcurrentQueue<APIRequest>();
|
||||
|
||||
if (failOldRequests)
|
||||
{
|
||||
APIRequest req;
|
||||
while (oldQueue.TryDequeue(out req))
|
||||
req.Fail(new WebException(@"Disconnected from server"));
|
||||
}
|
||||
}
|
||||
|
||||
public void Logout(bool clearUsername = true)
|
||||
{
|
||||
flushQueue();
|
||||
if (clearUsername) ProvidedUsername = null;
|
||||
password = null;
|
||||
authentication.Clear();
|
||||
LocalUser.Value = createGuestUser();
|
||||
}
|
||||
|
||||
private static User createGuestUser() => new User
|
||||
{
|
||||
Username = @"Guest",
|
||||
Id = 1,
|
||||
};
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
flushQueue();
|
||||
cancellationToken.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public enum APIState
|
||||
{
|
||||
/// <summary>
|
||||
/// We cannot login (not enough credentials).
|
||||
/// </summary>
|
||||
Offline,
|
||||
|
||||
/// <summary>
|
||||
/// We are having connectivity issues.
|
||||
/// </summary>
|
||||
Failing,
|
||||
|
||||
/// <summary>
|
||||
/// We are in the process of (re-)connecting.
|
||||
/// </summary>
|
||||
Connecting,
|
||||
|
||||
/// <summary>
|
||||
/// We are online.
|
||||
/// </summary>
|
||||
Online
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public abstract class APIDownloadRequest : APIRequest
|
||||
{
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var request = new WebRequest(Uri);
|
||||
request.DownloadProgress += request_Progress;
|
||||
return request;
|
||||
}
|
||||
|
||||
private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total));
|
||||
|
||||
protected APIDownloadRequest()
|
||||
{
|
||||
base.Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess()
|
||||
{
|
||||
Success?.Invoke(WebRequest.ResponseData);
|
||||
}
|
||||
|
||||
public event APIProgressHandler Progress;
|
||||
|
||||
public new event APISuccessHandler<byte[]> Success;
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public abstract class APIDownloadRequest : APIRequest
|
||||
{
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var request = new WebRequest(Uri);
|
||||
request.DownloadProgress += request_Progress;
|
||||
return request;
|
||||
}
|
||||
|
||||
private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total));
|
||||
|
||||
protected APIDownloadRequest()
|
||||
{
|
||||
base.Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess()
|
||||
{
|
||||
Success?.Invoke(WebRequest.ResponseData);
|
||||
}
|
||||
|
||||
public event APIProgressHandler Progress;
|
||||
|
||||
public new event APISuccessHandler<byte[]> Success;
|
||||
}
|
||||
}
|
||||
|
@ -1,121 +1,121 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
/// <summary>
|
||||
/// An API request with a well-defined response type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
|
||||
public abstract class APIRequest<T> : APIRequest
|
||||
{
|
||||
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
||||
|
||||
protected APIRequest()
|
||||
{
|
||||
base.Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess()
|
||||
{
|
||||
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
|
||||
}
|
||||
|
||||
public new event APISuccessHandler<T> Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AN API request with no specified response type.
|
||||
/// </summary>
|
||||
public abstract class APIRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum amount of time before this request will fail.
|
||||
/// </summary>
|
||||
public int Timeout = WebRequest.DEFAULT_TIMEOUT;
|
||||
|
||||
protected virtual string Target => string.Empty;
|
||||
|
||||
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
|
||||
|
||||
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
|
||||
|
||||
private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds);
|
||||
|
||||
public bool ExceededTimeout => remainingTime == 0;
|
||||
|
||||
private DateTimeOffset? startTime;
|
||||
|
||||
protected APIAccess API;
|
||||
protected WebRequest WebRequest;
|
||||
|
||||
public event APISuccessHandler Success;
|
||||
public event APIFailureHandler Failure;
|
||||
|
||||
private bool cancelled;
|
||||
|
||||
private Action pendingFailure;
|
||||
|
||||
public void Perform(APIAccess api)
|
||||
{
|
||||
API = api;
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
if (startTime == null)
|
||||
startTime = DateTimeOffset.UtcNow;
|
||||
|
||||
if (remainingTime <= 0)
|
||||
throw new TimeoutException(@"API request timeout hit");
|
||||
|
||||
WebRequest = CreateWebRequest();
|
||||
WebRequest.AllowRetryOnTimeout = false;
|
||||
WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}");
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
|
||||
WebRequest.Perform();
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
api.Schedule(delegate { Success?.Invoke(); });
|
||||
}
|
||||
|
||||
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
||||
|
||||
public void Fail(Exception e)
|
||||
{
|
||||
cancelled = true;
|
||||
|
||||
WebRequest?.Abort();
|
||||
|
||||
pendingFailure = () => Failure?.Invoke(e);
|
||||
checkAndProcessFailure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checked for cancellation or error. Also queues up the Failed event if we can.
|
||||
/// </summary>
|
||||
/// <returns>Whether we are in a failed or cancelled state.</returns>
|
||||
private bool checkAndProcessFailure()
|
||||
{
|
||||
if (API == null || pendingFailure == null) return cancelled;
|
||||
|
||||
API.Schedule(pendingFailure);
|
||||
pendingFailure = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void APIFailureHandler(Exception e);
|
||||
public delegate void APISuccessHandler();
|
||||
public delegate void APIProgressHandler(long current, long total);
|
||||
public delegate void APISuccessHandler<in T>(T content);
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
/// <summary>
|
||||
/// An API request with a well-defined response type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
|
||||
public abstract class APIRequest<T> : APIRequest
|
||||
{
|
||||
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
||||
|
||||
protected APIRequest()
|
||||
{
|
||||
base.Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess()
|
||||
{
|
||||
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
|
||||
}
|
||||
|
||||
public new event APISuccessHandler<T> Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AN API request with no specified response type.
|
||||
/// </summary>
|
||||
public abstract class APIRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum amount of time before this request will fail.
|
||||
/// </summary>
|
||||
public int Timeout = WebRequest.DEFAULT_TIMEOUT;
|
||||
|
||||
protected virtual string Target => string.Empty;
|
||||
|
||||
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
|
||||
|
||||
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
|
||||
|
||||
private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds);
|
||||
|
||||
public bool ExceededTimeout => remainingTime == 0;
|
||||
|
||||
private DateTimeOffset? startTime;
|
||||
|
||||
protected APIAccess API;
|
||||
protected WebRequest WebRequest;
|
||||
|
||||
public event APISuccessHandler Success;
|
||||
public event APIFailureHandler Failure;
|
||||
|
||||
private bool cancelled;
|
||||
|
||||
private Action pendingFailure;
|
||||
|
||||
public void Perform(APIAccess api)
|
||||
{
|
||||
API = api;
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
if (startTime == null)
|
||||
startTime = DateTimeOffset.UtcNow;
|
||||
|
||||
if (remainingTime <= 0)
|
||||
throw new TimeoutException(@"API request timeout hit");
|
||||
|
||||
WebRequest = CreateWebRequest();
|
||||
WebRequest.AllowRetryOnTimeout = false;
|
||||
WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}");
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
|
||||
WebRequest.Perform();
|
||||
|
||||
if (checkAndProcessFailure())
|
||||
return;
|
||||
|
||||
api.Schedule(delegate { Success?.Invoke(); });
|
||||
}
|
||||
|
||||
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
||||
|
||||
public void Fail(Exception e)
|
||||
{
|
||||
cancelled = true;
|
||||
|
||||
WebRequest?.Abort();
|
||||
|
||||
pendingFailure = () => Failure?.Invoke(e);
|
||||
checkAndProcessFailure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checked for cancellation or error. Also queues up the Failed event if we can.
|
||||
/// </summary>
|
||||
/// <returns>Whether we are in a failed or cancelled state.</returns>
|
||||
private bool checkAndProcessFailure()
|
||||
{
|
||||
if (API == null || pendingFailure == null) return cancelled;
|
||||
|
||||
API.Schedule(pendingFailure);
|
||||
pendingFailure = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void APIFailureHandler(Exception e);
|
||||
public delegate void APISuccessHandler();
|
||||
public delegate void APIProgressHandler(long current, long total);
|
||||
public delegate void APISuccessHandler<in T>(T content);
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class DummyAPIAccess : IAPIProvider
|
||||
{
|
||||
public Bindable<User> LocalUser { get; } = new Bindable<User>(new User
|
||||
{
|
||||
Username = @"Dummy",
|
||||
Id = 1,
|
||||
});
|
||||
|
||||
public bool IsLoggedIn => true;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Queue(APIRequest request)
|
||||
{
|
||||
}
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class DummyAPIAccess : IAPIProvider
|
||||
{
|
||||
public Bindable<User> LocalUser { get; } = new Bindable<User>(new User
|
||||
{
|
||||
Username = @"Dummy",
|
||||
Id = 1,
|
||||
});
|
||||
|
||||
public bool IsLoggedIn => true;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Queue(APIRequest request)
|
||||
{
|
||||
}
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public interface IAPIProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The local user.
|
||||
/// </summary>
|
||||
Bindable<User> LocalUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the local user is logged in.
|
||||
/// </summary>
|
||||
bool IsLoggedIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue a new request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to perform.</param>
|
||||
void Queue(APIRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Register a component to receive state changes.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to register.</param>
|
||||
void Register(IOnlineComponent component);
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public interface IAPIProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The local user.
|
||||
/// </summary>
|
||||
Bindable<User> LocalUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the local user is logged in.
|
||||
/// </summary>
|
||||
bool IsLoggedIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue a new request.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to perform.</param>
|
||||
void Queue(APIRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Register a component to receive state changes.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to register.</param>
|
||||
void Register(IOnlineComponent component);
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public interface IOnlineComponent
|
||||
{
|
||||
void APIStateChanged(APIAccess api, APIState state);
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public interface IOnlineComponent
|
||||
{
|
||||
void APIStateChanged(APIAccess api, APIState state);
|
||||
}
|
||||
}
|
||||
|
@ -1,178 +1,185 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class OAuth
|
||||
{
|
||||
private readonly string clientId;
|
||||
private readonly string clientSecret;
|
||||
private readonly string endpoint;
|
||||
|
||||
public OAuthToken Token;
|
||||
|
||||
internal OAuth(string clientId, string clientSecret, string endpoint)
|
||||
{
|
||||
Debug.Assert(clientId != null);
|
||||
Debug.Assert(clientSecret != null);
|
||||
Debug.Assert(endpoint != null);
|
||||
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
internal bool AuthenticateWithLogin(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username)) return false;
|
||||
if (string.IsNullOrEmpty(password)) return false;
|
||||
|
||||
using (var req = new AccessTokenRequestPassword(username, password)
|
||||
{
|
||||
Url = $@"{endpoint}/oauth/token",
|
||||
Method = HttpMethod.POST,
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret
|
||||
})
|
||||
{
|
||||
try
|
||||
{
|
||||
req.Perform();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Token = req.ResponseObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool AuthenticateWithRefresh(string refresh)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var req = new AccessTokenRequestRefresh(refresh)
|
||||
{
|
||||
Url = $@"{endpoint}/oauth/token",
|
||||
Method = HttpMethod.POST,
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret
|
||||
})
|
||||
{
|
||||
req.Perform();
|
||||
|
||||
Token = req.ResponseObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//todo: potentially only kill the refresh token on certain exception types.
|
||||
Token = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object access_token_retrieval_lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Should be run before any API request to make sure we have a valid key.
|
||||
/// </summary>
|
||||
private bool ensureAccessToken()
|
||||
{
|
||||
// if we already have a valid access token, let's use it.
|
||||
if (accessTokenValid) return true;
|
||||
|
||||
// we want to ensure only a single authentication update is happening at once.
|
||||
lock (access_token_retrieval_lock)
|
||||
{
|
||||
// re-check if valid, in case another request completed and revalidated our access.
|
||||
if (accessTokenValid) return true;
|
||||
|
||||
// if not, let's try using our refresh token to request a new access token.
|
||||
if (!string.IsNullOrEmpty(Token?.RefreshToken))
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
AuthenticateWithRefresh(Token.RefreshToken);
|
||||
|
||||
return accessTokenValid;
|
||||
}
|
||||
}
|
||||
|
||||
private bool accessTokenValid => Token?.IsValid ?? false;
|
||||
|
||||
internal bool HasValidAccessToken => RequestAccessToken() != null;
|
||||
|
||||
internal string RequestAccessToken()
|
||||
{
|
||||
if (!ensureAccessToken()) return null;
|
||||
|
||||
return Token.AccessToken;
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
Token = null;
|
||||
}
|
||||
|
||||
private class AccessTokenRequestRefresh : AccessTokenRequest
|
||||
{
|
||||
internal readonly string RefreshToken;
|
||||
|
||||
internal AccessTokenRequestRefresh(string refreshToken)
|
||||
{
|
||||
RefreshToken = refreshToken;
|
||||
GrantType = @"refresh_token";
|
||||
}
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("refresh_token", RefreshToken);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
|
||||
private class AccessTokenRequestPassword : AccessTokenRequest
|
||||
{
|
||||
internal readonly string Username;
|
||||
internal readonly string Password;
|
||||
|
||||
internal AccessTokenRequestPassword(string username, string password)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
GrantType = @"password";
|
||||
}
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("username", Username);
|
||||
AddParameter("password", Password);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
|
||||
private class AccessTokenRequest : JsonWebRequest<OAuthToken>
|
||||
{
|
||||
protected string GrantType;
|
||||
|
||||
internal string ClientId;
|
||||
internal string ClientSecret;
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("grant_type", GrantType);
|
||||
AddParameter("client_id", ClientId);
|
||||
AddParameter("client_secret", ClientSecret);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public class OAuth
|
||||
{
|
||||
private readonly string clientId;
|
||||
private readonly string clientSecret;
|
||||
private readonly string endpoint;
|
||||
|
||||
public readonly Bindable<OAuthToken> Token = new Bindable<OAuthToken>();
|
||||
|
||||
public string TokenString
|
||||
{
|
||||
get => Token.Value?.ToString();
|
||||
set => Token.Value = string.IsNullOrEmpty(value) ? null : OAuthToken.Parse(value);
|
||||
}
|
||||
|
||||
internal OAuth(string clientId, string clientSecret, string endpoint)
|
||||
{
|
||||
Debug.Assert(clientId != null);
|
||||
Debug.Assert(clientSecret != null);
|
||||
Debug.Assert(endpoint != null);
|
||||
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
internal bool AuthenticateWithLogin(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username)) return false;
|
||||
if (string.IsNullOrEmpty(password)) return false;
|
||||
|
||||
using (var req = new AccessTokenRequestPassword(username, password)
|
||||
{
|
||||
Url = $@"{endpoint}/oauth/token",
|
||||
Method = HttpMethod.POST,
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret
|
||||
})
|
||||
{
|
||||
try
|
||||
{
|
||||
req.Perform();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Token.Value = req.ResponseObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool AuthenticateWithRefresh(string refresh)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var req = new AccessTokenRequestRefresh(refresh)
|
||||
{
|
||||
Url = $@"{endpoint}/oauth/token",
|
||||
Method = HttpMethod.POST,
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret
|
||||
})
|
||||
{
|
||||
req.Perform();
|
||||
|
||||
Token.Value = req.ResponseObject;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//todo: potentially only kill the refresh token on certain exception types.
|
||||
Token.Value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object access_token_retrieval_lock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Should be run before any API request to make sure we have a valid key.
|
||||
/// </summary>
|
||||
private bool ensureAccessToken()
|
||||
{
|
||||
// if we already have a valid access token, let's use it.
|
||||
if (accessTokenValid) return true;
|
||||
|
||||
// we want to ensure only a single authentication update is happening at once.
|
||||
lock (access_token_retrieval_lock)
|
||||
{
|
||||
// re-check if valid, in case another request completed and revalidated our access.
|
||||
if (accessTokenValid) return true;
|
||||
|
||||
// if not, let's try using our refresh token to request a new access token.
|
||||
if (!string.IsNullOrEmpty(Token.Value?.RefreshToken))
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
AuthenticateWithRefresh(Token.Value.RefreshToken);
|
||||
|
||||
return accessTokenValid;
|
||||
}
|
||||
}
|
||||
|
||||
private bool accessTokenValid => Token.Value?.IsValid ?? false;
|
||||
|
||||
internal bool HasValidAccessToken => RequestAccessToken() != null;
|
||||
|
||||
internal string RequestAccessToken()
|
||||
{
|
||||
if (!ensureAccessToken()) return null;
|
||||
|
||||
return Token.Value.AccessToken;
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
Token.Value = null;
|
||||
}
|
||||
|
||||
private class AccessTokenRequestRefresh : AccessTokenRequest
|
||||
{
|
||||
internal readonly string RefreshToken;
|
||||
|
||||
internal AccessTokenRequestRefresh(string refreshToken)
|
||||
{
|
||||
RefreshToken = refreshToken;
|
||||
GrantType = @"refresh_token";
|
||||
}
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("refresh_token", RefreshToken);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
|
||||
private class AccessTokenRequestPassword : AccessTokenRequest
|
||||
{
|
||||
internal readonly string Username;
|
||||
internal readonly string Password;
|
||||
|
||||
internal AccessTokenRequestPassword(string username, string password)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
GrantType = @"password";
|
||||
}
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("username", Username);
|
||||
AddParameter("password", Password);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
|
||||
private class AccessTokenRequest : JsonWebRequest<OAuthToken>
|
||||
{
|
||||
protected string GrantType;
|
||||
|
||||
internal string ClientId;
|
||||
internal string ClientSecret;
|
||||
|
||||
protected override void PrePerform()
|
||||
{
|
||||
AddParameter("grant_type", GrantType);
|
||||
AddParameter("client_id", ClientId);
|
||||
AddParameter("client_secret", ClientSecret);
|
||||
|
||||
base.PrePerform();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,63 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
[Serializable]
|
||||
public class OAuthToken
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 access token.
|
||||
/// </summary>
|
||||
[JsonProperty(@"access_token")]
|
||||
public string AccessToken;
|
||||
|
||||
[JsonProperty(@"expires_in")]
|
||||
public long ExpiresIn
|
||||
{
|
||||
get
|
||||
{
|
||||
return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid => !string.IsNullOrEmpty(AccessToken) && ExpiresIn > 30;
|
||||
|
||||
public long AccessTokenExpiry;
|
||||
|
||||
/// <summary>
|
||||
/// OAuth 2.0 refresh token.
|
||||
/// </summary>
|
||||
[JsonProperty(@"refresh_token")]
|
||||
public string RefreshToken;
|
||||
|
||||
public override string ToString() => $@"{AccessToken}|{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}|{RefreshToken}";
|
||||
|
||||
public static OAuthToken Parse(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] parts = value.Split('|');
|
||||
return new OAuthToken
|
||||
{
|
||||
AccessToken = parts[0],
|
||||
AccessTokenExpiry = long.Parse(parts[1], NumberFormatInfo.InvariantInfo),
|
||||
RefreshToken = parts[2]
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
[Serializable]
|
||||
public class OAuthToken
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth 2.0 access token.
|
||||
/// </summary>
|
||||
[JsonProperty(@"access_token")]
|
||||
public string AccessToken;
|
||||
|
||||
[JsonProperty(@"expires_in")]
|
||||
public long ExpiresIn
|
||||
{
|
||||
get
|
||||
{
|
||||
return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid => !string.IsNullOrEmpty(AccessToken) && ExpiresIn > 30;
|
||||
|
||||
public long AccessTokenExpiry;
|
||||
|
||||
/// <summary>
|
||||
/// OAuth 2.0 refresh token.
|
||||
/// </summary>
|
||||
[JsonProperty(@"refresh_token")]
|
||||
public string RefreshToken;
|
||||
|
||||
public override string ToString() => $@"{AccessToken}|{AccessTokenExpiry.ToString(NumberFormatInfo.InvariantInfo)}|{RefreshToken}";
|
||||
|
||||
public static OAuthToken Parse(string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] parts = value.Split('|');
|
||||
return new OAuthToken
|
||||
{
|
||||
AccessToken = parts[0],
|
||||
AccessTokenExpiry = long.Parse(parts[1], NumberFormatInfo.InvariantInfo),
|
||||
RefreshToken = parts[2]
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,145 +1,145 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
|
||||
{
|
||||
[JsonProperty(@"covers")]
|
||||
private BeatmapSetOnlineCovers covers { get; set; }
|
||||
|
||||
[JsonProperty(@"preview_url")]
|
||||
private string preview { get; set; }
|
||||
|
||||
[JsonProperty(@"play_count")]
|
||||
private int playCount { get; set; }
|
||||
|
||||
[JsonProperty(@"favourite_count")]
|
||||
private int favouriteCount { get; set; }
|
||||
|
||||
[JsonProperty(@"bpm")]
|
||||
private double bpm { get; set; }
|
||||
|
||||
[JsonProperty(@"video")]
|
||||
private bool hasVideo { get; set; }
|
||||
|
||||
[JsonProperty(@"status")]
|
||||
private BeatmapSetOnlineStatus status { get; set; }
|
||||
|
||||
[JsonProperty(@"submitted_date")]
|
||||
private DateTimeOffset submitted { get; set; }
|
||||
|
||||
[JsonProperty(@"ranked_date")]
|
||||
private DateTimeOffset ranked { get; set; }
|
||||
|
||||
[JsonProperty(@"last_updated")]
|
||||
private DateTimeOffset lastUpdated { get; set; }
|
||||
|
||||
[JsonProperty(@"user_id")]
|
||||
private long creatorId {
|
||||
set { Author.Id = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmaps")]
|
||||
private IEnumerable<APIResponseBeatmap> beatmaps { get; set; }
|
||||
|
||||
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
|
||||
{
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = OnlineBeatmapSetID,
|
||||
Metadata = this,
|
||||
OnlineInfo = new BeatmapSetOnlineInfo
|
||||
{
|
||||
Covers = covers,
|
||||
Preview = preview,
|
||||
PlayCount = playCount,
|
||||
FavouriteCount = favouriteCount,
|
||||
BPM = bpm,
|
||||
Status = status,
|
||||
HasVideo = hasVideo,
|
||||
Submitted = submitted,
|
||||
Ranked = ranked,
|
||||
LastUpdated = lastUpdated,
|
||||
},
|
||||
Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private class APIResponseBeatmap : BeatmapMetadata
|
||||
{
|
||||
[JsonProperty(@"id")]
|
||||
private int onlineBeatmapID { get; set; }
|
||||
|
||||
[JsonProperty(@"playcount")]
|
||||
private int playCount { get; set; }
|
||||
|
||||
[JsonProperty(@"passcount")]
|
||||
private int passCount { get; set; }
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
private int ruleset { get; set; }
|
||||
|
||||
[JsonProperty(@"difficulty_rating")]
|
||||
private double starDifficulty { get; set; }
|
||||
|
||||
[JsonProperty(@"drain")]
|
||||
private float drainRate { get; set; }
|
||||
|
||||
[JsonProperty(@"cs")]
|
||||
private float circleSize { get; set; }
|
||||
|
||||
[JsonProperty(@"ar")]
|
||||
private float approachRate { get; set; }
|
||||
|
||||
[JsonProperty(@"accuracy")]
|
||||
private float overallDifficulty { get; set; }
|
||||
|
||||
[JsonProperty(@"total_length")]
|
||||
private double length { get; set; }
|
||||
|
||||
[JsonProperty(@"count_circles")]
|
||||
private int circleCount { get; set; }
|
||||
|
||||
[JsonProperty(@"count_sliders")]
|
||||
private int sliderCount { get; set; }
|
||||
|
||||
[JsonProperty(@"version")]
|
||||
private string version { get; set; }
|
||||
|
||||
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
|
||||
{
|
||||
return new BeatmapInfo
|
||||
{
|
||||
Metadata = this,
|
||||
Ruleset = rulesets.GetRuleset(ruleset),
|
||||
StarDifficulty = starDifficulty,
|
||||
OnlineBeatmapID = onlineBeatmapID,
|
||||
Version = version,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
DrainRate = drainRate,
|
||||
CircleSize = circleSize,
|
||||
ApproachRate = approachRate,
|
||||
OverallDifficulty = overallDifficulty,
|
||||
},
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
PlayCount = playCount,
|
||||
PassCount = passCount,
|
||||
Length = length,
|
||||
CircleCount = circleCount,
|
||||
SliderCount = sliderCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
|
||||
{
|
||||
[JsonProperty(@"covers")]
|
||||
private BeatmapSetOnlineCovers covers { get; set; }
|
||||
|
||||
[JsonProperty(@"preview_url")]
|
||||
private string preview { get; set; }
|
||||
|
||||
[JsonProperty(@"play_count")]
|
||||
private int playCount { get; set; }
|
||||
|
||||
[JsonProperty(@"favourite_count")]
|
||||
private int favouriteCount { get; set; }
|
||||
|
||||
[JsonProperty(@"bpm")]
|
||||
private double bpm { get; set; }
|
||||
|
||||
[JsonProperty(@"video")]
|
||||
private bool hasVideo { get; set; }
|
||||
|
||||
[JsonProperty(@"status")]
|
||||
private BeatmapSetOnlineStatus status { get; set; }
|
||||
|
||||
[JsonProperty(@"submitted_date")]
|
||||
private DateTimeOffset submitted { get; set; }
|
||||
|
||||
[JsonProperty(@"ranked_date")]
|
||||
private DateTimeOffset ranked { get; set; }
|
||||
|
||||
[JsonProperty(@"last_updated")]
|
||||
private DateTimeOffset lastUpdated { get; set; }
|
||||
|
||||
[JsonProperty(@"user_id")]
|
||||
private long creatorId {
|
||||
set { Author.Id = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmaps")]
|
||||
private IEnumerable<APIResponseBeatmap> beatmaps { get; set; }
|
||||
|
||||
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
|
||||
{
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
OnlineBeatmapSetID = OnlineBeatmapSetID,
|
||||
Metadata = this,
|
||||
OnlineInfo = new BeatmapSetOnlineInfo
|
||||
{
|
||||
Covers = covers,
|
||||
Preview = preview,
|
||||
PlayCount = playCount,
|
||||
FavouriteCount = favouriteCount,
|
||||
BPM = bpm,
|
||||
Status = status,
|
||||
HasVideo = hasVideo,
|
||||
Submitted = submitted,
|
||||
Ranked = ranked,
|
||||
LastUpdated = lastUpdated,
|
||||
},
|
||||
Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private class APIResponseBeatmap : BeatmapMetadata
|
||||
{
|
||||
[JsonProperty(@"id")]
|
||||
private int onlineBeatmapID { get; set; }
|
||||
|
||||
[JsonProperty(@"playcount")]
|
||||
private int playCount { get; set; }
|
||||
|
||||
[JsonProperty(@"passcount")]
|
||||
private int passCount { get; set; }
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
private int ruleset { get; set; }
|
||||
|
||||
[JsonProperty(@"difficulty_rating")]
|
||||
private double starDifficulty { get; set; }
|
||||
|
||||
[JsonProperty(@"drain")]
|
||||
private float drainRate { get; set; }
|
||||
|
||||
[JsonProperty(@"cs")]
|
||||
private float circleSize { get; set; }
|
||||
|
||||
[JsonProperty(@"ar")]
|
||||
private float approachRate { get; set; }
|
||||
|
||||
[JsonProperty(@"accuracy")]
|
||||
private float overallDifficulty { get; set; }
|
||||
|
||||
[JsonProperty(@"total_length")]
|
||||
private double length { get; set; }
|
||||
|
||||
[JsonProperty(@"count_circles")]
|
||||
private int circleCount { get; set; }
|
||||
|
||||
[JsonProperty(@"count_sliders")]
|
||||
private int sliderCount { get; set; }
|
||||
|
||||
[JsonProperty(@"version")]
|
||||
private string version { get; set; }
|
||||
|
||||
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
|
||||
{
|
||||
return new BeatmapInfo
|
||||
{
|
||||
Metadata = this,
|
||||
Ruleset = rulesets.GetRuleset(ruleset),
|
||||
StarDifficulty = starDifficulty,
|
||||
OnlineBeatmapID = onlineBeatmapID,
|
||||
Version = version,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
DrainRate = drainRate,
|
||||
CircleSize = circleSize,
|
||||
ApproachRate = approachRate,
|
||||
OverallDifficulty = overallDifficulty,
|
||||
},
|
||||
OnlineInfo = new BeatmapOnlineInfo
|
||||
{
|
||||
PlayCount = playCount,
|
||||
PassCount = passCount,
|
||||
Length = length,
|
||||
CircleCount = circleCount,
|
||||
SliderCount = sliderCount,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class DownloadBeatmapSetRequest : APIDownloadRequest
|
||||
{
|
||||
public readonly BeatmapSetInfo BeatmapSet;
|
||||
|
||||
public Action<float> DownloadProgressed;
|
||||
|
||||
private readonly bool noVideo;
|
||||
|
||||
public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo)
|
||||
{
|
||||
this.noVideo = noVideo;
|
||||
BeatmapSet = set;
|
||||
|
||||
Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total);
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class DownloadBeatmapSetRequest : APIDownloadRequest
|
||||
{
|
||||
public readonly BeatmapSetInfo BeatmapSet;
|
||||
|
||||
public Action<float> DownloadProgressed;
|
||||
|
||||
private readonly bool noVideo;
|
||||
|
||||
public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo)
|
||||
{
|
||||
this.noVideo = noVideo;
|
||||
BeatmapSet = set;
|
||||
|
||||
Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total);
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}";
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,46 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBeatmapDetailsRequest : APIRequest<GetBeatmapDetailsResponse>
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
|
||||
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
||||
|
||||
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{lookupString}";
|
||||
}
|
||||
|
||||
public class GetBeatmapDetailsResponse : BeatmapMetrics
|
||||
{
|
||||
//the online API returns some metrics as a nested object.
|
||||
[JsonProperty(@"failtimes")]
|
||||
private BeatmapMetrics failTimes
|
||||
{
|
||||
set
|
||||
{
|
||||
Fails = value.Fails;
|
||||
Retries = value.Retries;
|
||||
}
|
||||
}
|
||||
|
||||
//and other metrics in the beatmap set.
|
||||
[JsonProperty(@"beatmapset")]
|
||||
private BeatmapMetrics beatmapSet
|
||||
{
|
||||
set
|
||||
{
|
||||
Ratings = value.Ratings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBeatmapDetailsRequest : APIRequest<GetBeatmapDetailsResponse>
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
|
||||
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
||||
|
||||
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{lookupString}";
|
||||
}
|
||||
|
||||
public class GetBeatmapDetailsResponse : BeatmapMetrics
|
||||
{
|
||||
//the online API returns some metrics as a nested object.
|
||||
[JsonProperty(@"failtimes")]
|
||||
private BeatmapMetrics failTimes
|
||||
{
|
||||
set
|
||||
{
|
||||
Fails = value.Fails;
|
||||
Retries = value.Retries;
|
||||
}
|
||||
}
|
||||
|
||||
//and other metrics in the beatmap set.
|
||||
[JsonProperty(@"beatmapset")]
|
||||
private BeatmapMetrics beatmapSet
|
||||
{
|
||||
set
|
||||
{
|
||||
Ratings = value.Ratings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet>
|
||||
{
|
||||
private readonly int beatmapSetId;
|
||||
|
||||
public GetBeatmapSetRequest(int beatmapSetId)
|
||||
{
|
||||
this.beatmapSetId = beatmapSetId;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmapsets/{beatmapSetId}";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet>
|
||||
{
|
||||
private readonly int beatmapSetId;
|
||||
|
||||
public GetBeatmapSetRequest(int beatmapSetId)
|
||||
{
|
||||
this.beatmapSetId = beatmapSetId;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmapsets/{beatmapSetId}";
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetFriendsRequest : APIRequest<List<User>>
|
||||
{
|
||||
protected override string Target => @"friends";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetFriendsRequest : APIRequest<List<User>>
|
||||
{
|
||||
protected override string Target => @"friends";
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,38 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetMessagesRequest : APIMessagesRequest
|
||||
{
|
||||
private readonly IEnumerable<Channel> channels;
|
||||
|
||||
public GetMessagesRequest(IEnumerable<Channel> channels, long? sinceId) : base(sinceId)
|
||||
{
|
||||
if (channels == null)
|
||||
throw new ArgumentNullException(nameof(channels));
|
||||
if (channels.Any(c => c.Target != TargetType.Channel))
|
||||
throw new ArgumentException("All channels in the argument channels must have the targettype Channel");
|
||||
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
string channelString = string.Join(",", channels.Select(x => x.Id));
|
||||
|
||||
var req = base.CreateWebRequest();
|
||||
req.AddParameter(@"channels", channelString);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetMessagesRequest : APIMessagesRequest
|
||||
{
|
||||
private readonly IEnumerable<Channel> channels;
|
||||
|
||||
public GetMessagesRequest(IEnumerable<Channel> channels, long? sinceId) : base(sinceId)
|
||||
{
|
||||
if (channels == null)
|
||||
throw new ArgumentNullException(nameof(channels));
|
||||
if (channels.Any(c => c.Target != TargetType.Channel))
|
||||
throw new ArgumentException("All channels in the argument channels must have the targettype Channel");
|
||||
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
string channelString = string.Join(",", channels.Select(x => x.Id));
|
||||
|
||||
var req = base.CreateWebRequest();
|
||||
req.AddParameter(@"channels", channelString);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages";
|
||||
}
|
||||
}
|
||||
|
@ -1,166 +1,166 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetScoresRequest : APIRequest<GetScoresResponse>
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly LeaderboardScope scope;
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global)
|
||||
{
|
||||
if (!beatmap.OnlineBeatmapID.HasValue)
|
||||
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
|
||||
|
||||
if (scope == LeaderboardScope.Local)
|
||||
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
|
||||
|
||||
this.beatmap = beatmap;
|
||||
this.scope = scope;
|
||||
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
|
||||
|
||||
Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess(GetScoresResponse r)
|
||||
{
|
||||
foreach (OnlineScore score in r.Scores)
|
||||
score.ApplyBeatmap(beatmap);
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
req.Timeout = 30000;
|
||||
req.AddParameter(@"type", scope.ToString().ToLowerInvariant());
|
||||
req.AddParameter(@"mode", ruleset.ShortName);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores";
|
||||
}
|
||||
|
||||
public class GetScoresResponse
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public IEnumerable<OnlineScore> Scores;
|
||||
}
|
||||
|
||||
public class OnlineScore : Score
|
||||
{
|
||||
[JsonProperty(@"score")]
|
||||
private double totalScore
|
||||
{
|
||||
set { TotalScore = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"max_combo")]
|
||||
private int maxCombo
|
||||
{
|
||||
set { MaxCombo = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"user")]
|
||||
private User user
|
||||
{
|
||||
set { User = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"replay_data")]
|
||||
private Replay replay
|
||||
{
|
||||
set { Replay = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
public int OnlineRulesetID { get; set; }
|
||||
|
||||
[JsonProperty(@"score_id")]
|
||||
private long onlineScoreID
|
||||
{
|
||||
set { OnlineScoreID = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"created_at")]
|
||||
private DateTimeOffset date
|
||||
{
|
||||
set { Date = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
private BeatmapInfo beatmap
|
||||
{
|
||||
set { Beatmap = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
private BeatmapMetadata metadata
|
||||
{
|
||||
set { Beatmap.Metadata = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"statistics")]
|
||||
private Dictionary<string, object> jsonStats
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var kvp in value)
|
||||
{
|
||||
HitResult newKey;
|
||||
switch (kvp.Key)
|
||||
{
|
||||
case @"count_300":
|
||||
newKey = HitResult.Great;
|
||||
break;
|
||||
case @"count_100":
|
||||
newKey = HitResult.Good;
|
||||
break;
|
||||
case @"count_50":
|
||||
newKey = HitResult.Meh;
|
||||
break;
|
||||
case @"count_miss":
|
||||
newKey = HitResult.Miss;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
Statistics.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(@"mods")]
|
||||
private string[] modStrings { get; set; }
|
||||
|
||||
public void ApplyBeatmap(BeatmapInfo beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
ApplyRuleset(beatmap.Ruleset);
|
||||
}
|
||||
|
||||
public void ApplyRuleset(RulesetInfo ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
|
||||
// Evaluate the mod string
|
||||
Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Framework.IO.Network;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetScoresRequest : APIRequest<GetScoresResponse>
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly LeaderboardScope scope;
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
||||
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global)
|
||||
{
|
||||
if (!beatmap.OnlineBeatmapID.HasValue)
|
||||
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
|
||||
|
||||
if (scope == LeaderboardScope.Local)
|
||||
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
|
||||
|
||||
this.beatmap = beatmap;
|
||||
this.scope = scope;
|
||||
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
|
||||
|
||||
Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess(GetScoresResponse r)
|
||||
{
|
||||
foreach (OnlineScore score in r.Scores)
|
||||
score.ApplyBeatmap(beatmap);
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
req.Timeout = 30000;
|
||||
req.AddParameter(@"type", scope.ToString().ToLowerInvariant());
|
||||
req.AddParameter(@"mode", ruleset.ShortName);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores";
|
||||
}
|
||||
|
||||
public class GetScoresResponse
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public IEnumerable<OnlineScore> Scores;
|
||||
}
|
||||
|
||||
public class OnlineScore : Score
|
||||
{
|
||||
[JsonProperty(@"score")]
|
||||
private double totalScore
|
||||
{
|
||||
set { TotalScore = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"max_combo")]
|
||||
private int maxCombo
|
||||
{
|
||||
set { MaxCombo = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"user")]
|
||||
private User user
|
||||
{
|
||||
set { User = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"replay_data")]
|
||||
private Replay replay
|
||||
{
|
||||
set { Replay = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"mode_int")]
|
||||
public int OnlineRulesetID { get; set; }
|
||||
|
||||
[JsonProperty(@"score_id")]
|
||||
private long onlineScoreID
|
||||
{
|
||||
set { OnlineScoreID = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"created_at")]
|
||||
private DateTimeOffset date
|
||||
{
|
||||
set { Date = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmap")]
|
||||
private BeatmapInfo beatmap
|
||||
{
|
||||
set { Beatmap = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"beatmapset")]
|
||||
private BeatmapMetadata metadata
|
||||
{
|
||||
set { Beatmap.Metadata = value; }
|
||||
}
|
||||
|
||||
[JsonProperty(@"statistics")]
|
||||
private Dictionary<string, object> jsonStats
|
||||
{
|
||||
set
|
||||
{
|
||||
foreach (var kvp in value)
|
||||
{
|
||||
HitResult newKey;
|
||||
switch (kvp.Key)
|
||||
{
|
||||
case @"count_300":
|
||||
newKey = HitResult.Great;
|
||||
break;
|
||||
case @"count_100":
|
||||
newKey = HitResult.Good;
|
||||
break;
|
||||
case @"count_50":
|
||||
newKey = HitResult.Meh;
|
||||
break;
|
||||
case @"count_miss":
|
||||
newKey = HitResult.Miss;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
Statistics.Add(newKey, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(@"mods")]
|
||||
private string[] modStrings { get; set; }
|
||||
|
||||
public void ApplyBeatmap(BeatmapInfo beatmap)
|
||||
{
|
||||
Beatmap = beatmap;
|
||||
ApplyRuleset(beatmap.Ruleset);
|
||||
}
|
||||
|
||||
public void ApplyRuleset(RulesetInfo ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
|
||||
// Evaluate the mod string
|
||||
Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Humanizer;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserBeatmapsRequest : APIRequest<List<APIResponseBeatmapSet>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
private readonly BeatmapSetType type;
|
||||
|
||||
public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}";
|
||||
}
|
||||
|
||||
public enum BeatmapSetType
|
||||
{
|
||||
Favourite,
|
||||
RankedAndApproved,
|
||||
Unranked,
|
||||
Graveyard
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Humanizer;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserBeatmapsRequest : APIRequest<List<APIResponseBeatmapSet>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
private readonly BeatmapSetType type;
|
||||
|
||||
public GetUserBeatmapsRequest(long userId, BeatmapSetType type, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}?offset={offset}";
|
||||
}
|
||||
|
||||
public enum BeatmapSetType
|
||||
{
|
||||
Favourite,
|
||||
RankedAndApproved,
|
||||
Unranked,
|
||||
Graveyard
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,48 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<MostPlayedBeatmap>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}";
|
||||
}
|
||||
|
||||
public class MostPlayedBeatmap
|
||||
{
|
||||
[JsonProperty("beatmap_id")]
|
||||
public int BeatmapID;
|
||||
|
||||
[JsonProperty("count")]
|
||||
public int PlayCount;
|
||||
|
||||
[JsonProperty]
|
||||
private BeatmapInfo beatmap;
|
||||
|
||||
[JsonProperty]
|
||||
private APIResponseBeatmapSet beatmapSet;
|
||||
|
||||
public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
|
||||
{
|
||||
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
||||
beatmap.BeatmapSet = setInfo;
|
||||
beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
|
||||
beatmap.Metadata = setInfo.Metadata;
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<MostPlayedBeatmap>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}";
|
||||
}
|
||||
|
||||
public class MostPlayedBeatmap
|
||||
{
|
||||
[JsonProperty("beatmap_id")]
|
||||
public int BeatmapID;
|
||||
|
||||
[JsonProperty("count")]
|
||||
public int PlayCount;
|
||||
|
||||
[JsonProperty]
|
||||
private BeatmapInfo beatmap;
|
||||
|
||||
[JsonProperty]
|
||||
private APIResponseBeatmapSet beatmapSet;
|
||||
|
||||
public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
|
||||
{
|
||||
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
||||
beatmap.BeatmapSet = setInfo;
|
||||
beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
|
||||
beatmap.Metadata = setInfo.Metadata;
|
||||
return beatmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,130 +1,130 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using Humanizer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRecentActivitiesRequest : APIRequest<List<RecentActivity>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserRecentActivitiesRequest(long userId, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
protected override string Target => $"users/{userId}/recent_activity?offset={offset}";
|
||||
}
|
||||
|
||||
public class RecentActivity
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int ID;
|
||||
|
||||
[JsonProperty("createdAt")]
|
||||
public DateTimeOffset CreatedAt;
|
||||
|
||||
[JsonProperty]
|
||||
private string type
|
||||
{
|
||||
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize());
|
||||
}
|
||||
|
||||
public RecentActivityType Type;
|
||||
|
||||
[JsonProperty]
|
||||
private string scoreRank
|
||||
{
|
||||
set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value);
|
||||
}
|
||||
|
||||
public ScoreRank ScoreRank;
|
||||
|
||||
[JsonProperty("rank")]
|
||||
public int Rank;
|
||||
|
||||
[JsonProperty("approval")]
|
||||
public BeatmapApproval Approval;
|
||||
|
||||
[JsonProperty("count")]
|
||||
public int Count;
|
||||
|
||||
[JsonProperty("mode")]
|
||||
public string Mode;
|
||||
|
||||
[JsonProperty("beatmap")]
|
||||
public RecentActivityBeatmap Beatmap;
|
||||
|
||||
[JsonProperty("beatmapset")]
|
||||
public RecentActivityBeatmap Beatmapset;
|
||||
|
||||
[JsonProperty("user")]
|
||||
public RecentActivityUser User;
|
||||
|
||||
[JsonProperty("achievement")]
|
||||
public RecentActivityAchievement Achievement;
|
||||
|
||||
public class RecentActivityBeatmap
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url;
|
||||
}
|
||||
|
||||
public class RecentActivityUser
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Username;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url;
|
||||
|
||||
[JsonProperty("previousUsername")]
|
||||
public string PreviousUsername;
|
||||
}
|
||||
|
||||
public class RecentActivityAchievement
|
||||
{
|
||||
[JsonProperty("slug")]
|
||||
public string Slug;
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum RecentActivityType
|
||||
{
|
||||
Achievement,
|
||||
BeatmapPlaycount,
|
||||
BeatmapsetApprove,
|
||||
BeatmapsetDelete,
|
||||
BeatmapsetRevive,
|
||||
BeatmapsetUpdate,
|
||||
BeatmapsetUpload,
|
||||
Medal,
|
||||
Rank,
|
||||
RankLost,
|
||||
UserSupportAgain,
|
||||
UserSupportFirst,
|
||||
UserSupportGift,
|
||||
UsernameChange,
|
||||
}
|
||||
|
||||
public enum BeatmapApproval
|
||||
{
|
||||
Ranked,
|
||||
Approved,
|
||||
Qualified,
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using Humanizer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRecentActivitiesRequest : APIRequest<List<RecentActivity>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserRecentActivitiesRequest(long userId, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
protected override string Target => $"users/{userId}/recent_activity?offset={offset}";
|
||||
}
|
||||
|
||||
public class RecentActivity
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int ID;
|
||||
|
||||
[JsonProperty("createdAt")]
|
||||
public DateTimeOffset CreatedAt;
|
||||
|
||||
[JsonProperty]
|
||||
private string type
|
||||
{
|
||||
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize());
|
||||
}
|
||||
|
||||
public RecentActivityType Type;
|
||||
|
||||
[JsonProperty]
|
||||
private string scoreRank
|
||||
{
|
||||
set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value);
|
||||
}
|
||||
|
||||
public ScoreRank ScoreRank;
|
||||
|
||||
[JsonProperty("rank")]
|
||||
public int Rank;
|
||||
|
||||
[JsonProperty("approval")]
|
||||
public BeatmapApproval Approval;
|
||||
|
||||
[JsonProperty("count")]
|
||||
public int Count;
|
||||
|
||||
[JsonProperty("mode")]
|
||||
public string Mode;
|
||||
|
||||
[JsonProperty("beatmap")]
|
||||
public RecentActivityBeatmap Beatmap;
|
||||
|
||||
[JsonProperty("beatmapset")]
|
||||
public RecentActivityBeatmap Beatmapset;
|
||||
|
||||
[JsonProperty("user")]
|
||||
public RecentActivityUser User;
|
||||
|
||||
[JsonProperty("achievement")]
|
||||
public RecentActivityAchievement Achievement;
|
||||
|
||||
public class RecentActivityBeatmap
|
||||
{
|
||||
[JsonProperty("title")]
|
||||
public string Title;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url;
|
||||
}
|
||||
|
||||
public class RecentActivityUser
|
||||
{
|
||||
[JsonProperty("username")]
|
||||
public string Username;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public string Url;
|
||||
|
||||
[JsonProperty("previousUsername")]
|
||||
public string PreviousUsername;
|
||||
}
|
||||
|
||||
public class RecentActivityAchievement
|
||||
{
|
||||
[JsonProperty("slug")]
|
||||
public string Slug;
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum RecentActivityType
|
||||
{
|
||||
Achievement,
|
||||
BeatmapPlaycount,
|
||||
BeatmapsetApprove,
|
||||
BeatmapsetDelete,
|
||||
BeatmapsetRevive,
|
||||
BeatmapsetUpdate,
|
||||
BeatmapsetUpload,
|
||||
Medal,
|
||||
Rank,
|
||||
RankLost,
|
||||
UserSupportAgain,
|
||||
UserSupportFirst,
|
||||
UserSupportGift,
|
||||
UsernameChange,
|
||||
}
|
||||
|
||||
public enum BeatmapApproval
|
||||
{
|
||||
Ranked,
|
||||
Approved,
|
||||
Qualified,
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRequest : APIRequest<User>
|
||||
{
|
||||
private long? userId;
|
||||
|
||||
public GetUserRequest(long? userId = null)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserRequest : APIRequest<User>
|
||||
{
|
||||
private long? userId;
|
||||
|
||||
public GetUserRequest(long? userId = null)
|
||||
{
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me";
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserScoresRequest : APIRequest<List<OnlineScore>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly ScoreType type;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserScoresRequest(long userId, ScoreType type, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.type = type;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}";
|
||||
}
|
||||
|
||||
public enum ScoreType
|
||||
{
|
||||
Best,
|
||||
Firsts,
|
||||
Recent
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserScoresRequest : APIRequest<List<OnlineScore>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly ScoreType type;
|
||||
private readonly int offset;
|
||||
|
||||
public GetUserScoresRequest(long userId, ScoreType type, int offset = 0)
|
||||
{
|
||||
this.userId = userId;
|
||||
this.type = type;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}";
|
||||
}
|
||||
|
||||
public enum ScoreType
|
||||
{
|
||||
Best,
|
||||
Firsts,
|
||||
Recent
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUsersRequest : APIRequest<List<RankingEntry>>
|
||||
{
|
||||
protected override string Target => @"rankings/osu/performance";
|
||||
}
|
||||
|
||||
public class RankingEntry
|
||||
{
|
||||
[JsonProperty]
|
||||
public User User;
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUsersRequest : APIRequest<List<RankingEntry>>
|
||||
{
|
||||
protected override string Target => @"rankings/osu/performance";
|
||||
}
|
||||
|
||||
public class RankingEntry
|
||||
{
|
||||
[JsonProperty]
|
||||
public User User;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class ListChannelsRequest : APIRequest<List<Channel>>
|
||||
{
|
||||
protected override string Target => @"chat/channels";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class ListChannelsRequest : APIRequest<List<Channel>>
|
||||
{
|
||||
protected override string Target => @"chat/channels";
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,34 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class PostMessageRequest : APIRequest<Message>
|
||||
{
|
||||
private readonly Message message;
|
||||
|
||||
public PostMessageRequest(Message message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
req.Method = HttpMethod.POST;
|
||||
req.AddParameter(@"target_type", message.TargetType.GetDescription());
|
||||
req.AddParameter(@"target_id", message.TargetId.ToString());
|
||||
req.AddParameter(@"is_action", message.IsAction.ToString().ToLower());
|
||||
req.AddParameter(@"message", message.Content);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class PostMessageRequest : APIRequest<Message>
|
||||
{
|
||||
private readonly Message message;
|
||||
|
||||
public PostMessageRequest(Message message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
req.Method = HttpMethod.POST;
|
||||
req.AddParameter(@"target_type", message.TargetType.GetDescription());
|
||||
req.AddParameter(@"target_id", message.TargetId.ToString());
|
||||
req.AddParameter(@"is_action", message.IsAction.ToString().ToLower());
|
||||
req.AddParameter(@"message", message.Content);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages";
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIResponseBeatmapSet>>
|
||||
{
|
||||
private readonly string query;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly RankStatus rankStatus;
|
||||
private readonly DirectSortCriteria sortCriteria;
|
||||
private readonly SortDirection direction;
|
||||
private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending)
|
||||
{
|
||||
this.query = System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
this.rankStatus = rankStatus;
|
||||
this.sortCriteria = sortCriteria;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIResponseBeatmapSet>>
|
||||
{
|
||||
private readonly string query;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly RankStatus rankStatus;
|
||||
private readonly DirectSortCriteria sortCriteria;
|
||||
private readonly SortDirection direction;
|
||||
private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending)
|
||||
{
|
||||
this.query = System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
this.rankStatus = rankStatus;
|
||||
this.sortCriteria = sortCriteria;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}";
|
||||
}
|
||||
}
|
||||
|
@ -1,123 +1,123 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class Channel
|
||||
{
|
||||
public const int MAX_HISTORY = 300;
|
||||
|
||||
[JsonProperty(@"name")]
|
||||
public string Name;
|
||||
|
||||
[JsonProperty(@"description")]
|
||||
public string Topic;
|
||||
|
||||
[JsonProperty(@"type")]
|
||||
public string Type;
|
||||
|
||||
[JsonProperty(@"channel_id")]
|
||||
public long Id;
|
||||
|
||||
[JsonConstructor]
|
||||
public Channel()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contructs a private channel
|
||||
/// TODO this class needs to be serialized from something like channels/private, instead of creating from a contructor
|
||||
/// </summary>
|
||||
/// <param name="user">The user </param>
|
||||
public Channel(User user)
|
||||
{
|
||||
Target = TargetType.User;
|
||||
Name = user.Username;
|
||||
Id = user.Id;
|
||||
JoinedUsers.Add(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains every joined user except yourself
|
||||
/// </summary>
|
||||
public ObservableCollection<User> JoinedUsers = new ObservableCollection<User>();
|
||||
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
|
||||
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
|
||||
|
||||
public event Action<IEnumerable<Message>> NewMessagesArrived;
|
||||
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
|
||||
public event Action<Message> MessageRemoved;
|
||||
|
||||
public Bindable<bool> Joined = new Bindable<bool>();
|
||||
public TargetType Target { get; set; }
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
public void AddLocalEcho(LocalEchoMessage message)
|
||||
{
|
||||
pendingMessages.Add(message);
|
||||
Messages.Add(message);
|
||||
|
||||
NewMessagesArrived?.Invoke(new[] { message });
|
||||
}
|
||||
|
||||
public void AddNewMessages(params Message[] messages)
|
||||
{
|
||||
messages = messages.Except(Messages).ToArray();
|
||||
|
||||
Messages.AddRange(messages);
|
||||
|
||||
purgeOldMessages();
|
||||
|
||||
NewMessagesArrived?.Invoke(messages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace or remove a message from the channel.
|
||||
/// </summary>
|
||||
/// <param name="echo">The local echo message (client-side).</param>
|
||||
/// <param name="final">The response message, or null if the message became invalid.</param>
|
||||
public void ReplaceMessage(LocalEchoMessage echo, Message final)
|
||||
{
|
||||
if (!pendingMessages.Remove(echo))
|
||||
throw new InvalidOperationException("Attempted to remove echo that wasn't present");
|
||||
|
||||
Messages.Remove(echo);
|
||||
|
||||
if (final == null)
|
||||
{
|
||||
MessageRemoved?.Invoke(echo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Messages.Contains(final))
|
||||
{
|
||||
// message already inserted, so let's throw away this update.
|
||||
// we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed.
|
||||
MessageRemoved?.Invoke(echo);
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.Add(final);
|
||||
PendingMessageResolved?.Invoke(echo, final);
|
||||
}
|
||||
|
||||
private void purgeOldMessages()
|
||||
{
|
||||
// never purge local echos
|
||||
int messageCount = Messages.Count - pendingMessages.Count;
|
||||
if (messageCount > MAX_HISTORY)
|
||||
Messages.RemoveRange(0, messageCount - MAX_HISTORY);
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class Channel
|
||||
{
|
||||
public const int MAX_HISTORY = 300;
|
||||
|
||||
[JsonProperty(@"name")]
|
||||
public string Name;
|
||||
|
||||
[JsonProperty(@"description")]
|
||||
public string Topic;
|
||||
|
||||
[JsonProperty(@"type")]
|
||||
public string Type;
|
||||
|
||||
[JsonProperty(@"channel_id")]
|
||||
public long Id;
|
||||
|
||||
[JsonConstructor]
|
||||
public Channel()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contructs a private channel
|
||||
/// TODO this class needs to be serialized from something like channels/private, instead of creating from a contructor
|
||||
/// </summary>
|
||||
/// <param name="user">The user </param>
|
||||
public Channel(User user)
|
||||
{
|
||||
Target = TargetType.User;
|
||||
Name = user.Username;
|
||||
Id = user.Id;
|
||||
JoinedUsers.Add(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains every joined user except yourself
|
||||
/// </summary>
|
||||
public ObservableCollection<User> JoinedUsers = new ObservableCollection<User>();
|
||||
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
|
||||
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
|
||||
|
||||
public event Action<IEnumerable<Message>> NewMessagesArrived;
|
||||
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
|
||||
public event Action<Message> MessageRemoved;
|
||||
|
||||
public Bindable<bool> Joined = new Bindable<bool>();
|
||||
public TargetType Target { get; set; }
|
||||
public bool ReadOnly { get; set; }
|
||||
|
||||
public void AddLocalEcho(LocalEchoMessage message)
|
||||
{
|
||||
pendingMessages.Add(message);
|
||||
Messages.Add(message);
|
||||
|
||||
NewMessagesArrived?.Invoke(new[] { message });
|
||||
}
|
||||
|
||||
public void AddNewMessages(params Message[] messages)
|
||||
{
|
||||
messages = messages.Except(Messages).ToArray();
|
||||
|
||||
Messages.AddRange(messages);
|
||||
|
||||
purgeOldMessages();
|
||||
|
||||
NewMessagesArrived?.Invoke(messages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace or remove a message from the channel.
|
||||
/// </summary>
|
||||
/// <param name="echo">The local echo message (client-side).</param>
|
||||
/// <param name="final">The response message, or null if the message became invalid.</param>
|
||||
public void ReplaceMessage(LocalEchoMessage echo, Message final)
|
||||
{
|
||||
if (!pendingMessages.Remove(echo))
|
||||
throw new InvalidOperationException("Attempted to remove echo that wasn't present");
|
||||
|
||||
Messages.Remove(echo);
|
||||
|
||||
if (final == null)
|
||||
{
|
||||
MessageRemoved?.Invoke(echo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Messages.Contains(final))
|
||||
{
|
||||
// message already inserted, so let's throw away this update.
|
||||
// we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed.
|
||||
MessageRemoved?.Invoke(echo);
|
||||
return;
|
||||
}
|
||||
|
||||
Messages.Add(final);
|
||||
PendingMessageResolved?.Invoke(echo, final);
|
||||
}
|
||||
|
||||
private void purgeOldMessages()
|
||||
{
|
||||
// never purge local echos
|
||||
int messageCount = Messages.Count - pendingMessages.Count;
|
||||
if (messageCount > MAX_HISTORY)
|
||||
Messages.RemoveRange(0, messageCount - MAX_HISTORY);
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +1,59 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// An invisible drawable that brings multiple <see cref="SpriteText"/> pieces together to form a consumable clickable link.
|
||||
/// </summary>
|
||||
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
|
||||
{
|
||||
/// <summary>
|
||||
/// Each word part of a chat link (split for word-wrap support).
|
||||
/// </summary>
|
||||
public List<SpriteText> Parts;
|
||||
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos));
|
||||
|
||||
protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<SpriteText> parts)
|
||||
{
|
||||
Parts = parts.ToList();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
IdleColour = colours.Blue;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => Parts;
|
||||
|
||||
public string TooltipText { get; set; }
|
||||
|
||||
private class LinkHoverSounds : HoverClickSounds
|
||||
{
|
||||
private readonly List<SpriteText> parts;
|
||||
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, List<SpriteText> parts)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// An invisible drawable that brings multiple <see cref="SpriteText"/> pieces together to form a consumable clickable link.
|
||||
/// </summary>
|
||||
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
|
||||
{
|
||||
/// <summary>
|
||||
/// Each word part of a chat link (split for word-wrap support).
|
||||
/// </summary>
|
||||
public List<SpriteText> Parts;
|
||||
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos));
|
||||
|
||||
protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts);
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<SpriteText> parts)
|
||||
{
|
||||
Parts = parts.ToList();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
IdleColour = colours.Blue;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => Parts;
|
||||
|
||||
public string TooltipText { get; set; }
|
||||
|
||||
private class LinkHoverSounds : HoverClickSounds
|
||||
{
|
||||
private readonly List<SpriteText> parts;
|
||||
|
||||
public LinkHoverSounds(HoverSampleSet sampleSet, List<SpriteText> parts)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.parts = parts;
|
||||
}
|
||||
|
||||
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class ErrorMessage : InfoMessage
|
||||
{
|
||||
public ErrorMessage(string message) : base(message)
|
||||
{
|
||||
Sender.Colour = @"ff0000";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class ErrorMessage : InfoMessage
|
||||
{
|
||||
public ErrorMessage(string message) : base(message)
|
||||
{
|
||||
Sender.Colour = @"ff0000";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,25 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class InfoMessage : Message
|
||||
{
|
||||
private static int infoID = -1;
|
||||
|
||||
public InfoMessage(string message) : base(infoID--)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now;
|
||||
Content = message;
|
||||
|
||||
Sender = new User
|
||||
{
|
||||
Username = @"system",
|
||||
Colour = @"0000ff",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class InfoMessage : Message
|
||||
{
|
||||
private static int infoID = -1;
|
||||
|
||||
public InfoMessage(string message) : base(infoID--)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now;
|
||||
Content = message;
|
||||
|
||||
Sender = new User
|
||||
{
|
||||
Username = @"system",
|
||||
Colour = @"0000ff",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class LocalEchoMessage : Message
|
||||
{
|
||||
public LocalEchoMessage() : base(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class LocalEchoMessage : Message
|
||||
{
|
||||
public LocalEchoMessage() : base(null)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +1,83 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class Message : IComparable<Message>, IEquatable<Message>
|
||||
{
|
||||
[JsonProperty(@"message_id")]
|
||||
public readonly long? Id;
|
||||
|
||||
//todo: this should be inside sender.
|
||||
[JsonProperty(@"sender_id")]
|
||||
public int UserId;
|
||||
|
||||
[JsonProperty(@"target_type")]
|
||||
public TargetType TargetType;
|
||||
|
||||
[JsonProperty(@"target_id")]
|
||||
public long TargetId;
|
||||
|
||||
[JsonProperty(@"is_action")]
|
||||
public bool IsAction;
|
||||
|
||||
[JsonProperty(@"timestamp")]
|
||||
public DateTimeOffset Timestamp;
|
||||
|
||||
[JsonProperty(@"content")]
|
||||
public string Content;
|
||||
|
||||
[JsonProperty(@"sender")]
|
||||
public User Sender;
|
||||
|
||||
[JsonConstructor]
|
||||
public Message()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text that is displayed in chat.
|
||||
/// </summary>
|
||||
public string DisplayContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The links found in this message.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="Link"/>s' <see cref="Link.Index"/> and <see cref="Link.Length"/>s are according to <see cref="DisplayContent"/></remarks>
|
||||
public List<Link> Links;
|
||||
|
||||
public Message(long? id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public int CompareTo(Message other)
|
||||
{
|
||||
if (!Id.HasValue)
|
||||
return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp);
|
||||
if (!other.Id.HasValue)
|
||||
return -1;
|
||||
|
||||
return Id.Value.CompareTo(other.Id.Value);
|
||||
}
|
||||
|
||||
public virtual bool Equals(Message other) => Id == other?.Id;
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
|
||||
public enum TargetType
|
||||
{
|
||||
[Description(@"channel")]
|
||||
Channel,
|
||||
[Description(@"user")]
|
||||
User
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class Message : IComparable<Message>, IEquatable<Message>
|
||||
{
|
||||
[JsonProperty(@"message_id")]
|
||||
public readonly long? Id;
|
||||
|
||||
//todo: this should be inside sender.
|
||||
[JsonProperty(@"sender_id")]
|
||||
public int UserId;
|
||||
|
||||
[JsonProperty(@"target_type")]
|
||||
public TargetType TargetType;
|
||||
|
||||
[JsonProperty(@"target_id")]
|
||||
public long TargetId;
|
||||
|
||||
[JsonProperty(@"is_action")]
|
||||
public bool IsAction;
|
||||
|
||||
[JsonProperty(@"timestamp")]
|
||||
public DateTimeOffset Timestamp;
|
||||
|
||||
[JsonProperty(@"content")]
|
||||
public string Content;
|
||||
|
||||
[JsonProperty(@"sender")]
|
||||
public User Sender;
|
||||
|
||||
[JsonConstructor]
|
||||
public Message()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The text that is displayed in chat.
|
||||
/// </summary>
|
||||
public string DisplayContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The links found in this message.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="Link"/>s' <see cref="Link.Index"/> and <see cref="Link.Length"/>s are according to <see cref="DisplayContent"/></remarks>
|
||||
public List<Link> Links;
|
||||
|
||||
public Message(long? id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public int CompareTo(Message other)
|
||||
{
|
||||
if (!Id.HasValue)
|
||||
return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp);
|
||||
if (!other.Id.HasValue)
|
||||
return -1;
|
||||
|
||||
return Id.Value.CompareTo(other.Id.Value);
|
||||
}
|
||||
|
||||
public virtual bool Equals(Message other) => Id == other?.Id;
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
}
|
||||
|
||||
public enum TargetType
|
||||
{
|
||||
[Description(@"channel")]
|
||||
Channel,
|
||||
[Description(@"user")]
|
||||
User
|
||||
}
|
||||
}
|
||||
|
@ -1,278 +1,278 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public static class MessageFormatter
|
||||
{
|
||||
// [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points)
|
||||
private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]");
|
||||
|
||||
// (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234)
|
||||
private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]");
|
||||
|
||||
// [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234)
|
||||
private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?<open>\[)[^\[\]]*)+((?<close-open>\])[^\[\]]*)+)*(?(open)(?!)))\]");
|
||||
|
||||
// [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format
|
||||
private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)");
|
||||
|
||||
// advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used
|
||||
// This is in the format (<required>, [optional]):
|
||||
// http[s]://<domain>.<tld>[:port][/path][?query][#fragment]
|
||||
private static readonly Regex advanced_link_regex = new Regex(
|
||||
// protocol
|
||||
@"(?<link>[a-z]*?:\/\/" +
|
||||
// domain + tld
|
||||
@"(?<domain>(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" +
|
||||
// port (optional)
|
||||
@"(?::\d+)?)" +
|
||||
// path (optional)
|
||||
@"(?<path>(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" +
|
||||
// query (optional)
|
||||
@"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" +
|
||||
// fragment (optional)
|
||||
@"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)",
|
||||
RegexOptions.IgnoreCase);
|
||||
|
||||
// 00:00:000 (1,2,3) - test
|
||||
private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*");
|
||||
|
||||
// #osu
|
||||
private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)");
|
||||
|
||||
// Unicode emojis
|
||||
private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])");
|
||||
|
||||
private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null)
|
||||
{
|
||||
int captureOffset = 0;
|
||||
foreach (Match m in regex.Matches(result.Text, startIndex))
|
||||
{
|
||||
var index = m.Index - captureOffset;
|
||||
|
||||
var displayText = string.Format(display,
|
||||
m.Groups[0],
|
||||
m.Groups.Count > 1 ? m.Groups[1].Value : "",
|
||||
m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
|
||||
|
||||
var linkText = string.Format(link,
|
||||
m.Groups[0],
|
||||
m.Groups.Count > 1 ? m.Groups[1].Value : "",
|
||||
m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
|
||||
|
||||
if (displayText.Length == 0 || linkText.Length == 0) continue;
|
||||
|
||||
// Check for encapsulated links
|
||||
if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null)
|
||||
{
|
||||
result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText);
|
||||
|
||||
//since we just changed the line display text, offset any already processed links.
|
||||
result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0);
|
||||
|
||||
var details = getLinkDetails(linkText);
|
||||
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
|
||||
|
||||
//adjust the offset for processing the current matches group.
|
||||
captureOffset += m.Length - displayText.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleAdvanced(Regex regex, MessageFormatterResult result, int startIndex = 0)
|
||||
{
|
||||
foreach (Match m in regex.Matches(result.Text, startIndex))
|
||||
{
|
||||
var index = m.Index;
|
||||
var link = m.Groups["link"].Value;
|
||||
var indexLength = link.Length;
|
||||
|
||||
var details = getLinkDetails(link);
|
||||
result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument));
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkDetails getLinkDetails(string url)
|
||||
{
|
||||
var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
args[0] = args[0].TrimEnd(':');
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "http":
|
||||
case "https":
|
||||
// length > 3 since all these links need another argument to work
|
||||
if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh"))
|
||||
{
|
||||
switch (args[2])
|
||||
{
|
||||
case "b":
|
||||
case "beatmaps":
|
||||
return new LinkDetails(LinkAction.OpenBeatmap, args[3]);
|
||||
case "s":
|
||||
case "beatmapsets":
|
||||
case "d":
|
||||
return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]);
|
||||
case "u":
|
||||
return new LinkDetails(LinkAction.OpenUserProfile, args[3]);
|
||||
}
|
||||
}
|
||||
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
case "osu":
|
||||
// every internal link also needs some kind of argument
|
||||
if (args.Length < 3)
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
|
||||
LinkAction linkType;
|
||||
switch (args[1])
|
||||
{
|
||||
case "chan":
|
||||
linkType = LinkAction.OpenChannel;
|
||||
break;
|
||||
case "edit":
|
||||
linkType = LinkAction.OpenEditorTimestamp;
|
||||
break;
|
||||
case "b":
|
||||
linkType = LinkAction.OpenBeatmap;
|
||||
break;
|
||||
case "s":
|
||||
case "dl":
|
||||
linkType = LinkAction.OpenBeatmapSet;
|
||||
break;
|
||||
case "spectate":
|
||||
linkType = LinkAction.Spectate;
|
||||
break;
|
||||
case "u":
|
||||
linkType = LinkAction.OpenUserProfile;
|
||||
break;
|
||||
default:
|
||||
linkType = LinkAction.External;
|
||||
break;
|
||||
}
|
||||
|
||||
return new LinkDetails(linkType, args[2]);
|
||||
case "osump":
|
||||
return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]);
|
||||
default:
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3)
|
||||
{
|
||||
var result = new MessageFormatterResult(toFormat);
|
||||
|
||||
// handle the [link display] format
|
||||
handleMatches(new_link_regex, "{2}", "{1}", result, startIndex);
|
||||
|
||||
// handle the standard markdown []() format
|
||||
handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex);
|
||||
|
||||
// handle the ()[] link format
|
||||
handleMatches(old_link_regex, "{1}", "{2}", result, startIndex);
|
||||
|
||||
// handle wiki links
|
||||
handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex);
|
||||
|
||||
// handle bare links
|
||||
handleAdvanced(advanced_link_regex, result, startIndex);
|
||||
|
||||
// handle editor times
|
||||
handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
|
||||
// handle channels
|
||||
handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel);
|
||||
|
||||
var empty = "";
|
||||
while (space-- > 0)
|
||||
empty += "\0";
|
||||
|
||||
handleMatches(emoji_regex, empty, "{0}", result, startIndex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Message FormatMessage(Message inputMessage)
|
||||
{
|
||||
var result = format(inputMessage.Content);
|
||||
|
||||
inputMessage.DisplayContent = result.Text;
|
||||
|
||||
// Sometimes, regex matches are not in order
|
||||
result.Links.Sort();
|
||||
inputMessage.Links = result.Links;
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
public static MessageFormatterResult FormatText(string text)
|
||||
{
|
||||
var result = format(text);
|
||||
|
||||
result.Links.Sort();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public class MessageFormatterResult
|
||||
{
|
||||
public List<Link> Links = new List<Link>();
|
||||
public string Text;
|
||||
public string OriginalText;
|
||||
|
||||
public MessageFormatterResult(string text)
|
||||
{
|
||||
OriginalText = Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public class LinkDetails
|
||||
{
|
||||
public LinkAction Action;
|
||||
public string Argument;
|
||||
|
||||
public LinkDetails(LinkAction action, string argument)
|
||||
{
|
||||
Action = action;
|
||||
Argument = argument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum LinkAction
|
||||
{
|
||||
External,
|
||||
OpenBeatmap,
|
||||
OpenBeatmapSet,
|
||||
OpenChannel,
|
||||
OpenEditorTimestamp,
|
||||
JoinMultiplayerMatch,
|
||||
Spectate,
|
||||
OpenUserProfile,
|
||||
}
|
||||
|
||||
public class Link : IComparable<Link>
|
||||
{
|
||||
public string Url;
|
||||
public int Index;
|
||||
public int Length;
|
||||
public LinkAction Action;
|
||||
public string Argument;
|
||||
|
||||
public Link(string url, int startIndex, int length, LinkAction action, string argument)
|
||||
{
|
||||
Url = url;
|
||||
Index = startIndex;
|
||||
Length = length;
|
||||
Action = action;
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1;
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public static class MessageFormatter
|
||||
{
|
||||
// [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points)
|
||||
private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]");
|
||||
|
||||
// (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234)
|
||||
private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]");
|
||||
|
||||
// [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234)
|
||||
private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?<open>\[)[^\[\]]*)+((?<close-open>\])[^\[\]]*)+)*(?(open)(?!)))\]");
|
||||
|
||||
// [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format
|
||||
private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)");
|
||||
|
||||
// advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used
|
||||
// This is in the format (<required>, [optional]):
|
||||
// http[s]://<domain>.<tld>[:port][/path][?query][#fragment]
|
||||
private static readonly Regex advanced_link_regex = new Regex(
|
||||
// protocol
|
||||
@"(?<link>[a-z]*?:\/\/" +
|
||||
// domain + tld
|
||||
@"(?<domain>(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" +
|
||||
// port (optional)
|
||||
@"(?::\d+)?)" +
|
||||
// path (optional)
|
||||
@"(?<path>(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" +
|
||||
// query (optional)
|
||||
@"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" +
|
||||
// fragment (optional)
|
||||
@"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)",
|
||||
RegexOptions.IgnoreCase);
|
||||
|
||||
// 00:00:000 (1,2,3) - test
|
||||
private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*");
|
||||
|
||||
// #osu
|
||||
private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)");
|
||||
|
||||
// Unicode emojis
|
||||
private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])");
|
||||
|
||||
private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null)
|
||||
{
|
||||
int captureOffset = 0;
|
||||
foreach (Match m in regex.Matches(result.Text, startIndex))
|
||||
{
|
||||
var index = m.Index - captureOffset;
|
||||
|
||||
var displayText = string.Format(display,
|
||||
m.Groups[0],
|
||||
m.Groups.Count > 1 ? m.Groups[1].Value : "",
|
||||
m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
|
||||
|
||||
var linkText = string.Format(link,
|
||||
m.Groups[0],
|
||||
m.Groups.Count > 1 ? m.Groups[1].Value : "",
|
||||
m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim();
|
||||
|
||||
if (displayText.Length == 0 || linkText.Length == 0) continue;
|
||||
|
||||
// Check for encapsulated links
|
||||
if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null)
|
||||
{
|
||||
result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText);
|
||||
|
||||
//since we just changed the line display text, offset any already processed links.
|
||||
result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0);
|
||||
|
||||
var details = getLinkDetails(linkText);
|
||||
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
|
||||
|
||||
//adjust the offset for processing the current matches group.
|
||||
captureOffset += m.Length - displayText.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleAdvanced(Regex regex, MessageFormatterResult result, int startIndex = 0)
|
||||
{
|
||||
foreach (Match m in regex.Matches(result.Text, startIndex))
|
||||
{
|
||||
var index = m.Index;
|
||||
var link = m.Groups["link"].Value;
|
||||
var indexLength = link.Length;
|
||||
|
||||
var details = getLinkDetails(link);
|
||||
result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument));
|
||||
}
|
||||
}
|
||||
|
||||
private static LinkDetails getLinkDetails(string url)
|
||||
{
|
||||
var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
args[0] = args[0].TrimEnd(':');
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "http":
|
||||
case "https":
|
||||
// length > 3 since all these links need another argument to work
|
||||
if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh"))
|
||||
{
|
||||
switch (args[2])
|
||||
{
|
||||
case "b":
|
||||
case "beatmaps":
|
||||
return new LinkDetails(LinkAction.OpenBeatmap, args[3]);
|
||||
case "s":
|
||||
case "beatmapsets":
|
||||
case "d":
|
||||
return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]);
|
||||
case "u":
|
||||
return new LinkDetails(LinkAction.OpenUserProfile, args[3]);
|
||||
}
|
||||
}
|
||||
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
case "osu":
|
||||
// every internal link also needs some kind of argument
|
||||
if (args.Length < 3)
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
|
||||
LinkAction linkType;
|
||||
switch (args[1])
|
||||
{
|
||||
case "chan":
|
||||
linkType = LinkAction.OpenChannel;
|
||||
break;
|
||||
case "edit":
|
||||
linkType = LinkAction.OpenEditorTimestamp;
|
||||
break;
|
||||
case "b":
|
||||
linkType = LinkAction.OpenBeatmap;
|
||||
break;
|
||||
case "s":
|
||||
case "dl":
|
||||
linkType = LinkAction.OpenBeatmapSet;
|
||||
break;
|
||||
case "spectate":
|
||||
linkType = LinkAction.Spectate;
|
||||
break;
|
||||
case "u":
|
||||
linkType = LinkAction.OpenUserProfile;
|
||||
break;
|
||||
default:
|
||||
linkType = LinkAction.External;
|
||||
break;
|
||||
}
|
||||
|
||||
return new LinkDetails(linkType, args[2]);
|
||||
case "osump":
|
||||
return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]);
|
||||
default:
|
||||
return new LinkDetails(LinkAction.External, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3)
|
||||
{
|
||||
var result = new MessageFormatterResult(toFormat);
|
||||
|
||||
// handle the [link display] format
|
||||
handleMatches(new_link_regex, "{2}", "{1}", result, startIndex);
|
||||
|
||||
// handle the standard markdown []() format
|
||||
handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex);
|
||||
|
||||
// handle the ()[] link format
|
||||
handleMatches(old_link_regex, "{1}", "{2}", result, startIndex);
|
||||
|
||||
// handle wiki links
|
||||
handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex);
|
||||
|
||||
// handle bare links
|
||||
handleAdvanced(advanced_link_regex, result, startIndex);
|
||||
|
||||
// handle editor times
|
||||
handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp);
|
||||
|
||||
// handle channels
|
||||
handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel);
|
||||
|
||||
var empty = "";
|
||||
while (space-- > 0)
|
||||
empty += "\0";
|
||||
|
||||
handleMatches(emoji_regex, empty, "{0}", result, startIndex);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Message FormatMessage(Message inputMessage)
|
||||
{
|
||||
var result = format(inputMessage.Content);
|
||||
|
||||
inputMessage.DisplayContent = result.Text;
|
||||
|
||||
// Sometimes, regex matches are not in order
|
||||
result.Links.Sort();
|
||||
inputMessage.Links = result.Links;
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
public static MessageFormatterResult FormatText(string text)
|
||||
{
|
||||
var result = format(text);
|
||||
|
||||
result.Links.Sort();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public class MessageFormatterResult
|
||||
{
|
||||
public List<Link> Links = new List<Link>();
|
||||
public string Text;
|
||||
public string OriginalText;
|
||||
|
||||
public MessageFormatterResult(string text)
|
||||
{
|
||||
OriginalText = Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public class LinkDetails
|
||||
{
|
||||
public LinkAction Action;
|
||||
public string Argument;
|
||||
|
||||
public LinkDetails(LinkAction action, string argument)
|
||||
{
|
||||
Action = action;
|
||||
Argument = argument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum LinkAction
|
||||
{
|
||||
External,
|
||||
OpenBeatmap,
|
||||
OpenBeatmapSet,
|
||||
OpenChannel,
|
||||
OpenEditorTimestamp,
|
||||
JoinMultiplayerMatch,
|
||||
Spectate,
|
||||
OpenUserProfile,
|
||||
}
|
||||
|
||||
public class Link : IComparable<Link>
|
||||
{
|
||||
public string Url;
|
||||
public int Index;
|
||||
public int Length;
|
||||
public LinkAction Action;
|
||||
public string Argument;
|
||||
|
||||
public Link(string url, int startIndex, int length, LinkAction action, string argument)
|
||||
{
|
||||
Url = url;
|
||||
Index = startIndex;
|
||||
Length = length;
|
||||
Action = action;
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
@ -1,146 +1,146 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public abstract class GameType
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public abstract Drawable GetIcon(OsuColour colours, float size);
|
||||
}
|
||||
|
||||
public class GameTypeTag : GameType
|
||||
{
|
||||
public override string Name => "Tag";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size),
|
||||
Colour = colours.Blue,
|
||||
Shadow = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeVersus : GameType
|
||||
{
|
||||
public override string Name => "Versus";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new VersusRow(colours.Blue, colours.Blue, size * 0.6f)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeTagTeam : GameType
|
||||
{
|
||||
public override string Name => "Tag Team";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(2f),
|
||||
Children = new[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size * 0.75f),
|
||||
Colour = colours.Blue,
|
||||
Shadow = false,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size * 0.75f),
|
||||
Colour = colours.Pink,
|
||||
Shadow = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeTeamVersus : GameType
|
||||
{
|
||||
public override string Name => "Team Versus";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2f),
|
||||
Children = new[]
|
||||
{
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class VersusRow : FillFlowContainer
|
||||
{
|
||||
public VersusRow(Color4 first, Color4 second, float size)
|
||||
{
|
||||
var triangleSize = new Vector2(size);
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Spacing = new Vector2(2f, 0f);
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = first,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = 90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = second,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = -90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public abstract class GameType
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
public abstract Drawable GetIcon(OsuColour colours, float size);
|
||||
}
|
||||
|
||||
public class GameTypeTag : GameType
|
||||
{
|
||||
public override string Name => "Tag";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size),
|
||||
Colour = colours.Blue,
|
||||
Shadow = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeVersus : GameType
|
||||
{
|
||||
public override string Name => "Versus";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new VersusRow(colours.Blue, colours.Blue, size * 0.6f)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeTagTeam : GameType
|
||||
{
|
||||
public override string Name => "Tag Team";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(2f),
|
||||
Children = new[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size * 0.75f),
|
||||
Colour = colours.Blue,
|
||||
Shadow = false,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_refresh,
|
||||
Size = new Vector2(size * 0.75f),
|
||||
Colour = colours.Pink,
|
||||
Shadow = false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class GameTypeTeamVersus : GameType
|
||||
{
|
||||
public override string Name => "Team Versus";
|
||||
public override Drawable GetIcon(OsuColour colours, float size)
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2f),
|
||||
Children = new[]
|
||||
{
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class VersusRow : FillFlowContainer
|
||||
{
|
||||
public VersusRow(Color4 first, Color4 second, float size)
|
||||
{
|
||||
var triangleSize = new Vector2(size);
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Spacing = new Vector2(2f, 0f);
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = first,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = 90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = second,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = -90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public class Room
|
||||
{
|
||||
public Bindable<string> Name = new Bindable<string>();
|
||||
public Bindable<User> Host = new Bindable<User>();
|
||||
public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
|
||||
public Bindable<GameType> Type = new Bindable<GameType>();
|
||||
public Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
public Bindable<int?> MaxParticipants = new Bindable<int?>();
|
||||
public Bindable<User[]> Participants = new Bindable<User[]>();
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public class Room
|
||||
{
|
||||
public Bindable<string> Name = new Bindable<string>();
|
||||
public Bindable<User> Host = new Bindable<User>();
|
||||
public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
|
||||
public Bindable<GameType> Type = new Bindable<GameType>();
|
||||
public Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
|
||||
public Bindable<int?> MaxParticipants = new Bindable<int?>();
|
||||
public Bindable<User[]> Participants = new Bindable<User[]>();
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public abstract class RoomStatus
|
||||
{
|
||||
public abstract string Message { get; }
|
||||
public abstract Color4 GetAppropriateColour(OsuColour colours);
|
||||
}
|
||||
|
||||
public class RoomStatusOpen : RoomStatus
|
||||
{
|
||||
public override string Message => @"Welcoming Players";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
||||
}
|
||||
|
||||
public class RoomStatusPlaying : RoomStatus
|
||||
{
|
||||
public override string Message => @"Now Playing";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
public abstract class RoomStatus
|
||||
{
|
||||
public abstract string Message { get; }
|
||||
public abstract Color4 GetAppropriateColour(OsuColour colours);
|
||||
}
|
||||
|
||||
public class RoomStatusOpen : RoomStatus
|
||||
{
|
||||
public override string Message => @"Welcoming Players";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
|
||||
}
|
||||
|
||||
public class RoomStatusPlaying : RoomStatus
|
||||
{
|
||||
public override string Message => @"Now Playing";
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user