mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:23:20 +08:00
Merge pull request #15 from peppy/online_api
Basic networking / API authentication.
This commit is contained in:
commit
43d8dd8cf5
@ -1 +1 @@
|
|||||||
Subproject commit 3bbfe0137546497e767f7863cda66efc42b1686c
|
Subproject commit 79572e2da7d5f0467f6fd6ad277cfbade2e21b79
|
@ -2,6 +2,7 @@
|
|||||||
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
@ -12,6 +13,10 @@ namespace osu.Game.Configuration
|
|||||||
Set(OsuConfig.Width, 1366);
|
Set(OsuConfig.Width, 1366);
|
||||||
Set(OsuConfig.Height, 768);
|
Set(OsuConfig.Height, 768);
|
||||||
Set(OsuConfig.MouseSensitivity, 1.0);
|
Set(OsuConfig.MouseSensitivity, 1.0);
|
||||||
|
|
||||||
|
Set(OsuConfig.Username, string.Empty);
|
||||||
|
Set(OsuConfig.Password, string.Empty);
|
||||||
|
Set(OsuConfig.Token, string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,5 +25,8 @@ namespace osu.Game.Configuration
|
|||||||
Width,
|
Width,
|
||||||
Height,
|
Height,
|
||||||
MouseSensitivity,
|
MouseSensitivity,
|
||||||
|
Username,
|
||||||
|
Password,
|
||||||
|
Token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
277
osu.Game/Online/API/APIAccess.cs
Normal file
277
osu.Game/Online/API/APIAccess.cs
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
//Copyright (c) 2007-2016 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.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
internal class APIAccess
|
||||||
|
{
|
||||||
|
private OAuth authentication;
|
||||||
|
|
||||||
|
internal string Endpoint = @"https://new.ppy.sh";
|
||||||
|
const string ClientId = @"daNBnfdv7SppRVc61z0XuOI13y6Hroiz";
|
||||||
|
const string ClientSecret = @"d6fgZuZeQ0eSXkEj5igdqQX6ztdtS6Ow";
|
||||||
|
|
||||||
|
ConcurrentQueue<APIRequest> queue = new ConcurrentQueue<APIRequest>();
|
||||||
|
|
||||||
|
public string Username;
|
||||||
|
|
||||||
|
private SecurePassword password;
|
||||||
|
|
||||||
|
public string Password
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
password = string.IsNullOrEmpty(value) ? null : new SecurePassword(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Token
|
||||||
|
{
|
||||||
|
get { return authentication.Token?.ToString(); }
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
authentication.Token = null;
|
||||||
|
else
|
||||||
|
authentication.Token = OAuthToken.Parse(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool HasLogin => Token != null || (!string.IsNullOrEmpty(Username) && password != null);
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
Logger log;
|
||||||
|
|
||||||
|
internal APIAccess()
|
||||||
|
{
|
||||||
|
authentication = new OAuth(ClientId, ClientSecret, Endpoint);
|
||||||
|
log = Logger.GetLogger(LoggingTarget.Network);
|
||||||
|
|
||||||
|
thread = new Thread(run) { IsBackground = true };
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string AccessToken => authentication.RequestAccessToken();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of consecutive requests which failed due to network issues.
|
||||||
|
/// </summary>
|
||||||
|
int failureCount = 0;
|
||||||
|
|
||||||
|
private void run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
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:
|
||||||
|
//work to restore a connection...
|
||||||
|
if (!HasLogin)
|
||||||
|
{
|
||||||
|
//OsuGame.Scheduler.Add(() => { OsuGame.ShowLogin(); });
|
||||||
|
|
||||||
|
State = APIState.Offline;
|
||||||
|
Thread.Sleep(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (State < APIState.Connecting)
|
||||||
|
State = APIState.Connecting;
|
||||||
|
|
||||||
|
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, password.Get(Representation.Raw)))
|
||||||
|
{
|
||||||
|
//todo: this fails even on network-related issues. we should probably handle those differently.
|
||||||
|
//NotificationManager.ShowMessage("Login failed!");
|
||||||
|
log.Add(@"Login failed!");
|
||||||
|
ClearCredentials();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//we're connected!
|
||||||
|
State = APIState.Online;
|
||||||
|
failureCount = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//hard bail if we can't get a valid access token.
|
||||||
|
if (authentication.RequestAccessToken() == null)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearCredentials()
|
||||||
|
{
|
||||||
|
Username = null;
|
||||||
|
password = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
{
|
||||||
|
req.Perform(this);
|
||||||
|
|
||||||
|
State = APIState.Online;
|
||||||
|
failureCount = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (WebException we)
|
||||||
|
{
|
||||||
|
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode ?? HttpStatusCode.RequestTimeout;
|
||||||
|
|
||||||
|
switch (statusCode)
|
||||||
|
{
|
||||||
|
case HttpStatusCode.Unauthorized:
|
||||||
|
State = APIState.Offline;
|
||||||
|
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;
|
||||||
|
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; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
APIState oldState = state;
|
||||||
|
APIState newState = value;
|
||||||
|
|
||||||
|
state = value;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case APIState.Failing:
|
||||||
|
case APIState.Offline:
|
||||||
|
flushQueue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldState != newState)
|
||||||
|
{
|
||||||
|
//OsuGame.Scheduler.Add(delegate
|
||||||
|
{
|
||||||
|
//NotificationManager.ShowMessage($@"We just went {newState}!", newState == APIState.Online ? Color4.YellowGreen : Color4.OrangeRed, 5000);
|
||||||
|
log.Add($@"We just went {newState}!");
|
||||||
|
OnStateChange?.Invoke(oldState, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Queue(APIRequest request)
|
||||||
|
{
|
||||||
|
queue.Enqueue(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal event StateChangeDelegate OnStateChange;
|
||||||
|
|
||||||
|
internal delegate void StateChangeDelegate(APIState oldState, APIState newState);
|
||||||
|
|
||||||
|
internal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushQueue(bool failOldRequests = true)
|
||||||
|
{
|
||||||
|
var oldQueue = queue;
|
||||||
|
|
||||||
|
//flush the queue.
|
||||||
|
queue = new ConcurrentQueue<APIRequest>();
|
||||||
|
|
||||||
|
if (failOldRequests)
|
||||||
|
{
|
||||||
|
APIRequest req;
|
||||||
|
while (queue.TryDequeue(out req))
|
||||||
|
req.Fail(new Exception(@"Disconnected from server"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Logout()
|
||||||
|
{
|
||||||
|
authentication.Clear();
|
||||||
|
State = APIState.Offline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
osu.Game/Online/API/APIRequest.cs
Normal file
93
osu.Game/Online/API/APIRequest.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
//Copyright (c) 2007-2016 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>
|
||||||
|
internal class APIRequest<T> : APIRequest
|
||||||
|
{
|
||||||
|
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
||||||
|
|
||||||
|
public APIRequest()
|
||||||
|
{
|
||||||
|
base.Success += onSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSuccess()
|
||||||
|
{
|
||||||
|
Success?.Invoke((WebRequest as JsonWebRequest<T>).ResponseObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new event APISuccessHandler<T> Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AN API request with no specified response type.
|
||||||
|
/// </summary>
|
||||||
|
public class APIRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum amount of time before this request will fail.
|
||||||
|
/// </summary>
|
||||||
|
internal 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 - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0)));
|
||||||
|
|
||||||
|
internal bool ExceededTimeout => remainingTime == 0;
|
||||||
|
|
||||||
|
private double? startTime;
|
||||||
|
|
||||||
|
internal double StartTime => startTime ?? -1;
|
||||||
|
|
||||||
|
private APIAccess api;
|
||||||
|
protected WebRequest WebRequest;
|
||||||
|
|
||||||
|
public event APISuccessHandler Success;
|
||||||
|
public event APIFailureHandler Failure;
|
||||||
|
|
||||||
|
internal void Perform(APIAccess api)
|
||||||
|
{
|
||||||
|
if (startTime == null)
|
||||||
|
startTime = DateTime.Now.TotalMilliseconds();
|
||||||
|
|
||||||
|
this.api = api;
|
||||||
|
|
||||||
|
if (remainingTime <= 0)
|
||||||
|
throw new TimeoutException(@"API request timeout hit");
|
||||||
|
|
||||||
|
WebRequest = CreateWebRequest();
|
||||||
|
WebRequest.RetryCount = 0;
|
||||||
|
WebRequest.Headers[@"Authorization"] = $@"Bearer {api.AccessToken}";
|
||||||
|
|
||||||
|
WebRequest.BlockingPerform();
|
||||||
|
|
||||||
|
//OsuGame.Scheduler.Add(delegate {
|
||||||
|
Success?.Invoke();
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Fail(Exception e)
|
||||||
|
{
|
||||||
|
WebRequest?.Abort();
|
||||||
|
//OsuGame.Scheduler.Add(delegate {
|
||||||
|
Failure?.Invoke(e);
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void APIFailureHandler(Exception e);
|
||||||
|
public delegate void APISuccessHandler();
|
||||||
|
public delegate void APISuccessHandler<T>(T content);
|
||||||
|
}
|
162
osu.Game/Online/API/OAuth.cs
Normal file
162
osu.Game/Online/API/OAuth.cs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
internal 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)
|
||||||
|
{
|
||||||
|
var req = new AccessTokenRequestPassword(username, password)
|
||||||
|
{
|
||||||
|
Url = $@"{endpoint}/oauth/access_token",
|
||||||
|
Method = HttpMethod.POST,
|
||||||
|
ClientId = clientId,
|
||||||
|
ClientSecret = clientSecret
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
req.BlockingPerform();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token = req.ResponseObject;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool AuthenticateWithRefresh(string refresh)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var req = new AccessTokenRequestRefresh(refresh)
|
||||||
|
{
|
||||||
|
Url = $@"{endpoint}/oauth/access_token",
|
||||||
|
Method = HttpMethod.POST,
|
||||||
|
ClientId = clientId,
|
||||||
|
ClientSecret = clientSecret
|
||||||
|
};
|
||||||
|
req.BlockingPerform();
|
||||||
|
|
||||||
|
Token = req.ResponseObject;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//todo: potentially only kill the refresh token on certain exception types.
|
||||||
|
Token = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should be run before any API request to make sure we have a valid key.
|
||||||
|
/// </summary>
|
||||||
|
private bool ensureAccessToken()
|
||||||
|
{
|
||||||
|
//todo: we need to mutex this to ensure only one authentication request is running at a time.
|
||||||
|
|
||||||
|
//If we already have a valid access token, let's use it.
|
||||||
|
if (accessTokenValid) return true;
|
||||||
|
|
||||||
|
//If not, let's try using our refresh token to request a new access token.
|
||||||
|
if (!string.IsNullOrEmpty(Token?.RefreshToken))
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
Parameters[@"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()
|
||||||
|
{
|
||||||
|
Parameters[@"username"] = Username;
|
||||||
|
Parameters[@"password"] = Password;
|
||||||
|
base.PrePerform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AccessTokenRequest : JsonWebRequest<OAuthToken>
|
||||||
|
{
|
||||||
|
protected string GrantType;
|
||||||
|
|
||||||
|
internal string ClientId;
|
||||||
|
internal string ClientSecret;
|
||||||
|
|
||||||
|
protected override void PrePerform()
|
||||||
|
{
|
||||||
|
Parameters[@"grant_type"] = GrantType;
|
||||||
|
Parameters[@"client_id"] = ClientId;
|
||||||
|
Parameters[@"client_secret"] = ClientSecret;
|
||||||
|
base.PrePerform();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
osu.Game/Online/API/OAuthToken.cs
Normal file
65
osu.Game/Online/API/OAuthToken.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//Copyright (c) 2007-2016 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]
|
||||||
|
internal class OAuthToken
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// OAuth 2.0 access token.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty(@"access_token")]
|
||||||
|
public string AccessToken;
|
||||||
|
|
||||||
|
[JsonProperty(@"expires_in")]
|
||||||
|
public long ExpiresIn
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return AccessTokenExpiry - DateTime.Now.ToUnixTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
AccessTokenExpiry = DateTime.Now.AddSeconds(value).ToUnixTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
osu.Game/Online/API/Requests/GetMessagesRequest.cs
Normal file
37
osu.Game/Online/API/Requests/GetMessagesRequest.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//Copyright (c) 2007-2016 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.Framework.IO.Network;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
internal class GetMessagesRequest : APIRequest<List<Message>>
|
||||||
|
{
|
||||||
|
List<Channel> channels;
|
||||||
|
long? since;
|
||||||
|
|
||||||
|
public GetMessagesRequest(List<Channel> channels, long? sinceId)
|
||||||
|
{
|
||||||
|
this.channels = channels;
|
||||||
|
this.since = sinceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
string channelString = string.Empty;
|
||||||
|
foreach (Channel c in channels)
|
||||||
|
channelString += c.Id + ",";
|
||||||
|
channelString = channelString.TrimEnd(',');
|
||||||
|
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
req.AddParameter(@"channels", channelString);
|
||||||
|
if (since.HasValue) req.AddParameter(@"since", since.Value.ToString());
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => @"chat/messages";
|
||||||
|
}
|
||||||
|
}
|
13
osu.Game/Online/API/Requests/ListChannels.cs
Normal file
13
osu.Game/Online/API/Requests/ListChannels.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//Copyright (c) 2007-2016 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
|
||||||
|
{
|
||||||
|
internal class ListChannelsRequest : APIRequest<List<Channel>>
|
||||||
|
{
|
||||||
|
protected override string Target => @"chat/channels";
|
||||||
|
}
|
||||||
|
}
|
52
osu.Game/Online/API/SecurePassword.cs
Normal file
52
osu.Game/Online/API/SecurePassword.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API
|
||||||
|
{
|
||||||
|
internal class SecurePassword
|
||||||
|
{
|
||||||
|
private readonly SecureString storage = new SecureString();
|
||||||
|
private readonly Representation representation;
|
||||||
|
|
||||||
|
//todo: move this to a central constants file.
|
||||||
|
private const string password_entropy = @"cu24180ncjeiu0ci1nwui";
|
||||||
|
|
||||||
|
public SecurePassword(string input, bool encrypted = false)
|
||||||
|
{
|
||||||
|
//if (encrypted)
|
||||||
|
//{
|
||||||
|
// string rep;
|
||||||
|
// input = DPAPI.Decrypt(input, password_entropy, out rep);
|
||||||
|
// Enum.TryParse(rep, out representation);
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
{
|
||||||
|
representation = Representation.Raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (char c in input)
|
||||||
|
storage.AppendChar(c);
|
||||||
|
storage.MakeReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string Get(Representation request = Representation.Raw)
|
||||||
|
{
|
||||||
|
switch (request)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
return storage.UnsecureRepresentation();
|
||||||
|
//case Representation.Encrypted:
|
||||||
|
// return DPAPI.Encrypt(DPAPI.KeyType.UserKey, storage.UnsecureRepresentation(), password_entropy, representation.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Representation
|
||||||
|
{
|
||||||
|
Raw,
|
||||||
|
Encrypted
|
||||||
|
}
|
||||||
|
}
|
32
osu.Game/Online/Chat/Channel.cs
Normal file
32
osu.Game/Online/Chat/Channel.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//Copyright (c) 2007-2016 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Chat
|
||||||
|
{
|
||||||
|
public class Channel
|
||||||
|
{
|
||||||
|
[JsonProperty(@"name")]
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
[JsonProperty(@"description")]
|
||||||
|
public string Topic;
|
||||||
|
|
||||||
|
[JsonProperty(@"type")]
|
||||||
|
public string Type;
|
||||||
|
|
||||||
|
[JsonProperty(@"channel_id")]
|
||||||
|
public int Id;
|
||||||
|
|
||||||
|
public List<Message> Messages = new List<Message>();
|
||||||
|
|
||||||
|
internal bool Joined;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public Channel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
osu.Game/Online/Chat/Message.cs
Normal file
34
osu.Game/Online/Chat/Message.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Chat
|
||||||
|
{
|
||||||
|
public class Message
|
||||||
|
{
|
||||||
|
[JsonProperty(@"message_id")]
|
||||||
|
public long Id;
|
||||||
|
|
||||||
|
[JsonProperty(@"user_id")]
|
||||||
|
public string UserId;
|
||||||
|
|
||||||
|
[JsonProperty(@"channel_id")]
|
||||||
|
public string ChannelId;
|
||||||
|
|
||||||
|
[JsonProperty(@"timestamp")]
|
||||||
|
public DateTime Timestamp;
|
||||||
|
|
||||||
|
[JsonProperty(@"content")]
|
||||||
|
internal string Content;
|
||||||
|
|
||||||
|
[JsonProperty(@"sender")]
|
||||||
|
internal string User;
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public Message()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,15 @@
|
|||||||
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using osu.Framework.Framework;
|
using osu.Framework.Framework;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.GameModes.Menu;
|
using osu.Game.GameModes.Menu;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Processing;
|
using osu.Game.Graphics.Processing;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
|
||||||
namespace osu.Game
|
namespace osu.Game
|
||||||
{
|
{
|
||||||
@ -16,6 +19,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected override string MainResourceFile => @"osu.Game.Resources.dll";
|
protected override string MainResourceFile => @"osu.Game.Resources.dll";
|
||||||
|
|
||||||
|
internal APIAccess API;
|
||||||
|
|
||||||
public override void Load()
|
public override void Load()
|
||||||
{
|
{
|
||||||
base.Load();
|
base.Load();
|
||||||
@ -23,6 +28,19 @@ namespace osu.Game
|
|||||||
Window.Size = new Size(Config.Get<int>(OsuConfig.Width), Config.Get<int>(OsuConfig.Height));
|
Window.Size = new Size(Config.Get<int>(OsuConfig.Width), Config.Get<int>(OsuConfig.Height));
|
||||||
Window.OnSizeChanged += window_OnSizeChanged;
|
Window.OnSizeChanged += window_OnSizeChanged;
|
||||||
|
|
||||||
|
API = new APIAccess()
|
||||||
|
{
|
||||||
|
Username = Config.Get<string>(OsuConfig.Username),
|
||||||
|
Password = Config.Get<string>(OsuConfig.Password),
|
||||||
|
Token = Config.Get<string>(OsuConfig.Token)
|
||||||
|
};
|
||||||
|
|
||||||
|
//var req = new ListChannelsRequest();
|
||||||
|
//req.Success += content =>
|
||||||
|
//{
|
||||||
|
//};
|
||||||
|
//API.Queue(req);
|
||||||
|
|
||||||
AddProcessingContainer(new RatioAdjust());
|
AddProcessingContainer(new RatioAdjust());
|
||||||
|
|
||||||
//Add(new FontTest());
|
//Add(new FontTest());
|
||||||
@ -31,10 +49,18 @@ namespace osu.Game
|
|||||||
Add(new CursorContainer());
|
Add(new CursorContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
//refresh token may have changed.
|
||||||
|
Config.Set(OsuConfig.Token, API.Token);
|
||||||
|
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
}
|
||||||
|
|
||||||
private void window_OnSizeChanged()
|
private void window_OnSizeChanged()
|
||||||
{
|
{
|
||||||
Config.Set<int>(OsuConfig.Width, Window.Size.Width);
|
Config.Set(OsuConfig.Width, Window.Size.Width);
|
||||||
Config.Set<int>(OsuConfig.Height, Window.Size.Height);
|
Config.Set(OsuConfig.Height, Window.Size.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<TargetFrameworkProfile />
|
<TargetFrameworkProfile />
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -31,6 +33,10 @@
|
|||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\ppy.OpenTK.1.1.2225.2\lib\net20\OpenTK.dll</HintPath>
|
<HintPath>..\packages\ppy.OpenTK.1.1.2225.2\lib\net20\OpenTK.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
@ -51,6 +57,15 @@
|
|||||||
<Compile Include="Graphics\Cursor\CursorContainer.cs" />
|
<Compile Include="Graphics\Cursor\CursorContainer.cs" />
|
||||||
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
|
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
|
||||||
<Compile Include="Graphics\TextAwesome.cs" />
|
<Compile Include="Graphics\TextAwesome.cs" />
|
||||||
|
<Compile Include="Online\API\APIAccess.cs" />
|
||||||
|
<Compile Include="Online\API\APIRequest.cs" />
|
||||||
|
<Compile Include="Online\API\OAuth.cs" />
|
||||||
|
<Compile Include="Online\API\OAuthToken.cs" />
|
||||||
|
<Compile Include="Online\API\Requests\GetMessagesRequest.cs" />
|
||||||
|
<Compile Include="Online\API\SecurePassword.cs" />
|
||||||
|
<Compile Include="Online\API\Requests\ListChannels.cs" />
|
||||||
|
<Compile Include="Online\Chat\Channel.cs" />
|
||||||
|
<Compile Include="Online\Chat\Message.cs" />
|
||||||
<Compile Include="OsuGame.cs" />
|
<Compile Include="OsuGame.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<packages>
|
<packages>
|
||||||
|
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
|
||||||
<package id="ppy.OpenTK" version="1.1.2225.2" targetFramework="net452" />
|
<package id="ppy.OpenTK" version="1.1.2225.2" targetFramework="net452" />
|
||||||
</packages>
|
</packages>
|
Loading…
Reference in New Issue
Block a user