Merge branch 'master' into deprecate-radial
This commit is contained in:
Коммит
6ebfaf560e
|
@ -1,27 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// AuthenticationResult class, parameters: ResponseErrorDetail(uint), ResponseData(string) and ResponseStatus(AuthenticationResultStatus)
|
||||
/// </summary>
|
||||
public class AuthenticationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication error detail
|
||||
/// </summary>
|
||||
public uint ResponseErrorDetail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication result data
|
||||
/// </summary>
|
||||
public string ResponseData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication status, could be UserCancel, ErrorHttp and Success.
|
||||
/// </summary>
|
||||
public AuthenticationResultStatus ResponseStatus { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the status of the authentication operation
|
||||
/// </summary>
|
||||
public enum AuthenticationResultStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The operation succeeded, and the response data is available.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// The operation was canceled by the user
|
||||
/// </summary>
|
||||
UserCancel,
|
||||
|
||||
/// <summary>
|
||||
/// The operation failed because a specific HTTP error was returned, for example 404
|
||||
/// </summary>
|
||||
ErrorHttp
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for data providers in this library.
|
||||
/// </summary>
|
||||
/// <typeparam name="TConfig">Query configuration type for given provider.</typeparam>
|
||||
public abstract class DataProviderBase<TConfig>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataProviderBase{TConfig}"/> class.
|
||||
/// </summary>
|
||||
public DataProviderBase()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load data from provider endpoint.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Strong typed object to parse the response items into.</typeparam>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <param name="pageIndex">The zero-based index of the page that corresponds to the items to retrieve.</param>
|
||||
/// <param name="parser">Parser to use for results.</param>
|
||||
/// <returns>Strong typed list of results.</returns>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an async method, so nesting generic types is necessary.")]
|
||||
public async Task<IEnumerable<TSchema>> LoadDataAsync<TSchema>(TConfig config, int maxRecords, int pageIndex, Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Parsers.SchemaBase
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ConfigNullException();
|
||||
}
|
||||
|
||||
if (parser == null)
|
||||
{
|
||||
throw new ParserNullException();
|
||||
}
|
||||
|
||||
ValidateConfig(config);
|
||||
|
||||
var result = await GetDataAsync(config, maxRecords, pageIndex, parser);
|
||||
if (result != null)
|
||||
{
|
||||
return result
|
||||
.Take(maxRecords)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Array.Empty<TSchema>();
|
||||
}
|
||||
|
||||
private static HttpClient httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets static instance of HttpClient.
|
||||
/// </summary>
|
||||
public static HttpClient HttpClient
|
||||
{
|
||||
get { return httpClient ?? (httpClient = new HttpClient()); }
|
||||
set { httpClient = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derived classes will have to implement this method to return provider data
|
||||
/// </summary>
|
||||
/// <param name="config">Configuration to use</param>
|
||||
/// <param name="maxRecords">Maximum number of records to return</param>
|
||||
/// <param name="pageIndex">The zero-based index of the page that corresponds to the items to retrieve.</param>
|
||||
/// <param name="parser">Parser to use</param>
|
||||
/// <typeparam name="TSchema">Schema defining data returned</typeparam>
|
||||
/// <returns>List of data</returns>
|
||||
protected abstract Task<IEnumerable<TSchema>> GetDataAsync<TSchema>(TConfig config, int maxRecords, int pageIndex, Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Parsers.SchemaBase;
|
||||
|
||||
/// <summary>
|
||||
/// Method provided by derived class to validate specified configuration
|
||||
/// </summary>
|
||||
/// <param name="config">Configuration to validate</param>
|
||||
protected abstract void ValidateConfig(TConfig config);
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for data providers in this library.
|
||||
/// </summary>
|
||||
/// <typeparam name="TConfig">Strong typed query configuration object.</typeparam>
|
||||
/// <typeparam name="TSchema">Strong typed object to parse the response items into.</typeparam>
|
||||
public abstract class DataProviderBase<TConfig, TSchema> : DataProviderBase<TConfig>
|
||||
where TSchema : Parsers.SchemaBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Load data from provider endpoint.
|
||||
/// </summary>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <param name="pageIndex">The zero-based index of the page that corresponds to the items to retrieve.</param>
|
||||
/// <returns>List of strong typed objects.</returns>
|
||||
public Task<IEnumerable<TSchema>> LoadDataAsync(TConfig config, int maxRecords = 20, int pageIndex = 0)
|
||||
{
|
||||
return LoadDataAsync(config, maxRecords, pageIndex, GetDefaultParser(config));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default parser abstract method.
|
||||
/// </summary>
|
||||
/// <param name="config">Query configuration object.</param>
|
||||
/// <returns>Strong typed default parser.</returns>
|
||||
protected abstract Parsers.IParser<TSchema> GetDefaultParser(TConfig config);
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// This class offers general purpose methods.
|
||||
/// </summary>
|
||||
internal static class ExtensionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts between enumeration value and string value.
|
||||
/// </summary>
|
||||
/// <param name="value">Enumeration.</param>
|
||||
/// <returns>Returns string value.</returns>
|
||||
private static string GetStringValue(Enum value)
|
||||
{
|
||||
string output = null;
|
||||
Type type = value.GetType();
|
||||
|
||||
FieldInfo fi = type.GetRuntimeField(value.ToString());
|
||||
Parsers.Core.StringValueAttribute[] attrs = fi.GetCustomAttributes(typeof(Parsers.Core.StringValueAttribute), false) as Parsers.Core.StringValueAttribute[];
|
||||
if (attrs != null && attrs.Length > 0)
|
||||
{
|
||||
output = attrs[0].Value;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// This gets an Uri value.
|
||||
/// </summary>
|
||||
public interface IAuthenticationBroker
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the authentication status, it could be UserCancel, ErrorHttp and Success.
|
||||
/// </summary>
|
||||
/// <param name="requestUri"> Authorization base url</param>
|
||||
/// <param name="callbackUri"> LinkedInOAuthTokens callbackUri</param>
|
||||
/// <returns> Returns a status </returns>
|
||||
Task<AuthenticationResult> Authenticate(Uri requestUri, Uri callbackUri);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic interface that all deployed service providers implement.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Reference to underlying data service provider.</typeparam>
|
||||
/// <typeparam name="U">Strongly-typed schema for data returned in list query.</typeparam>
|
||||
/// <typeparam name="V">Configuration type specifying query parameters.</typeparam>
|
||||
public interface IDataService<T, U, V>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the underlying data service provider.
|
||||
/// </summary>
|
||||
T Provider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes a request for a list of data from the given service provider.
|
||||
/// </summary>
|
||||
/// <param name="config">Describes the query on the list data request.</param>
|
||||
/// <param name="maxRecords">Specifies an upper limit to the number of records returned.</param>
|
||||
/// <param name="pageIndex">The zero-based index of the page that corresponds to the items to retrieve.</param>
|
||||
/// <returns>Returns a strongly typed list of results from the service.</returns>
|
||||
Task<List<U>> RequestAsync(V config, int maxRecords, int pageIndex = 0);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface gets a PasswordCredential, store the credential and remove the key.
|
||||
/// </summary>
|
||||
public interface IPasswordManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the user credentials.
|
||||
/// </summary>
|
||||
/// <param name="key"> Receive the storage key user and the access token </param>
|
||||
/// <returns> Returns user credential.</returns>
|
||||
PasswordCredential Get(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Store users credential.
|
||||
/// </summary>
|
||||
/// <param name="resource"> Resource</param>
|
||||
/// <param name="credential"> Username and password.</param>
|
||||
void Store(string resource, PasswordCredential credential);
|
||||
|
||||
/// <summary>
|
||||
/// Remove users credential.
|
||||
/// </summary>
|
||||
/// <param name="key"> Credential unique key</param>
|
||||
void Remove(string key);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides platform specific logic to sign request for OAuth communication
|
||||
/// </summary>
|
||||
public interface ISignatureManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate request signature
|
||||
/// </summary>
|
||||
/// <param name="baseString">String to sign</param>
|
||||
/// <param name="secret">Secret to use to sign</param>
|
||||
/// <param name="append">If true append & to the base string</param>
|
||||
/// <returns>The signed baseString to use in the OAuth requests</returns>
|
||||
string GetSignature(string baseString, string secret, bool append = false);
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface store the key value
|
||||
/// </summary>
|
||||
public interface IStorageManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the key value
|
||||
/// </summary>
|
||||
/// <param name="key"> Token value </param>
|
||||
/// <returns> Returns a string value</returns>
|
||||
Task<string> GetAsync(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the key value
|
||||
/// </summary>
|
||||
/// <param name="key"> Token key </param>
|
||||
/// <param name="value"> String value </param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task SetAsync(string key, string value);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// PasswordCredential class composed of UserName and Password, both strings.
|
||||
/// </summary>
|
||||
public class PasswordCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the username from login form
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password from login form
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for null Config.
|
||||
/// </summary>
|
||||
public class ConfigNullException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigNullException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public ConfigNullException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigNullException"/> class.
|
||||
/// Constructor accepting additional message string.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional error information.</param>
|
||||
public ConfigNullException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigNullException"/> class.
|
||||
/// Constructor accepting additional message string and inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">Additional error information.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public ConfigNullException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for config parameter being null.
|
||||
/// </summary>
|
||||
public class ConfigParameterNullException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigParameterNullException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public ConfigParameterNullException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigParameterNullException"/> class.
|
||||
/// Accepts parameter name.
|
||||
/// </summary>
|
||||
/// <param name="parameter">Name of the parameter.</param>
|
||||
public ConfigParameterNullException(string parameter)
|
||||
: base(string.Format("The parameter '{0}' in config is null.", parameter))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfigParameterNullException"/> class.
|
||||
/// Accepts parameter name and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Name of the parameter.</param>
|
||||
/// <param name="innerException">Reference to the inner exception.</param>
|
||||
public ConfigParameterNullException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for no OAuth keys being present.
|
||||
/// </summary>
|
||||
public class OAuthKeysNotPresentException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysNotPresentException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public OAuthKeysNotPresentException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysNotPresentException"/> class.
|
||||
/// Constructor with information on missing key.
|
||||
/// </summary>
|
||||
/// <param name="key">Name of the missing key.</param>
|
||||
public OAuthKeysNotPresentException(string key)
|
||||
: base(string.Format("Open Authentication Key '{0}' not present", key))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysNotPresentException"/> class.
|
||||
/// Constructor with additional message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional exception message.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public OAuthKeysNotPresentException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for revoked OAuth keys.
|
||||
/// </summary>
|
||||
public class OAuthKeysRevokedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysRevokedException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public OAuthKeysRevokedException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysRevokedException"/> class.
|
||||
/// Constructor with additional message.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message</param>
|
||||
public OAuthKeysRevokedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthKeysRevokedException"/> class.
|
||||
/// Constructor with additional message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public OAuthKeysRevokedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for null Parser.
|
||||
/// </summary>
|
||||
public class ParserNullException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParserNullException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public ParserNullException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParserNullException"/> class.
|
||||
/// Constructor with additional message.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message</param>
|
||||
public ParserNullException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ParserNullException"/> class.
|
||||
/// Constructor with additional message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public ParserNullException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for failed requests.
|
||||
/// </summary>
|
||||
public class RequestFailedException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestFailedException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public RequestFailedException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestFailedException"/> class.
|
||||
/// Constructor with additional message.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
public RequestFailedException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestFailedException"/> class.
|
||||
/// Constructor with status code and reason for request failure.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">Failure status code.</param>
|
||||
/// <param name="reason">Failure reason.</param>
|
||||
public RequestFailedException(HttpStatusCode statusCode, string reason)
|
||||
: base(string.Format("Request failed with status code {0} and reason '{1}'", (int)statusCode, reason))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequestFailedException"/> class.
|
||||
/// Constructor with additional message and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public RequestFailedException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for too many requests.
|
||||
/// </summary>
|
||||
public class TooManyRequestsException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TooManyRequestsException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public TooManyRequestsException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TooManyRequestsException"/> class.
|
||||
/// Constructor with additional message.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
public TooManyRequestsException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TooManyRequestsException"/> class.
|
||||
/// Constructor with additional message and reference to inner exception.
|
||||
/// </summary>
|
||||
/// <param name="message">Additional message.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public TooManyRequestsException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception for user not found.
|
||||
/// </summary>
|
||||
public class UserNotFoundException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserNotFoundException"/> class.
|
||||
/// Default constructor.
|
||||
/// </summary>
|
||||
public UserNotFoundException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserNotFoundException"/> class.
|
||||
/// Constructor with screen/user name information.
|
||||
/// </summary>
|
||||
/// <param name="screenName">Name of user not found.</param>
|
||||
public UserNotFoundException(string screenName)
|
||||
: base("User " + screenName + " not found.")
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserNotFoundException"/> class.
|
||||
/// Constructor with screen/user name information and inner exception.
|
||||
/// </summary>
|
||||
/// <param name="screenName">Name of user not found.</param>
|
||||
/// <param name="innerException">Reference to inner exception.</param>
|
||||
public UserNotFoundException(string screenName, Exception innerException)
|
||||
: base("User " + screenName + " not found.", innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<Project Sdk="MSBuild.Sdk.Extras">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>uap10.0.17763;netstandard2.0;NET462</TargetFrameworks>
|
||||
<Title>Windows Community Toolkit .NET Standard Services</Title>
|
||||
<Description>
|
||||
This .NET standard library enables access to different data sources such as Microsoft Graph, OneDrive, Twitter, Microsoft Translator, and LinkedIn. It is part of the Windows Community Toolkit.
|
||||
</Description>
|
||||
<PackageTags>UWP Community Toolkit Windows Microsoft Graph OneDrive Twitter Translator LinkedIn service login OAuth</PackageTags>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<NoWarn>CS8002;CS0618</NoWarn>
|
||||
<DeterministicSourcePaths Condition="'$(EnableSourceLink)' == ''">false</DeterministicSourcePaths>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0.17763'">
|
||||
<DefineConstants Condition="'$(DisableImplicitFrameworkDefines)' != 'true'">$(DefineConstants);WINRT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||
<UseWpf>true</UseWpf>
|
||||
<EnableDefaultPageItems>false</EnableDefaultPageItems>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.Parsers\Microsoft.Toolkit.Parsers.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Toolkit\Microsoft.Toolkit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='uap10.0.17763'">
|
||||
<ProjectReference Include="..\Microsoft.Toolkit.Uwp\Microsoft.Toolkit.Uwp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!('$(TargetFramework)'=='net462')">
|
||||
<Compile Remove="PlatformSpecific\NetFramework\**\*" />
|
||||
<None Remove="PlatformSpecific\NetFramework\**\*" />
|
||||
<Page Remove="PlatformSpecific\NetFramework\**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<Reference Include="System.Web" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Wpf.UI.Controls.WebView" Version="[5.0.0-preview.gb86cb1c4cb,)" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Forms.UI.Controls.WebView" Version="[5.0.0-preview.gb86cb1c4cb,)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="!('$(TargetFramework)'=='uap10.0.17763')">
|
||||
<Compile Remove="PlatformSpecific\Uwp\**\*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -1,78 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
#if WINRT
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
#endif
|
||||
namespace Microsoft.Toolkit.Services.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth Encoder.
|
||||
/// </summary>
|
||||
internal static class OAuthEncoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Url encode input string.
|
||||
/// </summary>
|
||||
/// <param name="value">Input string.</param>
|
||||
/// <returns>Encoded string.</returns>
|
||||
public static string UrlEncode(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var result = Uri.EscapeDataString(value);
|
||||
|
||||
// UrlEncode escapes with lowercase characters (e.g. %2f) but oAuth needs %2F
|
||||
result = Regex.Replace(result, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());
|
||||
|
||||
// these characters are not escaped by UrlEncode() but needed to be escaped
|
||||
result = result
|
||||
.Replace("(", "%28")
|
||||
.Replace(")", "%29")
|
||||
.Replace("$", "%24")
|
||||
.Replace("!", "%21")
|
||||
.Replace("*", "%2A")
|
||||
.Replace("'", "%27");
|
||||
|
||||
// these characters are escaped by UrlEncode() but will fail if unescaped!
|
||||
result = result.Replace("%7E", "~");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode list of parameters.
|
||||
/// </summary>
|
||||
/// <param name="parameters">List of parameters.</param>
|
||||
/// <returns>Encoded string of parameters.</returns>
|
||||
public static string UrlEncode(IEnumerable<OAuthParameter> parameters)
|
||||
{
|
||||
string rawUrl = string.Join("&", parameters.OrderBy(p => p.Key).Select(p => p.ToString()).ToArray());
|
||||
return UrlEncode(rawUrl);
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
public static string GenerateHash(string input, string key)
|
||||
{
|
||||
MacAlgorithmProvider mac = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1);
|
||||
IBuffer keyMaterial = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
|
||||
CryptographicKey cryptoKey = mac.CreateKey(keyMaterial);
|
||||
IBuffer hash = CryptographicEngine.Sign(cryptoKey, CryptographicBuffer.ConvertStringToBinary(input, BinaryStringEncoding.Utf8));
|
||||
return CryptographicBuffer.EncodeToBase64String(hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth parameter.
|
||||
/// </summary>
|
||||
internal class OAuthParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets key property.
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets value property.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OAuthParameter"/> class.
|
||||
/// Constructor accepting key and value.
|
||||
/// </summary>
|
||||
/// <param name="key">Key.</param>
|
||||
/// <param name="value">Value.</param>
|
||||
public OAuthParameter(string key, string value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ToString override.
|
||||
/// </summary>
|
||||
/// <returns>String representation</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format key / value into string.
|
||||
/// </summary>
|
||||
/// <param name="withQuotes">Whether to create quotes in string.</param>
|
||||
/// <returns>Formatted string of key / value.</returns>
|
||||
public string ToString(bool withQuotes)
|
||||
{
|
||||
string format;
|
||||
if (withQuotes)
|
||||
{
|
||||
format = "{0}=\"{1}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
format = "{0}={1}";
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.InvariantCulture, format, OAuthEncoder.UrlEncode(Key), OAuthEncoder.UrlEncode(Value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.OAuth
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth Uri extensions.
|
||||
/// </summary>
|
||||
internal static class OAuthUriExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get query parameters from Uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri to process.</param>
|
||||
/// <returns>Dictionary of query parameters.</returns>
|
||||
public static IDictionary<string, string> GetQueryParams(this Uri uri)
|
||||
{
|
||||
var dict = uri.Query.Remove(0, 1).Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get absolute Uri.
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri to process.</param>
|
||||
/// <returns>Uri without query string.</returns>
|
||||
public static string AbsoluteWithoutQuery(this Uri uri)
|
||||
{
|
||||
if (string.IsNullOrEmpty(uri.Query))
|
||||
{
|
||||
return uri.AbsoluteUri;
|
||||
}
|
||||
|
||||
return uri.AbsoluteUri.Replace(uri.Query, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize the Uri into string.
|
||||
/// </summary>
|
||||
/// <param name="uri">Uri to process.</param>
|
||||
/// <returns>Normalized string.</returns>
|
||||
public static string Normalize(this Uri uri)
|
||||
{
|
||||
var result = new StringBuilder(string.Format(CultureInfo.InvariantCulture, "{0}://{1}", uri.Scheme, uri.Host));
|
||||
if (!((uri.Scheme == "http" && uri.Port == 80) || (uri.Scheme == "https" && uri.Port == 443)))
|
||||
{
|
||||
result.Append(string.Concat(":", uri.Port));
|
||||
}
|
||||
|
||||
result.Append(uri.AbsolutePath);
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using ApplicationForm = System.Windows.Forms.Application;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
internal class NetFrameworkAuthenticationBroker : IAuthenticationBroker
|
||||
{
|
||||
public Task<AuthenticationResult> Authenticate(Uri requestUri, Uri callbackUri)
|
||||
{
|
||||
int numberForms = ApplicationForm.OpenForms.Count;
|
||||
if (numberForms > 0)
|
||||
{
|
||||
return this.AuthenticateForm(requestUri, callbackUri);
|
||||
}
|
||||
else if (Application.Current != null)
|
||||
{
|
||||
return this.AuthenticateWindow(requestUri, callbackUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Your code shouldn't reach this exception.
|
||||
throw new Exception("Cannot identify the current application. Please review your main app");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuthenticationResult> AuthenticateWindow(Uri requestUri, Uri callbackUri)
|
||||
{
|
||||
PopupWPF popupWindow;
|
||||
var taskCompletionSource = new TaskCompletionSource<AuthenticationResult>();
|
||||
popupWindow = new PopupWPF(callbackUri);
|
||||
popupWindow.Closed += (sender, e) =>
|
||||
{
|
||||
taskCompletionSource.SetResult(HandleExit(popupWindow.ActualUrl));
|
||||
};
|
||||
|
||||
popupWindow.Show();
|
||||
popupWindow.NavigateTo(requestUri.AbsoluteUri);
|
||||
return await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
public async Task<AuthenticationResult> AuthenticateForm(Uri requestUri, Uri callbackUri)
|
||||
{
|
||||
PopupForm popupForm;
|
||||
var taskCompletionSource = new TaskCompletionSource<AuthenticationResult>();
|
||||
popupForm = new PopupForm(callbackUri);
|
||||
popupForm.FormClosed += (sender, e) =>
|
||||
{
|
||||
taskCompletionSource.SetResult(HandleExit(popupForm.ActualUrl));
|
||||
};
|
||||
|
||||
popupForm.Show();
|
||||
popupForm.NavigateTo(requestUri.AbsoluteUri);
|
||||
return await taskCompletionSource.Task;
|
||||
}
|
||||
|
||||
private AuthenticationResult HandleExit(Uri actualUrl)
|
||||
{
|
||||
var result = new AuthenticationResult();
|
||||
if (actualUrl != null)
|
||||
{
|
||||
var query = System.Web.HttpUtility.ParseQueryString(actualUrl.Query);
|
||||
|
||||
result.ResponseData = query.ToString();
|
||||
result.ResponseStatus = AuthenticationResultStatus.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ResponseStatus = AuthenticationResultStatus.ErrorHttp;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using static Microsoft.Toolkit.Services.PlatformSpecific.NetFramework.PasswordManagerNativeMethods;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
internal class NetFrameworkPasswordManager : IPasswordManager
|
||||
{
|
||||
public void Store(string resource, PasswordCredential credential)
|
||||
{
|
||||
// Validations.
|
||||
byte[] byteArray = Encoding.Unicode.GetBytes(credential.Password);
|
||||
|
||||
// Go ahead with what we have are stuff it into the CredMan structures.
|
||||
Credential cred = new Credential
|
||||
{
|
||||
TargetName = resource,
|
||||
UserName = credential.UserName,
|
||||
CredentialBlob = credential.Password,
|
||||
CredentialBlobSize = (uint)byteArray.Length,
|
||||
AttributeCount = 0,
|
||||
Attributes = IntPtr.Zero,
|
||||
Comment = null,
|
||||
TargetAlias = null,
|
||||
Type = CRED_TYPE.GENERIC,
|
||||
Persist = CRED_PERSIST.LOCAL_MACHINE
|
||||
};
|
||||
NativeCredential userCredential = NativeCredential.GetNativeCredential(cred);
|
||||
|
||||
// Write the info into the CredMan storage.
|
||||
bool written = CredWrite(ref userCredential, 0);
|
||||
int lastError = Marshal.GetLastWin32Error();
|
||||
if (!written)
|
||||
{
|
||||
string message = "CredWrite failed with the error code " + lastError.ToString();
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public PasswordCredential Get(string key)
|
||||
{
|
||||
int lastError = Marshal.GetHRForLastWin32Error();
|
||||
|
||||
if (!CredRead(key, CRED_TYPE.GENERIC, 0, out var nCredPtr))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CriticalCredentialHandle credentialHandle = new CriticalCredentialHandle(nCredPtr);
|
||||
|
||||
Credential credential = credentialHandle.GetCredential();
|
||||
return new PasswordCredential
|
||||
{
|
||||
UserName = credential.UserName,
|
||||
Password = credential.CredentialBlob
|
||||
};
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
CredDelete(key, CRED_TYPE.GENERIC, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
internal class NetFrameworkSignatureManager : ISignatureManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate request signature.
|
||||
/// </summary>
|
||||
/// <param name="baseString">String to sign</param>
|
||||
/// <param name="secret">Secret to use to sign</param>
|
||||
/// <param name="append">If true append & to the base string</param>
|
||||
/// <returns>Signature.</returns>
|
||||
public string GetSignature(string baseString, string secret, bool append = false)
|
||||
{
|
||||
var key = append ? secret + "&" : secret;
|
||||
|
||||
var baseStringByte = Encoding.UTF8.GetBytes(baseString);
|
||||
var keyByte = Encoding.UTF8.GetBytes(key);
|
||||
|
||||
using (HMACSHA1 hmac = new HMACSHA1(keyByte))
|
||||
{
|
||||
hmac.Initialize();
|
||||
var hash = hmac.ComputeHash(baseStringByte);
|
||||
string base64 = Convert.ToBase64String(hash);
|
||||
return base64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.IsolatedStorage;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
internal class NetFrameworkStorageManager : IStorageManager
|
||||
{
|
||||
private const string FileName = "credential_service_data.txt";
|
||||
private const char Separator = ':';
|
||||
|
||||
public async Task<string> GetAsync(string key)
|
||||
{
|
||||
var isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
|
||||
|
||||
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(FileName, FileMode.OpenOrCreate, isoStore))
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(isoStream))
|
||||
{
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = (await reader.ReadLineAsync()).Split(Separator);
|
||||
var currentKey = line.First();
|
||||
if (currentKey == key)
|
||||
{
|
||||
return line.Last();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task SetAsync(string key, string value)
|
||||
{
|
||||
var isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
|
||||
|
||||
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(FileName, FileMode.Append, isoStore))
|
||||
{
|
||||
using (StreamWriter writer = new StreamWriter(isoStream))
|
||||
{
|
||||
return writer.WriteLineAsync(string.Concat(key, Separator, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
internal class PasswordManagerNativeMethods
|
||||
{
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr credentialPtr);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern bool CredWrite([In] ref NativeCredential userCredential, [In] uint flags);
|
||||
|
||||
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
|
||||
internal static extern bool CredFree([In] IntPtr cred);
|
||||
|
||||
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
|
||||
internal static extern bool CredDelete(string target, CRED_TYPE type, int flags);
|
||||
|
||||
internal enum CRED_TYPE : uint
|
||||
{
|
||||
GENERIC = 1,
|
||||
DOMAIN_PASSWORD = 2,
|
||||
DOMAIN_CERTIFICATE = 3,
|
||||
DOMAIN_VISIBLE_PASSWORD = 4,
|
||||
GENERIC_CERTIFICATE = 5,
|
||||
DOMAIN_EXTENDED = 6,
|
||||
MAXIMUM = 7, // Maximum supported cred type
|
||||
MAXIMUM_EX = MAXIMUM + 1000, // Allow new applications to run on old OSes
|
||||
}
|
||||
|
||||
internal enum CRED_PERSIST : uint
|
||||
{
|
||||
SESSION = 1,
|
||||
LOCAL_MACHINE = 2,
|
||||
ENTERPRISE = 3,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct NativeCredential
|
||||
{
|
||||
internal uint Flags;
|
||||
internal CRED_TYPE Type;
|
||||
internal IntPtr TargetName;
|
||||
internal IntPtr Comment;
|
||||
internal System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
|
||||
internal uint CredentialBlobSize;
|
||||
internal IntPtr CredentialBlob;
|
||||
internal uint Persist;
|
||||
internal uint AttributeCount;
|
||||
internal IntPtr Attributes;
|
||||
internal IntPtr TargetAlias;
|
||||
internal IntPtr UserName;
|
||||
|
||||
/// <summary>
|
||||
/// This method derives a NativeCredential instance from a given Credential instance.
|
||||
/// </summary>
|
||||
/// <param name="cred">The managed Credential counterpart containing data to be stored.</param>
|
||||
/// <returns>A NativeCredential instance that is derived from the given Credential
|
||||
/// instance.</returns>
|
||||
internal static NativeCredential GetNativeCredential(Credential cred)
|
||||
{
|
||||
return new NativeCredential
|
||||
{
|
||||
AttributeCount = 0,
|
||||
Attributes = IntPtr.Zero,
|
||||
Comment = IntPtr.Zero,
|
||||
TargetAlias = IntPtr.Zero,
|
||||
Type = CRED_TYPE.GENERIC,
|
||||
Persist = (uint)cred.Persist,
|
||||
CredentialBlobSize = (uint)cred.CredentialBlobSize,
|
||||
TargetName = Marshal.StringToCoTaskMemUni(cred.TargetName),
|
||||
CredentialBlob = Marshal.StringToCoTaskMemUni(cred.CredentialBlob),
|
||||
UserName = Marshal.StringToCoTaskMemUni(cred.UserName)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct Credential
|
||||
{
|
||||
internal uint Flags;
|
||||
internal CRED_TYPE Type;
|
||||
internal string TargetName;
|
||||
internal string Comment;
|
||||
internal System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
|
||||
internal uint CredentialBlobSize;
|
||||
internal string CredentialBlob;
|
||||
internal CRED_PERSIST Persist;
|
||||
internal uint AttributeCount;
|
||||
internal IntPtr Attributes;
|
||||
internal string TargetAlias;
|
||||
internal string UserName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle and create the credential.
|
||||
/// </summary>
|
||||
internal sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
// Set the handle.
|
||||
internal CriticalCredentialHandle(IntPtr preexistingHandle)
|
||||
{
|
||||
SetHandle(preexistingHandle);
|
||||
}
|
||||
|
||||
internal Credential GetCredential()
|
||||
{
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// Get the Credential from the mem location
|
||||
NativeCredential nativeCredential = (NativeCredential)Marshal.PtrToStructure(handle, typeof(NativeCredential));
|
||||
|
||||
// Create a managed Credential type and fill it with data from the native counterpart.
|
||||
return new Credential
|
||||
{
|
||||
CredentialBlobSize = nativeCredential.CredentialBlobSize,
|
||||
CredentialBlob = Marshal.PtrToStringUni(nativeCredential.CredentialBlob, (int)nativeCredential.CredentialBlobSize / 2),
|
||||
UserName = Marshal.PtrToStringUni(nativeCredential.UserName),
|
||||
TargetName = Marshal.PtrToStringUni(nativeCredential.TargetName),
|
||||
TargetAlias = Marshal.PtrToStringUni(nativeCredential.TargetAlias),
|
||||
Type = nativeCredential.Type,
|
||||
Flags = nativeCredential.Flags,
|
||||
Persist = (CRED_PERSIST)nativeCredential.Persist
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid CriticalHandle!");
|
||||
}
|
||||
}
|
||||
|
||||
// Perform any specific actions to release the handle in the ReleaseHandle method.
|
||||
// Often, you need to use PInvoke to make a call into the Win32 API to release the
|
||||
// handle. In this case, however, we can use the Marshal class to release the unmanaged memory.
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
// If the handle was set, free it. Return success.
|
||||
if (!IsInvalid)
|
||||
{
|
||||
// NOTE: We should also ZERO out the memory allocated to the handle, before freeing it
|
||||
// so there are no traces of the sensitive data left in memory.
|
||||
CredFree(handle);
|
||||
|
||||
// Mark the handle as invalid for future users.
|
||||
SetHandleAsInvalid();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return false.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
|
||||
partial class PopupForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.webView1 = new Microsoft.Toolkit.Forms.UI.Controls.WebView();
|
||||
((System.ComponentModel.ISupportInitialize)(this.webView1)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// webView1
|
||||
//
|
||||
this.webView1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.webView1.Location = new System.Drawing.Point(0, 0);
|
||||
this.webView1.MinimumSize = new System.Drawing.Size(20, 20);
|
||||
this.webView1.Name = "webView1";
|
||||
this.webView1.Size = new System.Drawing.Size(800, 450);
|
||||
this.webView1.TabIndex = 0;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.webView1);
|
||||
this.Name = "Form1";
|
||||
this.Text = "Form1";
|
||||
((System.ComponentModel.ISupportInitialize)(this.webView1)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Microsoft.Toolkit.Forms.UI.Controls.WebView webView1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Service WebView for windows forms
|
||||
/// </summary>
|
||||
public partial class PopupForm : Form
|
||||
{
|
||||
private string initialHost;
|
||||
private string callbackHost;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current URL before closing the form
|
||||
/// </summary>
|
||||
public Uri ActualUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PopupForm"/> class.
|
||||
/// </summary>
|
||||
/// <param name="callbackUrl">Uri callback url</param>
|
||||
public PopupForm(Uri callbackUrl)
|
||||
{
|
||||
InitializeComponent();
|
||||
webView1.NavigationStarting += (s, e) => WebViewNavigationStartingHandler(e.Uri);
|
||||
callbackHost = GetTopLevelDomain(callbackUrl);
|
||||
}
|
||||
|
||||
private void WebViewNavigationStartingHandler(Uri uri)
|
||||
{
|
||||
var topLevelDomain = GetTopLevelDomain(uri);
|
||||
if (initialHost != topLevelDomain && topLevelDomain == callbackHost)
|
||||
{
|
||||
ActualUrl = uri;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a given url in the WebView
|
||||
/// </summary>
|
||||
/// <param name="url">Url string to navigate to.</param>
|
||||
public void NavigateTo(string url)
|
||||
{
|
||||
initialHost = GetTopLevelDomain(url);
|
||||
webView1.Navigate(url);
|
||||
}
|
||||
|
||||
private string GetTopLevelDomain(string url)
|
||||
{
|
||||
return GetTopLevelDomain(new Uri(url));
|
||||
}
|
||||
|
||||
private string GetTopLevelDomain(Uri url)
|
||||
{
|
||||
var hostParts = url.Host.Split('.').Select(x => x.ToString());
|
||||
if (hostParts.Count() > 1)
|
||||
{
|
||||
return hostParts.ElementAt(1);
|
||||
}
|
||||
|
||||
return hostParts.ElementAt(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<Window x:Class="Microsoft.Toolkit.Services.PlatformSpecific.NetFramework.PopupWPF"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Microsoft.Toolkit.Services.PlatformSpecific.NetFramework"
|
||||
xmlns:Controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView"
|
||||
mc:Ignorable="d"
|
||||
Title="PopupPage" Height="450" Width="800">
|
||||
<Grid>
|
||||
<Controls:WebView Grid.Row="0" x:Name="WebView1" Width="800" Height="800" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
|
||||
</Grid>
|
||||
</Window>
|
|
@ -1,83 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.NetFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for PopupWPF.xaml
|
||||
/// </summary>
|
||||
public partial class PopupWPF : Window
|
||||
{
|
||||
private string initialHost;
|
||||
private string callbackHost;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current URL before closing the form
|
||||
/// </summary>
|
||||
public Uri ActualUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PopupWPF"/> class.
|
||||
/// </summary>
|
||||
/// <param name="callbackUrl">Uri callback url</param>
|
||||
public PopupWPF(Uri callbackUrl)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
WebView1.NavigationStarting += (s, e) => WebViewNavigationStartingHandler(e.Uri);
|
||||
callbackHost = GetTopLevelDomain(callbackUrl);
|
||||
}
|
||||
|
||||
private void WebViewNavigationStartingHandler(Uri uri)
|
||||
{
|
||||
var topLevelDomain = GetTopLevelDomain(uri);
|
||||
if (initialHost != topLevelDomain && topLevelDomain == callbackHost)
|
||||
{
|
||||
ActualUrl = uri;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a given url in the WebView
|
||||
/// </summary>
|
||||
/// <param name="url">Url string to navigate to.</param>
|
||||
public void NavigateTo(string url)
|
||||
{
|
||||
initialHost = GetTopLevelDomain(url);
|
||||
WebView1.Navigate(url);
|
||||
}
|
||||
|
||||
private string GetTopLevelDomain(string url)
|
||||
{
|
||||
return GetTopLevelDomain(new Uri(url));
|
||||
}
|
||||
|
||||
private string GetTopLevelDomain(Uri url)
|
||||
{
|
||||
var hostParts = url.Host.Split('.').Select(x => x.ToString());
|
||||
if (hostParts.Count() > 1)
|
||||
{
|
||||
return hostParts.ElementAt(1);
|
||||
}
|
||||
|
||||
return hostParts.ElementAt(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using Windows.Security.Authentication.Web;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication Broker
|
||||
/// </summary>
|
||||
internal class UwpAuthenticationBroker : IAuthenticationBroker
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication process
|
||||
/// </summary>
|
||||
/// <param name="requestUri"> Request Uri</param>
|
||||
/// <param name="callbackUri"> Uri result</param>
|
||||
/// <returns> Returns login status</returns>
|
||||
public async Task<AuthenticationResult> Authenticate(Uri requestUri, Uri callbackUri)
|
||||
{
|
||||
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(
|
||||
WebAuthenticationOptions.None,
|
||||
requestUri,
|
||||
callbackUri);
|
||||
|
||||
switch (result.ResponseStatus)
|
||||
{
|
||||
case WebAuthenticationStatus.Success:
|
||||
return new AuthenticationResult { ResponseData = result.ResponseData, ResponseStatus = AuthenticationResultStatus.Success };
|
||||
case WebAuthenticationStatus.UserCancel:
|
||||
return new AuthenticationResult { ResponseData = result.ResponseData, ResponseStatus = AuthenticationResultStatus.UserCancel, ResponseErrorDetail = result.ResponseErrorDetail };
|
||||
case WebAuthenticationStatus.ErrorHttp:
|
||||
return new AuthenticationResult { ResponseData = result.ResponseData, ResponseStatus = AuthenticationResultStatus.ErrorHttp, ResponseErrorDetail = result.ResponseErrorDetail };
|
||||
default:
|
||||
// TODO: Change with correct name;
|
||||
throw new ArgumentException("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using Windows.Security.Credentials;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp
|
||||
{
|
||||
/// <summary>
|
||||
/// Password Manager
|
||||
/// </summary>
|
||||
internal class UwpPasswordManager : IPasswordManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Password vault used to store access tokens
|
||||
/// </summary>
|
||||
private readonly PasswordVault _vault;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UwpPasswordManager"/> class.
|
||||
/// </summary>
|
||||
public UwpPasswordManager()
|
||||
{
|
||||
_vault = new PasswordVault();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Toolkit.Services.Core.PasswordCredential Get(string key)
|
||||
{
|
||||
var credentials = RetrievePasswordCredential(key);
|
||||
if (credentials == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Toolkit.Services.Core.PasswordCredential { Password = credentials.Password, UserName = credentials.UserName };
|
||||
}
|
||||
|
||||
private Windows.Security.Credentials.PasswordCredential RetrievePasswordCredential(string key)
|
||||
{
|
||||
var passwordCredentials = _vault.RetrieveAll();
|
||||
var temp = passwordCredentials.FirstOrDefault(c => c.Resource == key);
|
||||
|
||||
if (temp == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _vault.Retrieve(temp.Resource, temp.UserName);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Remove(string key)
|
||||
{
|
||||
_vault.Remove(RetrievePasswordCredential(key));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Store(string resource, Toolkit.Services.Core.PasswordCredential credentials)
|
||||
{
|
||||
var passwordCredential = new Windows.Security.Credentials.PasswordCredential(resource, credentials.UserName, credentials.Password);
|
||||
_vault.Add(passwordCredential);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp
|
||||
{
|
||||
/// <summary>
|
||||
/// UWP specific signature generator using cryptographic library
|
||||
/// </summary>
|
||||
internal class UwpSignatureManager : ISignatureManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate request signature.
|
||||
/// </summary>
|
||||
/// <param name="baseString">String to sign</param>
|
||||
/// <param name="secret">Secret to use to sign</param>
|
||||
/// <param name="append">If true append & to the base string</param>
|
||||
/// <returns>Signature.</returns>
|
||||
public string GetSignature(string baseString, string secret, bool append = false)
|
||||
{
|
||||
var key = append ? secret + "&" : secret;
|
||||
|
||||
IBuffer keyMaterial = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
|
||||
MacAlgorithmProvider mac = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1);
|
||||
CryptographicKey cryptoKey = mac.CreateKey(keyMaterial);
|
||||
IBuffer dataToBeSigned = CryptographicBuffer.ConvertStringToBinary(baseString, BinaryStringEncoding.Utf8);
|
||||
IBuffer hash = CryptographicEngine.Sign(cryptoKey, dataToBeSigned);
|
||||
return CryptographicBuffer.EncodeToBase64String(hash);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using Windows.Security.Credentials;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.PlatformSpecific.Uwp
|
||||
{
|
||||
/// <summary>
|
||||
/// UWP specific implementation for IStorageManager using ApplicationData and LocalSettings
|
||||
/// </summary>
|
||||
internal class UwpStorageManager : IStorageManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Read the storage to return the key if exists if not null;
|
||||
/// </summary>
|
||||
/// <param name="key">Key to lookup</param>
|
||||
/// <returns>Return string value if exists if not null</returns>
|
||||
public Task<string> GetAsync(string key)
|
||||
{
|
||||
return Task.FromResult<string>(ApplicationData.Current.LocalSettings.Values[key]?.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the value in the key inside the storage
|
||||
/// </summary>
|
||||
/// <param name="key">Key name in storage</param>
|
||||
/// <param name="value">Value associated to the storage</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public Task SetAsync(string key, string value)
|
||||
{
|
||||
ApplicationData.Current.LocalSettings.Values[key] = value;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
|
||||
<Library Name="Microsoft.Toolkit.Services">
|
||||
<Namespace Name="System.Text.Json.Serialization.Converters" Dynamic="Required All"/>
|
||||
</Library>
|
||||
</Directives>
|
|
@ -1,22 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant strings for LinkedIn Service.
|
||||
/// </summary>
|
||||
public static class LinkedInConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage key name for access token.
|
||||
/// </summary>
|
||||
public static readonly string STORAGEKEYACCESSTOKEN = "LinkedInAccessToken";
|
||||
|
||||
/// <summary>
|
||||
/// Storage key name for user name.
|
||||
/// </summary>
|
||||
public static readonly string STORAGEKEYUSER = "LinkedInUser";
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type representation of Content.
|
||||
/// </summary>
|
||||
public class LinkedInContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets title property.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets description property.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets submitted url property.
|
||||
/// </summary>
|
||||
public string SubmittedUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets submitted image url property.
|
||||
/// </summary>
|
||||
public string SubmittedImageUrl { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration object for specifying richer query information.
|
||||
/// </summary>
|
||||
public class LinkedInDataConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the query string for filtering service results.
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,334 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
#if WINRT
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.Uwp;
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.NetFramework;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Data Provider for connecting to LinkedIn service.
|
||||
/// </summary>
|
||||
public class LinkedInDataProvider
|
||||
{
|
||||
private const string _oAuthBaseUrl = "https://www.linkedin.com/uas/oauth2/";
|
||||
private const string _baseUrl = "https://api.linkedin.com/v1";
|
||||
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets logged in user information.
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the provider is already logged in
|
||||
/// </summary>
|
||||
public bool LoggedIn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets requiredPermissions property.
|
||||
/// </summary>
|
||||
public LinkedInPermissions RequiredPermissions { get; set; }
|
||||
|
||||
private readonly IAuthenticationBroker _authentication;
|
||||
private readonly IPasswordManager _passwordManager;
|
||||
private readonly IStorageManager _storageManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tokens property.
|
||||
/// </summary>
|
||||
public LinkedInOAuthTokens Tokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkedInDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
/// <param name="requiredPermissions">Required permissions for the session.</param>
|
||||
/// <param name="authentication">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Password Manager interface, store the password.</param>
|
||||
/// <param name="storageManager">Storage Manager interface.</param>
|
||||
public LinkedInDataProvider(LinkedInOAuthTokens tokens, LinkedInPermissions requiredPermissions, IAuthenticationBroker authentication, IPasswordManager passwordManager, IStorageManager storageManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tokens.ClientSecret))
|
||||
{
|
||||
throw new ArgumentException("Missing client secret key");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.ClientId))
|
||||
{
|
||||
throw new ArgumentException("Missing client ID");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.CallbackUri))
|
||||
{
|
||||
throw new ArgumentException("Missing callback uri");
|
||||
}
|
||||
|
||||
// Check if its a valid combination of LinkedInPermissions
|
||||
if ((~(int)LinkedInPermissionsHelpers.AllPermissions & (int)requiredPermissions) != 0)
|
||||
{
|
||||
throw new ArgumentException("Error retrieving required permissions");
|
||||
}
|
||||
|
||||
Tokens = tokens;
|
||||
RequiredPermissions = requiredPermissions;
|
||||
_authentication = authentication ?? throw new ArgumentException("Invalid AuthenticationBroker");
|
||||
_storageManager = storageManager ?? throw new ArgumentException("Invalid StorageManager");
|
||||
_passwordManager = passwordManager ?? throw new ArgumentException("Invalid PasswordManager");
|
||||
}
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkedInDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
/// <param name="requiredPermissions">Required permissions for the session.</param>
|
||||
public LinkedInDataProvider(LinkedInOAuthTokens tokens, LinkedInPermissions requiredPermissions)
|
||||
: this(tokens, requiredPermissions, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager())
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LinkedInDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
/// <param name="requiredPermissions">Required permissions for the session.</param>
|
||||
public LinkedInDataProvider(LinkedInOAuthTokens tokens, LinkedInPermissions requiredPermissions)
|
||||
: this(tokens, requiredPermissions, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager())
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Log user in to LinkedIn.
|
||||
/// </summary>
|
||||
/// <returns>Boolean indicating login success.</returns>
|
||||
public async Task<bool> LoginAsync()
|
||||
{
|
||||
var user = await _storageManager.GetAsync(LinkedInConstants.STORAGEKEYUSER);
|
||||
var credential = _passwordManager.Get(LinkedInConstants.STORAGEKEYACCESSTOKEN);
|
||||
if (!string.IsNullOrEmpty(user) && credential != null)
|
||||
{
|
||||
Tokens.AccessToken = credential.Password;
|
||||
Username = user;
|
||||
LoggedIn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
string authorizeCode = await GetAuthorizeCodeAsync(Tokens, RequiredPermissions);
|
||||
|
||||
if (!string.IsNullOrEmpty(authorizeCode))
|
||||
{
|
||||
var accessToken = await GetAccessTokenAsync(Tokens, authorizeCode);
|
||||
|
||||
if (!string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
Tokens.AccessToken = accessToken;
|
||||
|
||||
_passwordManager.Store(LinkedInConstants.STORAGEKEYACCESSTOKEN, new PasswordCredential { UserName = LinkedInConstants.STORAGEKEYUSER, Password = accessToken });
|
||||
await _storageManager.SetAsync(LinkedInConstants.STORAGEKEYUSER, LinkedInConstants.STORAGEKEYUSER);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
LoggedIn = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user out of LinkedIn.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
var credential = _passwordManager.Get(LinkedInConstants.STORAGEKEYACCESSTOKEN);
|
||||
|
||||
if (credential != null)
|
||||
{
|
||||
_passwordManager.Remove(LinkedInConstants.STORAGEKEYACCESSTOKEN);
|
||||
await _storageManager.SetAsync(LinkedInConstants.STORAGEKEYUSER, null);
|
||||
}
|
||||
|
||||
LoggedIn = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around REST API for making data request.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Schema to use</typeparam>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
/// <param name="maxRecords">Upper limit for records returned.</param>
|
||||
/// <param name="startRecord">Index of paged results.</param>
|
||||
/// <param name="fields">A comma separated string of required fields, which will have strongly typed representation in the model passed in.</param>
|
||||
/// <returns>Strongly typed list of results.</returns>
|
||||
public async Task<IEnumerable<TSchema>> GetDataAsync<TSchema>(LinkedInDataConfig config, int maxRecords, int startRecord = 0, string fields = "id")
|
||||
{
|
||||
var parser = new LinkedInParser<TSchema>();
|
||||
|
||||
var url = $"{_baseUrl}{config.Query}/~:({fields})?oauth2_access_token={Tokens.AccessToken}&format=json&count={maxRecords}&start={startRecord}";
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
|
||||
request.Headers.Connection.TryParseAdd("Keep-Alive");
|
||||
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (response.IsSuccessStatusCode && !string.IsNullOrEmpty(data))
|
||||
{
|
||||
return parser.Parse(data);
|
||||
}
|
||||
|
||||
throw new RequestFailedException((System.Net.HttpStatusCode)response.StatusCode, data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Share data to LinkedIn.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Schema of data to share.</typeparam>
|
||||
/// <typeparam name="U">Type of response object.</typeparam>
|
||||
/// <param name="dataToShare">Share request content.</param>
|
||||
/// <returns>Boolean indicating success or failure.</returns>
|
||||
public async Task<U> ShareDataAsync<T, U>(T dataToShare)
|
||||
{
|
||||
var shareRequest = dataToShare as LinkedInShareRequest;
|
||||
if (shareRequest != null)
|
||||
{
|
||||
LinkedInVisibility.ParseVisibilityStringToEnum(shareRequest.Visibility.Code);
|
||||
|
||||
var requestParser = new LinkedInParser<LinkedInShareRequest>();
|
||||
|
||||
var url = $"{_baseUrl}/people/~/shares?oauth2_access_token={Tokens.AccessToken}&format=json";
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
||||
request.Headers.Add("x-li-format", "json");
|
||||
var stringContent = requestParser.Parse(shareRequest);
|
||||
request.Content = new StringContent(stringContent, Encoding.UTF8, "application/json");
|
||||
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
var responseParser = new LinkedInParser<U>();
|
||||
|
||||
var listResults = responseParser.Parse(data) as List<U>;
|
||||
return listResults[0];
|
||||
}
|
||||
|
||||
return default(U);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check validity of configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
protected void ValidateConfig(LinkedInDataConfig config)
|
||||
{
|
||||
if (config?.Query == null)
|
||||
{
|
||||
throw new ConfigParameterNullException(nameof(config.Query));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetAccessTokenAsync(LinkedInOAuthTokens tokens, string authorizeCode)
|
||||
{
|
||||
var url = $"{_oAuthBaseUrl}accessToken?grant_type=authorization_code"
|
||||
+ "&code=" + authorizeCode
|
||||
+ "&redirect_uri=" + Uri.EscapeDataString(tokens.CallbackUri)
|
||||
+ "&client_id=" + tokens.ClientId
|
||||
+ "&client_secret=" + tokens.ClientSecret;
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, new Uri(url));
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
using var jsonStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var jsonDoc = await JsonDocument.ParseAsync(jsonStream).ConfigureAwait(false);
|
||||
|
||||
var value = jsonDoc.RootElement.GetProperty("access_token");
|
||||
return value.GetString();
|
||||
}
|
||||
|
||||
private async Task<string> GetAuthorizeCodeAsync(LinkedInOAuthTokens tokens, LinkedInPermissions permissions)
|
||||
{
|
||||
string scopes = ConvertPermissionsToEncodedScopeString(permissions);
|
||||
|
||||
var url = $"{_oAuthBaseUrl}authorization?response_type=code"
|
||||
+ "&client_id=" + tokens.ClientId
|
||||
+ "&state=STATE"
|
||||
+ "&redirect_uri=" + Uri.EscapeDataString(tokens.CallbackUri)
|
||||
+ "&" + scopes;
|
||||
|
||||
var startUri = new Uri(url);
|
||||
var endUri = new Uri(tokens.CallbackUri);
|
||||
|
||||
var result = await _authentication.Authenticate(startUri, endUri);
|
||||
switch (result.ResponseStatus)
|
||||
{
|
||||
case AuthenticationResultStatus.Success:
|
||||
{
|
||||
var response = result.ResponseData;
|
||||
IDictionary<string, string> dictionary = new Dictionary<string, string>();
|
||||
var split = response.Split('?');
|
||||
foreach (var keyValue in split[split.Length - 1].Split('&'))
|
||||
{
|
||||
var keyValueSplit = keyValue.Split('=');
|
||||
if (keyValueSplit.Length == 2)
|
||||
{
|
||||
dictionary.Add(keyValueSplit[0], keyValueSplit[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return dictionary["code"];
|
||||
}
|
||||
|
||||
case AuthenticationResultStatus.ErrorHttp:
|
||||
Debug.WriteLine("WAB failed, message={0}", result.ResponseErrorDetail.ToString());
|
||||
return string.Empty;
|
||||
|
||||
case AuthenticationResultStatus.UserCancel:
|
||||
Debug.WriteLine("WAB user aborted.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string ConvertPermissionsToEncodedScopeString(LinkedInPermissions requiredPermissions)
|
||||
{
|
||||
StringBuilder scope = new StringBuilder();
|
||||
|
||||
foreach (LinkedInPermissions value in Enum.GetValues(typeof(LinkedInPermissions)))
|
||||
{
|
||||
if ((requiredPermissions & value) != LinkedInPermissions.NotSet)
|
||||
{
|
||||
var name = value.ToString().ToLower();
|
||||
name = name.Replace("readwrite", "rw_");
|
||||
name = name.Replace("read", "r_");
|
||||
name = name.Replace("write", "w_");
|
||||
name = name.Replace("companyadmin", "company_admin");
|
||||
|
||||
scope.Append($"{name} ");
|
||||
}
|
||||
}
|
||||
|
||||
return "scope=" + Uri.EscapeDataString(scope.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// LinkedIn OAuth tokens.
|
||||
/// </summary>
|
||||
public class LinkedInOAuthTokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets clientId.
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets clientSecret.
|
||||
/// </summary>
|
||||
public string ClientSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets callback Uri.
|
||||
/// </summary>
|
||||
public string CallbackUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access token.
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access token Secret.
|
||||
/// </summary>
|
||||
public string AccessTokenSecret { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse results into strong type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to parse into.</typeparam>
|
||||
public class LinkedInParser<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Take string data and parse into strong data type.
|
||||
/// </summary>
|
||||
/// <param name="data">String data.</param>
|
||||
/// <returns>Returns strong type.</returns>
|
||||
public IEnumerable<T> Parse(string data)
|
||||
{
|
||||
List<T> results;
|
||||
|
||||
try
|
||||
{
|
||||
results = JsonSerializer.Deserialize<List<T>>(data);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
T linkedInResult = JsonSerializer.Deserialize<T>(data);
|
||||
results = new List<T> { linkedInResult };
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take strong type and return corresponding JSON string.
|
||||
/// </summary>
|
||||
/// <param name="dataToShare">Strong typed instance.</param>
|
||||
/// <returns>Returns string data.</returns>
|
||||
public string Parse(T dataToShare)
|
||||
{
|
||||
return JsonSerializer.Serialize(dataToShare, typeof(T), new JsonSerializerOptions { IgnoreNullValues = true });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// List of user related data permissions
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LinkedInPermissions
|
||||
{
|
||||
/// <summary>
|
||||
/// Not set
|
||||
/// </summary>
|
||||
NotSet = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Read - Basic profile (r_basicprofile)
|
||||
/// </summary>
|
||||
ReadBasicProfile = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Read - Email Address (r_emailaddress)
|
||||
/// </summary>
|
||||
ReadEmailAddress = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Read / Write - Company Admin (rw_company_admin)
|
||||
/// </summary>
|
||||
ReadWriteCompanyAdmin = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Write - Share (w_share)
|
||||
/// </summary>
|
||||
WriteShare = 8
|
||||
}
|
||||
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
internal static class LinkedInPermissionsHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal AllPermissions for LinkedInPermissions, so we don't expose it. Keep it in sync with <see cref="LinkedInPermissions"/>
|
||||
/// </summary>
|
||||
internal const LinkedInPermissions AllPermissions =
|
||||
LinkedInPermissions.ReadBasicProfile |
|
||||
LinkedInPermissions.ReadEmailAddress |
|
||||
LinkedInPermissions.ReadWriteCompanyAdmin |
|
||||
LinkedInPermissions.WriteShare;
|
||||
}
|
||||
#pragma warning restore SA1649 // File name should match first type name
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strongly typed LinkedIn Basic Profile. More details here https://developer.linkedin.com/docs/fields/basic-profile.
|
||||
/// </summary>
|
||||
public partial class LinkedInProfile
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a string description of the strongly typed properties in this model.
|
||||
/// </summary>
|
||||
public static string Fields => "first-name,last-name,headline,id,picture-url,site-standard-profile-request,num-connections,summary,public-profile-url";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets firstName property.
|
||||
/// </summary>
|
||||
public string FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets headline property.
|
||||
/// </summary>
|
||||
public string Headline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets id property.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets lastname property.
|
||||
/// </summary>
|
||||
public string LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets picture-url property.
|
||||
/// </summary>
|
||||
public string PictureUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets num-connections property.
|
||||
/// </summary>
|
||||
public string NumConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets summary property.
|
||||
/// </summary>
|
||||
public string Summary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets public-profile-url property.
|
||||
/// </summary>
|
||||
public string PublicProfileUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets email-address property. Requires r_emailaddress permission.
|
||||
/// </summary>
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets siteStandardProfileRequest property.
|
||||
/// </summary>
|
||||
public LinkedInProfileRequest SiteStandardProfileRequest { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strongly typed SiteStandardProfileRequest class.
|
||||
/// </summary>
|
||||
public class LinkedInProfileRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets url property.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
#if WINRT
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.Uwp;
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.NetFramework;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for connecting to LinkedIn.
|
||||
/// </summary>
|
||||
public class LinkedInService
|
||||
{
|
||||
/// <summary>
|
||||
/// Private singleton field.
|
||||
/// </summary>
|
||||
private static LinkedInService _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets public singleton property.
|
||||
/// </summary>
|
||||
public static LinkedInService Instance => _instance ?? (_instance = new LinkedInService());
|
||||
|
||||
private LinkedInDataProvider _provider;
|
||||
|
||||
private LinkedInOAuthTokens _oAuthTokens;
|
||||
|
||||
private LinkedInPermissions _requiredPermissions;
|
||||
|
||||
private IAuthenticationBroker _authenticationBroker;
|
||||
private IPasswordManager _passwordManager;
|
||||
private IStorageManager _storageManager;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to an instance of the underlying data provider.
|
||||
/// </summary>
|
||||
public LinkedInDataProvider Provider => _provider ?? (_provider = new LinkedInDataProvider(_oAuthTokens, _requiredPermissions, _authenticationBroker, _passwordManager, _storageManager));
|
||||
|
||||
private LinkedInService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user in to LinkedIn.
|
||||
/// </summary>
|
||||
/// <returns>Returns success or failure of login attempt.</returns>
|
||||
public Task<bool> LoginAsync()
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("Initialized needs to be called first.");
|
||||
}
|
||||
|
||||
return Provider.LoginAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Share content to LinkedIn.
|
||||
/// </summary>
|
||||
/// <param name="commentContainingUrl">Comment containing a Url.</param>
|
||||
/// <param name="visibilityCode">Code for who to share with.</param>
|
||||
/// <returns>Boolean indicating success or failure.</returns>
|
||||
public Task<LinkedInShareResponse> ShareActivityAsync(string commentContainingUrl, LinkedInShareVisibility visibilityCode = LinkedInShareVisibility.ConnectionsOnly)
|
||||
{
|
||||
var shareRequest = new LinkedInShareRequest
|
||||
{
|
||||
Comment = commentContainingUrl,
|
||||
Visibility = new LinkedInVisibility { Code = LinkedInVisibility.ParseVisibilityEnumToString(visibilityCode) }
|
||||
};
|
||||
|
||||
return ShareActivityAsync(shareRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Share content to LinkedIn.
|
||||
/// </summary>
|
||||
/// <param name="shareRequest">Share request.</param>
|
||||
/// <returns>Boolean indicating success or failure.</returns>
|
||||
public Task<LinkedInShareResponse> ShareActivityAsync(LinkedInShareRequest shareRequest)
|
||||
{
|
||||
return Provider.ShareDataAsync<LinkedInShareRequest, LinkedInShareResponse>(shareRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user out of LinkedIn.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public Task LogoutAsync()
|
||||
{
|
||||
_isInitialized = false;
|
||||
return Provider.LogoutAsync();
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information for UWP.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <param name="requiredPermissions">Scope / permissions app requires user to sign up for.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(LinkedInOAuthTokens oAuthTokens, LinkedInPermissions requiredPermissions = LinkedInPermissions.NotSet)
|
||||
{
|
||||
return Initialize(oAuthTokens, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager(), requiredPermissions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client Id.</param>
|
||||
/// <param name="clientSecret">Client secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at www.linkedin.com/developer/apps/ (can be arbitrary).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string clientId, string clientSecret, string callbackUri)
|
||||
{
|
||||
return Initialize(clientId, clientSecret, callbackUri, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information for UWP.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <param name="requiredPermissions">Scope / permissions app requires user to sign up for.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(LinkedInOAuthTokens oAuthTokens, LinkedInPermissions requiredPermissions = LinkedInPermissions.NotSet)
|
||||
{
|
||||
return Initialize(oAuthTokens, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager(), requiredPermissions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client Id.</param>
|
||||
/// <param name="clientSecret">Client secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at www.linkedin.com/developer/apps/ (can be arbitrary).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string clientId, string clientSecret, string callbackUri)
|
||||
{
|
||||
return Initialize(clientId, clientSecret, callbackUri, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager());
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="clientId">Client Id.</param>
|
||||
/// <param name="clientSecret">Client secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at www.linkedin.com/developer/apps/ (can be arbitrary).</param>
|
||||
/// <param name="authentication">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Password Manager interface, store the password.</param>
|
||||
/// <param name="storageManager">Storage Manager interface.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string clientId, string clientSecret, string callbackUri, IAuthenticationBroker authentication, IPasswordManager passwordManager, IStorageManager storageManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(clientSecret))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientSecret));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(callbackUri))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callbackUri));
|
||||
}
|
||||
|
||||
var oAuthTokens = new LinkedInOAuthTokens
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
CallbackUri = callbackUri
|
||||
};
|
||||
|
||||
return Initialize(oAuthTokens, authentication, passwordManager, storageManager, LinkedInPermissions.ReadBasicProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <param name="authentication">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Password Manager interface, store the password.</param>
|
||||
/// <param name="storageManager">Storage Manager interface.</param>
|
||||
/// <param name="requiredPermissions">Scope / permissions app requires user to sign up for.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(LinkedInOAuthTokens oAuthTokens, IAuthenticationBroker authentication, IPasswordManager passwordManager, IStorageManager storageManager, LinkedInPermissions requiredPermissions = LinkedInPermissions.NotSet)
|
||||
{
|
||||
_oAuthTokens = oAuthTokens ?? throw new ArgumentNullException(nameof(oAuthTokens));
|
||||
_authenticationBroker = authentication ?? throw new ArgumentNullException(nameof(authentication));
|
||||
_storageManager = storageManager ?? throw new ArgumentNullException(nameof(storageManager));
|
||||
_passwordManager = passwordManager ?? throw new ArgumentNullException(nameof(passwordManager));
|
||||
|
||||
_requiredPermissions = requiredPermissions;
|
||||
|
||||
Provider.RequiredPermissions = requiredPermissions;
|
||||
Provider.Tokens = oAuthTokens;
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request list data from service provider based upon a given config / query.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Strong type of model.</typeparam>
|
||||
/// <param name="config">LinkedInDataConfig instance.</param>
|
||||
/// <param name="maxRecords">Upper limit of records to return.</param>
|
||||
/// <param name="startRecord">Index of paged results.</param>
|
||||
/// <param name="fields">A comma separated string of required fields, which will have strongly typed representation in the model passed in.</param>
|
||||
/// <returns>Strongly typed list of data returned from the service.</returns>
|
||||
public async Task<List<T>> RequestAsync<T>(LinkedInDataConfig config, int maxRecords = 20, int startRecord = 0, string fields = "id")
|
||||
{
|
||||
List<T> queryResults = new List<T>();
|
||||
|
||||
var results = await Provider.GetDataAsync<T>(config, maxRecords, startRecord, fields);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
queryResults.Add(result);
|
||||
}
|
||||
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve logged in users profile details.
|
||||
/// </summary>
|
||||
/// <param name="requireEmailAddress">Require email address - which needs user consensus.</param>
|
||||
/// <returns>Strongly typed profile.</returns>
|
||||
public async Task<LinkedInProfile> GetUserProfileAsync(bool requireEmailAddress = false)
|
||||
{
|
||||
var fields = LinkedInProfile.Fields;
|
||||
|
||||
if (requireEmailAddress)
|
||||
{
|
||||
if (!_requiredPermissions.HasFlag(LinkedInPermissions.ReadEmailAddress))
|
||||
{
|
||||
throw new InvalidOperationException("Please re-initialize with email permission and call LoginAsync again so user may grant access.");
|
||||
}
|
||||
|
||||
fields += ",email-address";
|
||||
}
|
||||
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
var results = await LinkedInService.Instance.RequestAsync<LinkedInProfile>(new LinkedInDataConfig { Query = "/people" }, 1, 0, fields);
|
||||
|
||||
return results[0];
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await GetUserProfileAsync();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for sharing data to LinkedIn.
|
||||
/// </summary>
|
||||
public partial class LinkedInShareRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets comment property.
|
||||
/// </summary>
|
||||
public string Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets visibility property.
|
||||
/// </summary>
|
||||
public LinkedInVisibility Visibility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets content property.
|
||||
/// </summary>
|
||||
public LinkedInContent Content { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Share Response.
|
||||
/// </summary>
|
||||
public class LinkedInShareResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets UpdateKey property.
|
||||
/// </summary>
|
||||
public string UpdateKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets UpdateUrl property.
|
||||
/// </summary>
|
||||
public string UpdateUrl { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// List of user related data permissions
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LinkedInShareVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// Connections only
|
||||
/// </summary>
|
||||
ConnectionsOnly = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Anyone
|
||||
/// </summary>
|
||||
Anyone = 2
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.LinkedIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type representation of Visibility.
|
||||
/// </summary>
|
||||
public class LinkedInVisibility
|
||||
{
|
||||
private const string ANYONE = "anyone";
|
||||
private const string CONNECTIONSONLY = "connections-only";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets code property.
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts enum counterpart to appropriate data string.
|
||||
/// </summary>
|
||||
/// <param name="visibility">Enumeration.</param>
|
||||
/// <returns>String representation</returns>
|
||||
public static string ParseVisibilityEnumToString(LinkedInShareVisibility visibility)
|
||||
{
|
||||
switch (visibility)
|
||||
{
|
||||
case LinkedInShareVisibility.Anyone:
|
||||
return ANYONE;
|
||||
case LinkedInShareVisibility.ConnectionsOnly:
|
||||
return CONNECTIONSONLY;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts string to enum counterpart.
|
||||
/// </summary>
|
||||
/// <param name="visibility">String.</param>
|
||||
/// <returns>Enumeration.</returns>
|
||||
public static LinkedInShareVisibility ParseVisibilityStringToEnum(string visibility)
|
||||
{
|
||||
switch (visibility.ToLower())
|
||||
{
|
||||
case ANYONE:
|
||||
return LinkedInShareVisibility.Anyone;
|
||||
case CONNECTIONSONLY:
|
||||
return LinkedInShareVisibility.ConnectionsOnly;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid visibility string supplied.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Client to call Cognitive Services Azure Auth Token service in order to get an access token.
|
||||
/// </summary>
|
||||
internal class AzureAuthToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of header used to pass the subscription key to the token service
|
||||
/// </summary>
|
||||
private const string OcpApimSubscriptionKeyHeader = "Ocp-Apim-Subscription-Key";
|
||||
|
||||
/// <summary>
|
||||
/// URL of the token service
|
||||
/// </summary>
|
||||
private static readonly Uri ServiceUrl = new Uri("https://api.cognitive.microsoft.com/sts/v1.0/issueToken");
|
||||
|
||||
// TODO
|
||||
// private static readonly Uri ServiceUrl = new Uri(THIS SHOULD BE A PARAMETER NOW);
|
||||
|
||||
/// <summary>
|
||||
/// After obtaining a valid token, this class will cache it for this duration.
|
||||
/// Use a duration of 8 minutes, which is less than the actual token lifetime of 10 minutes.
|
||||
/// </summary>
|
||||
private static readonly TimeSpan TokenCacheDuration = new TimeSpan(0, 8, 0);
|
||||
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
private string _storedTokenValue = string.Empty;
|
||||
private DateTime _storedTokenTime = DateTime.MinValue;
|
||||
private string _subscriptionKey;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Service Subscription Key.
|
||||
/// </summary>
|
||||
public string SubscriptionKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return _subscriptionKey;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_subscriptionKey != value)
|
||||
{
|
||||
// If the subscription key is changed, the token is no longer valid.
|
||||
_subscriptionKey = value;
|
||||
_storedTokenValue = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AzureAuthToken"/> class, that is used to obtain access token
|
||||
/// </summary>
|
||||
/// <param name="key">Subscription key to use to get an authentication token.</param>
|
||||
public AzureAuthToken(string key)
|
||||
{
|
||||
SubscriptionKey = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a token for the specified subscription.
|
||||
/// </summary>
|
||||
/// <returns>The encoded JWT token prefixed with the string "Bearer ".</returns>
|
||||
/// <remarks>
|
||||
/// This method uses a cache to limit the number of request to the token service.
|
||||
/// A fresh token can be re-used during its lifetime of 10 minutes. After a successful
|
||||
/// request to the token service, this method caches the access token. Subsequent
|
||||
/// invocations of the method return the cached token for the next 8 minutes. After
|
||||
/// 8 minutes, a new token is fetched from the token service and the cache is updated.
|
||||
/// </remarks>
|
||||
public async Task<string> GetAccessTokenAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_subscriptionKey))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(SubscriptionKey), "A subscription key is required. Go to Azure Portal and sign up for Microsoft Translator: https://portal.azure.com/#create/Microsoft.CognitiveServices/apitype/TextTranslation");
|
||||
}
|
||||
|
||||
// Re-use the cached token if there is one.
|
||||
if ((DateTime.Now - _storedTokenTime) < TokenCacheDuration && !string.IsNullOrWhiteSpace(_storedTokenValue))
|
||||
{
|
||||
return _storedTokenValue;
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, ServiceUrl);
|
||||
request.Headers.Add(OcpApimSubscriptionKeyHeader, SubscriptionKey);
|
||||
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = JsonSerializer.Deserialize<ErrorResponse>(content);
|
||||
throw new TranslatorServiceException(error?.Error?.Message);
|
||||
}
|
||||
|
||||
_storedTokenTime = DateTime.Now;
|
||||
_storedTokenValue = $"Bearer {content}";
|
||||
|
||||
return _storedTokenValue;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Detected Language
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguageWithResponseAsync(string)"/>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguagesWithResponseAsync(IEnumerable{string})"/>
|
||||
public class DetectedLanguage : DetectedLanguageBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the detected language is one of the languages supported for text translation.
|
||||
/// </summary>
|
||||
public bool IsTranslationSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the detected language is one of the languages supported for transliteration.
|
||||
/// </summary>
|
||||
public bool IsTransliterationSupported { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectedLanguage"/> class.
|
||||
/// </summary>
|
||||
/// <param name="language">The code of the detected language.</param>
|
||||
/// <param name="score">A float value indicating the confidence in the result. The score is between zero and one and a low score indicates a low confidence.</param>
|
||||
/// <param name="isTranslationSupported">A value indicating whether the detected language is one of the languages supported for text translation.</param>
|
||||
/// <param name="isTransliterationSupported">A value indicating whether the detected language is one of the languages supported for transliteration.</param>
|
||||
/// <seealso cref="DetectedLanguageBase"/>
|
||||
public DetectedLanguage(string language, float score, bool isTranslationSupported, bool isTransliterationSupported)
|
||||
: base(language, score)
|
||||
{
|
||||
IsTranslationSupported = isTranslationSupported;
|
||||
IsTransliterationSupported = isTransliterationSupported;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Base Detected Language
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguageWithResponseAsync(string)"/>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguagesWithResponseAsync(IEnumerable{string})"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(IEnumerable{string}, IEnumerable{string})"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(IEnumerable{string}, string, IEnumerable{string})"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(IEnumerable{string}, string, string)"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(string, IEnumerable{string})"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(string, string)"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(string, string, IEnumerable{string})"/>
|
||||
/// <seealso cref="ITranslatorService.TranslateWithResponseAsync(string, string, string)"/>
|
||||
public class DetectedLanguageBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the code of the detected language.
|
||||
/// </summary>
|
||||
public string Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a float value indicating the confidence in the result. The score is between zero and one and a low score indicates a low confidence.
|
||||
/// </summary>
|
||||
public float Score { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString() => Language;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectedLanguageBase"/> class.
|
||||
/// Returns the language friendly name.
|
||||
/// </summary>
|
||||
/// <param name="language">the code of the detected language.</param>
|
||||
/// <param name="score">a float value indicating the confidence in the result. The score is between zero and one and a low score indicates a low confidence.</param>
|
||||
public DetectedLanguageBase(string language, float score)
|
||||
{
|
||||
Language = language;
|
||||
Score = score;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Detect Language Response
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguageWithResponseAsync(string)"/>
|
||||
/// <seealso cref="ITranslatorService.DetectLanguagesWithResponseAsync(IEnumerable{string})"/>
|
||||
public class DetectedLanguageResponse : DetectedLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an array of other possible languages.
|
||||
/// </summary>
|
||||
public IEnumerable<DetectedLanguage> Alternatives { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DetectedLanguageResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="language">The code of the detected language.</param>
|
||||
/// <param name="score">A float value indicating the confidence in the result. The score is between zero and one and a low score indicates a low confidence.</param>
|
||||
/// <param name="isTranslationSupported">A value indicating whether the detected language is one of the languages supported for text translation.</param>
|
||||
/// <param name="isTransliterationSupported">A value indicating whether the detected language is one of the languages supported for transliteration.</param>
|
||||
/// <param name="alternatives">An array of other possible languages</param>
|
||||
/// <seealso cref="DetectedLanguage"/>
|
||||
public DetectedLanguageResponse(string language, float score, bool isTranslationSupported, bool isTransliterationSupported, IEnumerable<DetectedLanguage> alternatives)
|
||||
: base(language, score, isTranslationSupported, isTransliterationSupported)
|
||||
{
|
||||
Alternatives = alternatives;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
/// <summary>
|
||||
/// Holds information about an error occurred while accessing Microsoft Translator Service.
|
||||
/// </summary>
|
||||
internal class ErrorResponse
|
||||
{
|
||||
[JsonPropertyName("error")]
|
||||
public Error Error { get; set; }
|
||||
}
|
||||
|
||||
internal class Error
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the error message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
}
|
|
@ -1,372 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// The <strong>ITranslatorServiceClient</strong> interface specifies properties and methods to translate text in various supported languages.
|
||||
/// </summary>
|
||||
public interface ITranslatorService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the Subscription key that is necessary to use <strong>Microsoft Translator Service</strong>.
|
||||
/// </summary>
|
||||
/// <value>The Subscription Key.</value>
|
||||
/// <remarks>
|
||||
/// <para>You must register Microsoft Translator on https://portal.azure.com/#create/Microsoft.CognitiveServices/apitype/TextTranslation to obtain the Subscription key needed to use the service.</para>
|
||||
/// </remarks>
|
||||
string SubscriptionKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the string representing the supported language code to translate the text in.
|
||||
/// </summary>
|
||||
/// <value>The string representing the supported language code to translate the text in. The code must be present in the list of codes returned from the method <see cref="GetLanguagesAsync"/>.</value>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="TranslatorService"/> class by getting an access token for the service.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> that represents the initialize operation.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <see cref="SubscriptionKey"/> property hasn't been set.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks>Calling this method isn't mandatory, because the token is get/refreshed every time is needed. However, it is called at startup, it can speed-up subsequent requests.</remarks>
|
||||
Task InitializeAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="TranslatorService"/> class by getting an access token for the service.
|
||||
/// </summary>
|
||||
/// <param name="subscriptionKey">The subscription key for the Microsoft Translator Service on Azure.</param>
|
||||
/// <param name="language">A string representing the supported language code to speak the text in. The code must be present in the list of codes returned from the method <see cref="GetLanguagesAsync"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> that represents the initialize operation.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <see cref="SubscriptionKey"/> property hasn't been set.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks>
|
||||
/// <para>Calling this method isn't mandatory, because the token is get/refreshed every time is needed. However, it is called at startup, it can speed-up subsequent requests.</para>
|
||||
/// <para>You must register Microsoft Translator on https://portal.azure.com to obtain the Subscription key needed to use the service.</para>
|
||||
/// </remarks>
|
||||
Task InitializeAsync(string subscriptionKey, string language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Detects the language of a text.
|
||||
/// </summary>
|
||||
/// <param name="input">A string representing the text whose language must be detected.</param>
|
||||
/// <returns>A string containing a two-character Language code for the given text.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method performs a non-blocking request for language detection.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-detect.
|
||||
/// </para></remarks>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
/// <seealso cref="Language"/>
|
||||
Task<string> DetectLanguageAsync(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Detects the language of a text.
|
||||
/// </summary>
|
||||
/// <param name="input">A string representing the text whose language must be detected.</param>
|
||||
/// <returns>A <see cref="DetectedLanguageResponse"/> object containing information about the detected language.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method performs a non-blocking request for language detection.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-detect.
|
||||
/// </para></remarks>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
/// <seealso cref="Language"/>
|
||||
Task<DetectedLanguageResponse> DetectLanguageWithResponseAsync(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Detects the language of a text.
|
||||
/// </summary>
|
||||
/// <param name="input">A string array containing the sentences whose language must be detected.</param>
|
||||
/// <returns>A <see cref="DetectedLanguageResponse"/> array with one result for each string in the input array. Each object contains information about the detected language.</returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter doesn't contain any element.</term>
|
||||
/// <term>The <paramref name="input"/> array contains more than 100 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> array is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic).</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method performs a non-blocking request for language detection.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-detect.
|
||||
/// </para></remarks>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
/// <seealso cref="Language"/>
|
||||
Task<IEnumerable<DetectedLanguageResponse>> DetectLanguagesWithResponseAsync(IEnumerable<string> input);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the languages available for translation.
|
||||
/// </summary>
|
||||
/// <returns>A string array containing the language codes supported for translation by <strong>Microsoft Translator Service</strong>.</returns> /// <exception cref="ArgumentNullException">The <see cref="SubscriptionKey"/> property hasn't been set.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method performs a non-blocking request for language codes.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-languages.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetLanguageNamesAsync(string)"/>
|
||||
Task<IEnumerable<string>> GetLanguagesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves friendly names for the languages available for text translation.
|
||||
/// </summary>
|
||||
/// <param name="language">The language used to localize the language names. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <returns>An array of <see cref="ServiceLanguage"/> containing the language codes and names supported for translation by <strong>Microsoft Translator Service</strong>.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <see cref="SubscriptionKey"/> property hasn't been set.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method performs a non-blocking request for language names.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-languages.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<IEnumerable<ServiceLanguage>> GetLanguageNamesAsync(string language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text string into the specified language.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the translated text.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="to">A string representing the language code to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="input"/> parameter is longer than 1000 characters.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<string> TranslateAsync(string input, string to = null);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text string into the specified language.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the translated text.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="from">A string representing the language code of the original text. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <param name="to">A string representing the language code to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="input"/> parameter is longer than 1000 characters.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<string> TranslateAsync(string input, string from, string to);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text string into the specified language.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> object containing translated text and information.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="to">A string representing the language code to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="input"/> parameter is longer than 1000 characters.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<TranslationResponse> TranslateWithResponseAsync(string input, string to = null);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text string into the specified languages.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> object containing translated text and information.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="from">A string representing the language code of the original text. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <param name="to">A string representing the language code to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">The <paramref name="input"/> parameter is longer than 1000 characters.</exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<TranslationResponse> TranslateWithResponseAsync(string input, string from, string to);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a list of sentences into the specified language.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> array with one result for each language code in the <paramref name="to"/> array. Each object contains translated text and information.</returns>
|
||||
/// <param name="input">A string array containing the sentences to translate.</param>
|
||||
/// <param name="from">A string representing the language code of the original text. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <param name="to">A string representing the language code to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic).</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter is longer than 1000 characters.</term>
|
||||
/// <term>The <paramref name="input"/> array contains more than 25 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, string from, string to);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text into the specified languages.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> object containing translated text and information.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="from">A string representing the language code of the original text. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <param name="to">A string array representing the language codes to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter is longer than 1000 characters.</term>
|
||||
/// <term>The <paramref name="to"/> array contains more than 25 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<TranslationResponse> TranslateWithResponseAsync(string input, string from, IEnumerable<string> to);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a text string into the specified languages.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> object containing translated text and information.</returns>
|
||||
/// <param name="input">A string representing the text to translate.</param>
|
||||
/// <param name="to">A string array representing the language codes to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic) or empty.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter is longer than 1000 characters.</term>
|
||||
/// <term>The <paramref name="to"/> array contains more than 25 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<TranslationResponse> TranslateWithResponseAsync(string input, IEnumerable<string> to);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a list of sentences into the specified languages.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> array with one result for each language code in the <paramref name="to"/> array. Each object contains translated text and information.</returns>
|
||||
/// <param name="input">A string array containing the sentences to translate.</param>
|
||||
/// <param name="to">A string array representing the language codes to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic).</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter is longer than 1000 characters.</term>
|
||||
/// <term>The <paramref name="input"/> array contains more than 25 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, IEnumerable<string> to = null);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a list of sentences into the specified languages.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="TranslationResponse"/> array with one result for each language code in the <paramref name="to"/> array. Each object contains translated text and information.</returns>
|
||||
/// <param name="input">A string array containing the sentences to translate.</param>
|
||||
/// <param name="from">A string representing the language code of the original text. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <param name="to">A string array representing the language codes to translate the text into. The code must be present in the list of codes returned from the <see cref="GetLanguagesAsync"/> method. If the parameter is set to <strong>null</strong>, the language specified in the <seealso cref="Language"/> property will be used.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <see cref="SubscriptionKey"/> property hasn't been set.</term>
|
||||
/// <term>The <paramref name="input"/> parameter is <strong>null</strong> (<strong>Nothing</strong> in Visual Basic).</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <list type="bullet">
|
||||
/// <term>The <paramref name="input"/> parameter is longer than 1000 characters.</term>
|
||||
/// <term>The <paramref name="input"/> array contains more than 25 elements.</term>
|
||||
/// </list>
|
||||
/// </exception>
|
||||
/// <exception cref="TranslatorServiceException">The provided <see cref="SubscriptionKey"/> isn't valid or has expired.</exception>
|
||||
/// <remarks><para>This method perform a non-blocking request for text translation.</para>
|
||||
/// <para>For more information, go to https://docs.microsoft.com/azure/cognitive-services/translator/reference/v3-0-translate.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="Language"/>
|
||||
/// <seealso cref="GetLanguagesAsync"/>
|
||||
Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, string from, IEnumerable<string> to);
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds information about languages supported for text translation and speech synthesis.
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.GetLanguageNamesAsync(string)"/>
|
||||
public class ServiceLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the language code.
|
||||
/// </summary>
|
||||
public string Code { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the language friendly name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name of the language in the locale native for this language.
|
||||
/// </summary>
|
||||
public string NativeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directionality, which is rtl for right-to-left languages or ltr for left-to-right languages.
|
||||
/// </summary>
|
||||
[JsonPropertyName("dir")]
|
||||
public string Directionality { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the language friendly name.
|
||||
/// </summary>
|
||||
/// <returns>The language friendly name.</returns>
|
||||
public override string ToString() => Name;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceLanguage"/> class.
|
||||
/// Returns the language friendly name.
|
||||
/// </summary>
|
||||
/// <param name="code">The language code.</param>
|
||||
/// <param name="name">The language friendly name.</param>
|
||||
/// <param name="nativeName">The display name of the language in the locale native for this language.</param>
|
||||
/// <param name="directionality">The directionality, which is rtl for right-to-left languages or ltr for left-to-right languages</param>
|
||||
public ServiceLanguage(string code, string name, string nativeName = null, string directionality = null)
|
||||
{
|
||||
Code = code;
|
||||
Name = name;
|
||||
NativeName = nativeName ?? name;
|
||||
Directionality = directionality ?? "ltr";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Translation
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.TranslateAsync(string, string, string)"/>
|
||||
public class Translation
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a string giving the translated text.
|
||||
/// </summary>
|
||||
public string Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representing the language code of the target language.
|
||||
/// </summary>
|
||||
public string To { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Translation"/> class.
|
||||
/// </summary>
|
||||
/// <param name="text">A string giving the translated text.</param>
|
||||
/// <param name="to">a string representing the language code of the target language.</param>
|
||||
public Translation(string text, string to)
|
||||
{
|
||||
Text = text;
|
||||
To = to;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// Strong type for Translate Response
|
||||
/// </summary>
|
||||
/// <seealso cref="ITranslatorService.TranslateAsync(string, string, string)"/>
|
||||
public class TranslationResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a <see cref="DetectedLanguageBase"/> object describing the detected language.
|
||||
/// </summary>
|
||||
/// <remarks>This property has a value only when the <see cref="ITranslatorService.TranslateWithResponseAsync(string, string)"/> method is invoked without the <strong>from</strong> parameter, so that automatic language detection is applied to determine the source language.
|
||||
/// </remarks>
|
||||
public DetectedLanguageBase DetectedLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of <see cref="MicrosoftTranslator.Translation"/> results.
|
||||
/// </summary>
|
||||
public IEnumerable<Translation> Translations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first translation result.
|
||||
/// </summary>
|
||||
public Translation Translation => Translations?.FirstOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranslationResponse"/> class.
|
||||
/// </summary>
|
||||
/// <param name="detectedLanguage">A <see cref="DetectedLanguageBase"/> object describing the detected language.</param>
|
||||
/// <param name="translations">an array of <see cref="MicrosoftTranslator.Translation"/> results.</param>
|
||||
/// <seealso cref="MicrosoftTranslator.Translation"/>
|
||||
/// <seealso cref="DetectedLanguageBase"/>
|
||||
public TranslationResponse(DetectedLanguageBase detectedLanguage, IEnumerable<Translation> translations)
|
||||
{
|
||||
DetectedLanguage = detectedLanguage;
|
||||
Translations = translations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// The <strong>TranslatorService</strong> class provides methods to translate text in various supported languages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>To use this library, you must register Microsoft Translator on https://portal.azure.com/#create/Microsoft.CognitiveServices/apitype/TextTranslation to obtain the Subscription key.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class TranslatorService : ITranslatorService
|
||||
{
|
||||
private const string BaseUrl = "https://api.cognitive.microsofttranslator.com/";
|
||||
private const string ApiVersion = "api-version=3.0";
|
||||
private const string AuthorizationUri = "Authorization";
|
||||
private const string JsonMediaType = "application/json";
|
||||
|
||||
private const int _MaxArrayLengthForTranslation = 25;
|
||||
private const int _MaxTextLengthForTranslation = 5000;
|
||||
private const int _MaxArrayLengthForDetection = 100;
|
||||
private const int _MaxTextLengthForDetection = 10000;
|
||||
|
||||
private static HttpClient client = new HttpClient();
|
||||
|
||||
/// <summary>
|
||||
/// Private singleton field.
|
||||
/// </summary>
|
||||
private static TranslatorService instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets public singleton property.
|
||||
/// </summary>
|
||||
public static TranslatorService Instance => instance ?? (instance = new TranslatorService());
|
||||
|
||||
private AzureAuthToken _authToken;
|
||||
private string _authorizationHeaderValue = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to an instance of the underlying data provider.
|
||||
/// </summary>
|
||||
public object Provider
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
private TranslatorService()
|
||||
{
|
||||
_authToken = new AzureAuthToken(string.Empty);
|
||||
Language = CultureInfo.CurrentCulture.Name.ToLower();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string SubscriptionKey
|
||||
{
|
||||
get { return _authToken.SubscriptionKey; }
|
||||
set { _authToken.SubscriptionKey = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> DetectLanguageAsync(string input)
|
||||
{
|
||||
var response = await DetectLanguageWithResponseAsync(input).ConfigureAwait(false);
|
||||
return response?.Language;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<DetectedLanguageResponse> DetectLanguageWithResponseAsync(string input)
|
||||
{
|
||||
var response = await DetectLanguagesWithResponseAsync(new string[] { input }).ConfigureAwait(false);
|
||||
return response.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<DetectedLanguageResponse>> DetectLanguagesWithResponseAsync(IEnumerable<string> input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
if (!input.Any())
|
||||
{
|
||||
throw new ArgumentException($"{nameof(input)} array must contain at least 1 element");
|
||||
}
|
||||
|
||||
if (input.Count() > _MaxArrayLengthForDetection)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(input)} array can have at most {_MaxArrayLengthForDetection} elements");
|
||||
}
|
||||
|
||||
// Checks if it is necessary to obtain/update access token.
|
||||
await CheckUpdateTokenAsync().ConfigureAwait(false);
|
||||
|
||||
var uriString = $"{BaseUrl}detect?{ApiVersion}";
|
||||
using var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t.Substring(0, Math.Min(t.Length, _MaxTextLengthForDetection)) }));
|
||||
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
var responseContent = JsonSerializer.Deserialize<IEnumerable<DetectedLanguageResponse>>(content);
|
||||
return responseContent;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<string>> GetLanguagesAsync()
|
||||
{
|
||||
var languages = await GetLanguageNamesAsync();
|
||||
return languages.OrderBy(l => l.Code).Select(l => l.Code).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<ServiceLanguage>> GetLanguageNamesAsync(string language = null)
|
||||
{
|
||||
// Check if it is necessary to obtain/update access token.
|
||||
await CheckUpdateTokenAsync().ConfigureAwait(false);
|
||||
|
||||
var uriString = $"{BaseUrl}languages?scope=translation&{ApiVersion}";
|
||||
using var request = CreateHttpRequest(uriString);
|
||||
|
||||
language = language ?? Language;
|
||||
if (!string.IsNullOrWhiteSpace(language))
|
||||
{
|
||||
// If necessary, adds the Accept-Language header in order to get localized language names.
|
||||
request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(language));
|
||||
}
|
||||
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
var jsonContent = JsonDocument.Parse(content).RootElement.GetProperty("translation");
|
||||
var responseContent = JsonSerializer.Deserialize<Dictionary<string, ServiceLanguage>>(jsonContent.ToString()).ToList();
|
||||
responseContent.ForEach(r => r.Value.Code = r.Key);
|
||||
|
||||
return responseContent.Select(r => r.Value).OrderBy(r => r.Name).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<string> TranslateAsync(string input, string to = null) => TranslateAsync(input, null, to ?? Language);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> TranslateAsync(string input, string from, string to)
|
||||
{
|
||||
var response = await TranslateWithResponseAsync(new string[] { input }, from, new string[] { to }).ConfigureAwait(false);
|
||||
return response.FirstOrDefault()?.Translation.Text;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<TranslationResponse> TranslateWithResponseAsync(string input, string to = null) => TranslateWithResponseAsync(input, null, to ?? Language);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TranslationResponse> TranslateWithResponseAsync(string input, string from, string to)
|
||||
{
|
||||
var response = await TranslateWithResponseAsync(new string[] { input }, from, new string[] { to }).ConfigureAwait(false);
|
||||
return response.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, string from, string to) => TranslateWithResponseAsync(input, from, new string[] { to });
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<TranslationResponse> TranslateWithResponseAsync(string input, IEnumerable<string> to) => TranslateWithResponseAsync(input, null, to);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TranslationResponse> TranslateWithResponseAsync(string input, string from, IEnumerable<string> to)
|
||||
{
|
||||
var response = await TranslateWithResponseAsync(new string[] { input }, from, to).ConfigureAwait(false);
|
||||
return response.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, IEnumerable<string> to = null) => TranslateWithResponseAsync(input, null, to);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<IEnumerable<TranslationResponse>> TranslateWithResponseAsync(IEnumerable<string> input, string from, IEnumerable<string> to)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
if (input.Count() > _MaxArrayLengthForTranslation)
|
||||
{
|
||||
throw new ArgumentException($"{nameof(input)} array can have at most {_MaxArrayLengthForTranslation} elements");
|
||||
}
|
||||
|
||||
if (input.Any(str => string.IsNullOrWhiteSpace(str) || str.Length > _MaxTextLengthForTranslation))
|
||||
{
|
||||
throw new ArgumentException($"Each sentence cannot be null and longer than {_MaxTextLengthForTranslation} characters");
|
||||
}
|
||||
|
||||
if (to == null || !to.Any())
|
||||
{
|
||||
to = new string[] { Language };
|
||||
}
|
||||
|
||||
// Checks if it is necessary to obtain/update access token.
|
||||
await CheckUpdateTokenAsync().ConfigureAwait(false);
|
||||
|
||||
var toQueryString = string.Join("&", to.Select(t => $"to={t}"));
|
||||
var uriString = (string.IsNullOrWhiteSpace(from) ? $"{BaseUrl}translate?{toQueryString}" : $"{BaseUrl}translate?from={from}&{toQueryString}") + $"&{ApiVersion}";
|
||||
using var request = CreateHttpRequest(uriString, HttpMethod.Post, input.Select(t => new { Text = t }));
|
||||
|
||||
var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
var responseContent = JsonSerializer.Deserialize<IEnumerable<TranslationResponse>>(content);
|
||||
return responseContent;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task InitializeAsync() => CheckUpdateTokenAsync();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task InitializeAsync(string subscriptionKey, string language = null)
|
||||
{
|
||||
_authToken = new AzureAuthToken(subscriptionKey);
|
||||
Language = language ?? CultureInfo.CurrentCulture.Name.ToLower();
|
||||
|
||||
return InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task CheckUpdateTokenAsync()
|
||||
{
|
||||
// If necessary, updates the access token.
|
||||
_authorizationHeaderValue = await _authToken.GetAccessTokenAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private HttpRequestMessage CreateHttpRequest(string uriString)
|
||||
=> CreateHttpRequest(uriString, HttpMethod.Get);
|
||||
|
||||
private HttpRequestMessage CreateHttpRequest(string uriString, HttpMethod method, object content = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(method, new Uri(uriString));
|
||||
request.Headers.Add(AuthorizationUri, _authorizationHeaderValue);
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
var jsonRequest = JsonSerializer.Serialize(content);
|
||||
var requestContent = new StringContent(jsonRequest, System.Text.Encoding.UTF8, JsonMediaType);
|
||||
request.Content = requestContent;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.MicrosoftTranslator
|
||||
{
|
||||
/// <summary>
|
||||
/// The <strong>TranslatorServiceException</strong> class holds information about Exception related to <see cref="TranslatorService"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="TranslatorService"/>
|
||||
public class TranslatorServiceException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TranslatorServiceException"/> class using the specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">Message that describes the error</param>
|
||||
public TranslatorServiceException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Any kind of twitter object.
|
||||
/// </summary>
|
||||
public interface ITwitterResult
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Timeline item.
|
||||
/// </summary>
|
||||
public class Tweet : Toolkit.Parsers.SchemaBase, ITwitterResult
|
||||
{
|
||||
private string _text;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets time item was created.
|
||||
/// </summary>
|
||||
[JsonPropertyName("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets item Id.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id_str")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets text of the tweet (handles both 140 and 280 characters)
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text
|
||||
{
|
||||
get { return _text ?? FullText; }
|
||||
set { _text = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets text of the tweet (280 characters).
|
||||
/// </summary>
|
||||
[JsonPropertyName("full_text")]
|
||||
private string FullText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets display text range (indexes of tweet text without RT and leading user mentions)
|
||||
/// </summary>
|
||||
[JsonPropertyName("display_text_range")]
|
||||
public int[] DisplayTextRange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether tweet is truncated
|
||||
/// (true when tweet is longer than 140 characters)
|
||||
/// This entity may be deprecated - it never seems to be set to true.
|
||||
/// </summary>
|
||||
[JsonPropertyName("truncated")]
|
||||
public bool IsTruncated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets attached content of the tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("entities")]
|
||||
public TwitterEntities Entities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets extended attached content of the tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("extended_entities")]
|
||||
public TwitterExtendedEntities ExtendedEntities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tweet source (client or website used)
|
||||
/// </summary>
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets in_reply_to_screen_name
|
||||
/// </summary>
|
||||
[JsonPropertyName("in_reply_to_screen_name")]
|
||||
public string InReplyToScreenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets in_reply_to_status_id_str
|
||||
/// </summary>
|
||||
[JsonPropertyName("in_reply_to_status_id_str")]
|
||||
public string InReplyToStatusId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets in_reply_to_user_id_str
|
||||
/// </summary>
|
||||
[JsonPropertyName("in_reply_to_user_id_str")]
|
||||
public string InReplyToUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets user who posted the status.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user")]
|
||||
public TwitterUser User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets geo coordinates (latitude and longitude) returned by Twitter for some locations
|
||||
/// </summary>
|
||||
[JsonPropertyName("coordinates")]
|
||||
[JsonConverter(typeof(TwitterCoordinatesConverter))]
|
||||
public TwitterCoordinates Coordinates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Place object returned by Twitter for some locations
|
||||
/// </summary>
|
||||
[JsonPropertyName("place")]
|
||||
public TwitterPlace Place { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Retweeted Tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("retweeted_status")]
|
||||
public Tweet RetweetedStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation date
|
||||
/// </summary>
|
||||
public DateTime CreationDate
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime dt;
|
||||
if (!DateTime.TryParseExact(CreatedAt, "ddd MMM dd HH:mm:ss zzzz yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
|
||||
{
|
||||
dt = DateTime.Today;
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets quoted_status
|
||||
/// </summary>
|
||||
[JsonPropertyName("quoted_status")]
|
||||
public Tweet QuotedStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets quoted_status_id_str
|
||||
/// </summary>
|
||||
[JsonPropertyName("quoted_status_id_str")]
|
||||
public string QuotedStatusId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets quoted_status_permalink
|
||||
/// </summary>
|
||||
[JsonPropertyName("quoted_status_permalink")]
|
||||
public TwitterUrl QuotedStatusPermalink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets approximate count of tweets quoting tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("quote_count")]
|
||||
public int QuoteCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets number of replies to tweet
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Premium and Enterprise API access only
|
||||
/// </remarks>
|
||||
[JsonPropertyName("reply_count")]
|
||||
public int ReplyCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets number of times tweet has been retweeted
|
||||
/// </summary>
|
||||
[JsonPropertyName("retweet_count")]
|
||||
public int RetweetCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets number of times tweet has been liked
|
||||
/// </summary>
|
||||
[JsonPropertyName("favorite_count")]
|
||||
public int FavoriteCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not logged-in user has liked tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("favorited")]
|
||||
public bool Favorited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not logged-in user has retweeted tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("retweeted")]
|
||||
public bool Retweeted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether URL in tweet has been flagged for sensitive content
|
||||
/// </summary>
|
||||
[JsonPropertyName("possibly_sensitive")]
|
||||
public bool Sensitive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets stream filter of tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("filter_level")]
|
||||
public string FilterLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets BCP 47 language identifier of tweet content
|
||||
/// </summary>
|
||||
[JsonPropertyName("lang")]
|
||||
public string Language { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Timeline Parser.
|
||||
/// </summary>
|
||||
public class TweetParser : Parsers.IParser<Tweet>
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse string data into strongly typed list.
|
||||
/// </summary>
|
||||
/// <param name="data">Input string.</param>
|
||||
/// <returns>List of strongly typed objects.</returns>
|
||||
public IEnumerable<Tweet> Parse(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<List<Tweet>>(data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Longitude and Latitude for a tweet
|
||||
/// </summary>
|
||||
public class TwitterCoordinates
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the numeric latitude (null if the value could not be converted)
|
||||
/// </summary>
|
||||
public double Latitude { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numeric longitude (null if the value could not be converted)
|
||||
/// </summary>
|
||||
public double Longitude { get; internal set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({Latitude}, {Longitude})";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
internal class TwitterCoordinatesConverter : JsonConverter<TwitterCoordinates>
|
||||
{
|
||||
private readonly JsonEncodedText latitudeName = JsonEncodedText.Encode("Latitude");
|
||||
private readonly JsonEncodedText longitudeName = JsonEncodedText.Encode("Longitude");
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly JsonConverter<double> doubleConverter;
|
||||
|
||||
public TwitterCoordinatesConverter(JsonSerializerOptions options)
|
||||
{
|
||||
doubleConverter = options?.GetConverter(typeof(double)) as JsonConverter<double> ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override TwitterCoordinates Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
double latitude = default;
|
||||
bool latitudeSet = false;
|
||||
|
||||
double longitude = default;
|
||||
bool longitudeSet = false;
|
||||
|
||||
// Get the first property.
|
||||
reader.Read();
|
||||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
if (reader.ValueTextEquals(latitudeName.EncodedUtf8Bytes))
|
||||
{
|
||||
latitude = ReadProperty(ref reader, options);
|
||||
latitudeSet = true;
|
||||
}
|
||||
else if (reader.ValueTextEquals(longitudeName.EncodedUtf8Bytes))
|
||||
{
|
||||
longitude = ReadProperty(ref reader, options);
|
||||
longitudeSet = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
// Get the second property.
|
||||
reader.Read();
|
||||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
if (latitudeSet && reader.ValueTextEquals(longitudeName.EncodedUtf8Bytes))
|
||||
{
|
||||
longitude = ReadProperty(ref reader, options);
|
||||
}
|
||||
else if (longitudeSet && reader.ValueTextEquals(latitudeName.EncodedUtf8Bytes))
|
||||
{
|
||||
latitude = ReadProperty(ref reader, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
reader.Read();
|
||||
|
||||
if (reader.TokenType != JsonTokenType.EndObject)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
return new TwitterCoordinates
|
||||
{
|
||||
Latitude = latitude,
|
||||
Longitude = longitude
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private double ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
reader.Read();
|
||||
return doubleConverter.Read(ref reader, typeof(double), options);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TwitterCoordinates value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Query string configuration.
|
||||
/// </summary>
|
||||
public class TwitterDataConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets twitter query type.
|
||||
/// </summary>
|
||||
public TwitterQueryType QueryType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets query parameters.
|
||||
/// </summary>
|
||||
public string Query { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,705 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
#if WINRT
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.Uwp;
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.NetFramework;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Data Provider for connecting to Twitter service.
|
||||
/// </summary>
|
||||
public class TwitterDataProvider : Toolkit.Services.DataProviderBase<TwitterDataConfig, Toolkit.Parsers.SchemaBase>
|
||||
{
|
||||
/// <summary>
|
||||
/// Base Url for service.
|
||||
/// </summary>
|
||||
private const string BaseUrl = "https://api.twitter.com/1.1";
|
||||
private const string OAuthBaseUrl = "https://api.twitter.com/oauth";
|
||||
private const string PublishUrl = "https://upload.twitter.com/1.1";
|
||||
private const string UserStreamUrl = "https://userstream.twitter.com/1.1";
|
||||
|
||||
private static HttpClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Base Url for service.
|
||||
/// </summary>
|
||||
private readonly TwitterOAuthTokens _tokens;
|
||||
private readonly IAuthenticationBroker _authenticationBroker;
|
||||
private readonly IPasswordManager _passwordManager;
|
||||
private readonly IStorageManager _storageManager;
|
||||
private readonly ISignatureManager _signatureManager;
|
||||
private TwitterOAuthRequest _streamRequest;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets logged in user information.
|
||||
/// </summary>
|
||||
public string UserScreenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the provider is already logged in
|
||||
/// </summary>
|
||||
public bool LoggedIn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
/// <param name="authenticationBroker">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Platform password manager</param>
|
||||
/// <param name="storageManager">Platform storage provider</param>
|
||||
/// <param name="signatureManager">Platform signature manager</param>
|
||||
public TwitterDataProvider(TwitterOAuthTokens tokens, IAuthenticationBroker authenticationBroker, IPasswordManager passwordManager, IStorageManager storageManager, ISignatureManager signatureManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tokens.ConsumerSecret))
|
||||
{
|
||||
throw new ArgumentException("Missing consumer secret");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.ConsumerKey))
|
||||
{
|
||||
throw new ArgumentException("Missing consumer key");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tokens.CallbackUri))
|
||||
{
|
||||
throw new ArgumentException("Missing callback uri");
|
||||
}
|
||||
|
||||
_tokens = tokens;
|
||||
_authenticationBroker = authenticationBroker ?? throw new ArgumentException("Missing AuthenticationBroker");
|
||||
_passwordManager = passwordManager ?? throw new ArgumentException("Missing PasswordManager");
|
||||
_storageManager = storageManager ?? throw new ArgumentException("Missing StorageManager");
|
||||
_signatureManager = signatureManager ?? throw new ArgumentException("Missing SignatureManager");
|
||||
|
||||
if (_client == null)
|
||||
{
|
||||
HttpClientHandler handler = new HttpClientHandler();
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
_client = new HttpClient(handler);
|
||||
}
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
public TwitterDataProvider(TwitterOAuthTokens tokens)
|
||||
: this(tokens, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager(), new UwpSignatureManager())
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterDataProvider"/> class.
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="tokens">OAuth tokens for request.</param>
|
||||
public TwitterDataProvider(TwitterOAuthTokens tokens)
|
||||
: this(tokens, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager(), new NetFrameworkSignatureManager())
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve user data.
|
||||
/// </summary>
|
||||
/// <param name="screenName">User screen name or null for current logged user</param>
|
||||
/// <returns>Returns user data.</returns>
|
||||
public async Task<TwitterUser> GetUserAsync(string screenName = null)
|
||||
{
|
||||
string rawResult = null;
|
||||
try
|
||||
{
|
||||
var userScreenName = screenName ?? UserScreenName;
|
||||
var uri = new Uri($"{BaseUrl}/users/show.json?screen_name={userScreenName}");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
|
||||
return JsonSerializer.Deserialize<TwitterUser>(rawResult);
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
throw new UserNotFoundException(screenName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!string.IsNullOrEmpty(rawResult))
|
||||
{
|
||||
var errors = JsonSerializer.Deserialize<TwitterErrors>(rawResult);
|
||||
|
||||
throw new TwitterException { Errors = errors };
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve user timeline data with specific parser.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Strong type for results.</typeparam>
|
||||
/// <param name="screenName">User screen name.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <param name="parser">Specific results parser.</param>
|
||||
/// <returns>Returns strongly typed list of results.</returns>
|
||||
public async Task<IEnumerable<TSchema>> GetUserTimeLineAsync<TSchema>(string screenName, int maxRecords, Toolkit.Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
string rawResult = null;
|
||||
try
|
||||
{
|
||||
var uri = new Uri($"{BaseUrl}/statuses/user_timeline.json?screen_name={screenName}&count={maxRecords}&include_rts=1&tweet_mode=extended");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
|
||||
|
||||
var result = parser.Parse(rawResult);
|
||||
return result
|
||||
.Take(maxRecords)
|
||||
.ToList();
|
||||
}
|
||||
catch (UserNotFoundException)
|
||||
{
|
||||
throw new UserNotFoundException(screenName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!string.IsNullOrEmpty(rawResult))
|
||||
{
|
||||
var errors = JsonSerializer.Deserialize<TwitterErrors>(rawResult);
|
||||
|
||||
throw new TwitterException { Errors = errors };
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for specific hash tag with specific parser.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Strong type for results.</typeparam>
|
||||
/// <param name="hashTag">Hash tag.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <param name="parser">Specific results parser.</param>
|
||||
/// <returns>Returns strongly typed list of results.</returns>
|
||||
public async Task<IEnumerable<TSchema>> SearchAsync<TSchema>(string hashTag, int maxRecords, Toolkit.Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
var uri = new Uri($"{BaseUrl}/search/tweets.json?q={Uri.EscapeDataString(hashTag)}&count={maxRecords}&tweet_mode=extended");
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
var rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
|
||||
|
||||
var result = parser.Parse(rawResult);
|
||||
return result
|
||||
.Take(maxRecords)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user in to Twitter.
|
||||
/// </summary>
|
||||
/// <returns>Boolean indicating login success.</returns>
|
||||
public async Task<bool> LoginAsync()
|
||||
{
|
||||
var credentials = _passwordManager.Get("TwitterAccessToken");
|
||||
var user = await _storageManager.GetAsync("TwitterScreenName");
|
||||
if (!string.IsNullOrEmpty(user) && credentials != null)
|
||||
{
|
||||
_tokens.AccessToken = credentials.UserName;
|
||||
_tokens.AccessTokenSecret = credentials.Password;
|
||||
UserScreenName = user;
|
||||
LoggedIn = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (await InitializeRequestAccessTokensAsync(_tokens.CallbackUri) == false)
|
||||
{
|
||||
LoggedIn = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
string requestToken = _tokens.RequestToken;
|
||||
string twitterUrl = $"{OAuthBaseUrl}/authorize?oauth_token={requestToken}";
|
||||
|
||||
Uri startUri = new Uri(twitterUrl);
|
||||
Uri endUri = new Uri(_tokens.CallbackUri);
|
||||
|
||||
var result = await _authenticationBroker.Authenticate(startUri, endUri);
|
||||
|
||||
switch (result.ResponseStatus)
|
||||
{
|
||||
case AuthenticationResultStatus.Success:
|
||||
LoggedIn = true;
|
||||
return await ExchangeRequestTokenForAccessTokenAsync(result.ResponseData);
|
||||
|
||||
case AuthenticationResultStatus.ErrorHttp:
|
||||
Debug.WriteLine("WAB failed, message={0}", result.ResponseErrorDetail.ToString());
|
||||
LoggedIn = false;
|
||||
return false;
|
||||
|
||||
case AuthenticationResultStatus.UserCancel:
|
||||
Debug.WriteLine("WAB user aborted.");
|
||||
LoggedIn = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
LoggedIn = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user out of Twitter.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task LogoutAsync()
|
||||
{
|
||||
var credential = _passwordManager.Get("TwitterAccessToken");
|
||||
|
||||
if (credential != null)
|
||||
{
|
||||
_passwordManager.Remove("TwitterAccessToken");
|
||||
await _storageManager.SetAsync("TwitterScreenName", null);
|
||||
}
|
||||
|
||||
UserScreenName = null;
|
||||
LoggedIn = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tweets a status update.
|
||||
/// </summary>
|
||||
/// <param name="tweet">Tweet text.</param>
|
||||
/// <param name="pictures">Pictures to attach to the tweet (up to 4).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public async Task<bool> TweetStatusAsync(string tweet, params Stream[] pictures)
|
||||
{
|
||||
return await TweetStatusAsync(new TwitterStatus { Message = tweet }, pictures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tweets a status update.
|
||||
/// </summary>
|
||||
/// <param name="status">Tweet text.</param>
|
||||
/// <param name="pictures">Pictures to attach to the tweet (up to 4).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public async Task<bool> TweetStatusAsync(TwitterStatus status, params Stream[] pictures)
|
||||
{
|
||||
var mediaIds = string.Empty;
|
||||
|
||||
if (pictures != null && pictures.Length > 0)
|
||||
{
|
||||
var ids = new List<string>();
|
||||
foreach (var picture in pictures)
|
||||
{
|
||||
ids.Add(await UploadPictureAsync(picture));
|
||||
}
|
||||
|
||||
mediaIds = "&media_ids=" + string.Join(",", ids);
|
||||
}
|
||||
|
||||
var uri = new Uri($"{BaseUrl}/statuses/update.json?{status.RequestParameters}{mediaIds}");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
await request.ExecutePostAsync(uri, _tokens, _signatureManager);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish a picture to Twitter user's medias.
|
||||
/// </summary>
|
||||
/// <param name="stream">Picture stream.</param>
|
||||
/// <returns>Media ID</returns>
|
||||
public async Task<string> UploadPictureAsync(Stream stream)
|
||||
{
|
||||
var uri = new Uri($"{PublishUrl}/media/upload.json");
|
||||
|
||||
byte[] fileBytes;
|
||||
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
await stream.CopyToAsync(ms);
|
||||
fileBytes = ms.ToArray();
|
||||
}
|
||||
|
||||
string boundary = DateTime.Now.Ticks.ToString("x");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
return await request.ExecutePostMultipartAsync(uri, _tokens, boundary, fileBytes, _signatureManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a connection to user streams service (Events, DirectMessages...).
|
||||
/// </summary>
|
||||
/// <param name="parser">Specific stream's result parser.</param>
|
||||
/// <param name="callback">Method invoked each time a result occurs.</param>
|
||||
/// <returns>Awaitable task.</returns>
|
||||
public Task StartUserStreamAsync(TwitterUserStreamParser parser, TwitterStreamCallbacks.TwitterStreamCallback callback)
|
||||
{
|
||||
var uri = new Uri($"{UserStreamUrl}/user.json?replies=all");
|
||||
|
||||
_streamRequest = new TwitterOAuthRequest();
|
||||
|
||||
return _streamRequest.ExecuteGetStreamAsync(uri, _tokens, rawResult => callback(parser.Parse(rawResult)), _signatureManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop user's stream
|
||||
/// </summary>
|
||||
public void StopStream()
|
||||
{
|
||||
_streamRequest?.Abort();
|
||||
_streamRequest = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns parser implementation for specified configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
/// <returns>Strongly typed parser.</returns>
|
||||
protected override Toolkit.Parsers.IParser<Toolkit.Parsers.SchemaBase> GetDefaultParser(TwitterDataConfig config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ConfigNullException();
|
||||
}
|
||||
|
||||
switch (config.QueryType)
|
||||
{
|
||||
case TwitterQueryType.Search:
|
||||
return new TwitterSearchParser();
|
||||
|
||||
case TwitterQueryType.Home:
|
||||
case TwitterQueryType.User:
|
||||
case TwitterQueryType.Custom:
|
||||
return new TwitterParser<Toolkit.Parsers.SchemaBase>();
|
||||
|
||||
default:
|
||||
return new TwitterParser<Toolkit.Parsers.SchemaBase>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around REST API for making data request.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Schema to use</typeparam>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
/// <param name="maxRecords">Upper limit for records returned.</param>
|
||||
/// <param name="pageIndex">The zero-based index of the page that corresponds to the items to retrieve.</param>
|
||||
/// <param name="parser">IParser implementation for interpreting results.</param>
|
||||
/// <returns>Strongly typed list of results.</returns>
|
||||
protected override async Task<IEnumerable<TSchema>> GetDataAsync<TSchema>(TwitterDataConfig config, int maxRecords, int pageIndex, Toolkit.Parsers.IParser<TSchema> parser)
|
||||
{
|
||||
IEnumerable<TSchema> items;
|
||||
switch (config.QueryType)
|
||||
{
|
||||
case TwitterQueryType.User:
|
||||
items = await GetUserTimeLineAsync(config.Query, maxRecords, parser);
|
||||
break;
|
||||
|
||||
case TwitterQueryType.Search:
|
||||
items = await SearchAsync(config.Query, maxRecords, parser);
|
||||
break;
|
||||
|
||||
case TwitterQueryType.Custom:
|
||||
items = await GetCustomSearch(config.Query, parser);
|
||||
break;
|
||||
|
||||
case TwitterQueryType.Home:
|
||||
default:
|
||||
items = await GetHomeTimeLineAsync(maxRecords, parser);
|
||||
break;
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check validity of configuration.
|
||||
/// </summary>
|
||||
/// <param name="config">Query configuration.</param>
|
||||
protected override void ValidateConfig(TwitterDataConfig config)
|
||||
{
|
||||
if (config?.Query == null && config?.QueryType != TwitterQueryType.Home)
|
||||
{
|
||||
throw new ConfigParameterNullException(nameof(config.Query));
|
||||
}
|
||||
|
||||
if (_tokens == null)
|
||||
{
|
||||
throw new ConfigParameterNullException(nameof(_tokens));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_tokens.ConsumerKey))
|
||||
{
|
||||
throw new OAuthKeysNotPresentException(nameof(_tokens.ConsumerKey));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(_tokens.ConsumerSecret))
|
||||
{
|
||||
throw new OAuthKeysNotPresentException(nameof(_tokens.ConsumerSecret));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract requested token from the REST API response string.
|
||||
/// </summary>
|
||||
/// <param name="getResponse">REST API response string.</param>
|
||||
/// <param name="tokenType">Token type to retrieve.</param>
|
||||
/// <returns>Required token.</returns>
|
||||
private static string ExtractTokenFromResponse(string getResponse, TwitterOAuthTokenType tokenType)
|
||||
{
|
||||
string requestOrAccessToken = null;
|
||||
string requestOrAccessTokenSecret = null;
|
||||
string oauthVerifier = null;
|
||||
string oauthCallbackConfirmed = null;
|
||||
string screenName = null;
|
||||
string[] keyValPairs = getResponse.Split('&');
|
||||
|
||||
for (int i = 0; i < keyValPairs.Length; i++)
|
||||
{
|
||||
string[] splits = keyValPairs[i].Split('=');
|
||||
switch (splits[0])
|
||||
{
|
||||
case "screen_name":
|
||||
screenName = splits[1];
|
||||
break;
|
||||
|
||||
case "oauth_token":
|
||||
requestOrAccessToken = splits[1];
|
||||
break;
|
||||
|
||||
case "oauth_token_secret":
|
||||
requestOrAccessTokenSecret = splits[1];
|
||||
break;
|
||||
|
||||
case "oauth_callback_confirmed":
|
||||
oauthCallbackConfirmed = splits[1];
|
||||
break;
|
||||
|
||||
case "oauth_verifier":
|
||||
oauthVerifier = splits[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (tokenType)
|
||||
{
|
||||
case TwitterOAuthTokenType.OAuthRequestOrAccessToken:
|
||||
return requestOrAccessToken;
|
||||
|
||||
case TwitterOAuthTokenType.OAuthRequestOrAccessTokenSecret:
|
||||
return requestOrAccessTokenSecret;
|
||||
|
||||
case TwitterOAuthTokenType.OAuthVerifier:
|
||||
return oauthVerifier;
|
||||
|
||||
case TwitterOAuthTokenType.ScreenName:
|
||||
return screenName;
|
||||
|
||||
case TwitterOAuthTokenType.OAuthCallbackConfirmed:
|
||||
return oauthCallbackConfirmed;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get home time line data.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSchema">Strong typed result.</typeparam>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <param name="parser">Specific result parser.</param>
|
||||
/// <returns>Return strong typed list of results.</returns>
|
||||
private async Task<IEnumerable<TSchema>> GetHomeTimeLineAsync<TSchema>(int maxRecords, Toolkit.Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
var uri = new Uri($"{BaseUrl}/statuses/home_timeline.json?count={maxRecords}&tweet_mode=extended");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
var rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
|
||||
|
||||
return parser.Parse(rawResult);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<TSchema>> GetCustomSearch<TSchema>(string query, Toolkit.Parsers.IParser<TSchema> parser)
|
||||
where TSchema : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
var uri = new Uri($"{BaseUrl}/{query}");
|
||||
|
||||
TwitterOAuthRequest request = new TwitterOAuthRequest();
|
||||
|
||||
var rawResult = await request.ExecuteGetAsync(uri, _tokens, _signatureManager);
|
||||
|
||||
return parser.Parse(rawResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Package up token request.
|
||||
/// </summary>
|
||||
/// <param name="twitterCallbackUrl">Callback Uri.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
private async Task<bool> InitializeRequestAccessTokensAsync(string twitterCallbackUrl)
|
||||
{
|
||||
var twitterUrl = $"{OAuthBaseUrl}/request_token";
|
||||
|
||||
string nonce = GetNonce();
|
||||
string timeStamp = GetTimeStamp();
|
||||
string sigBaseStringParams = GetSignatureBaseStringParams(_tokens.ConsumerKey, nonce, timeStamp, "oauth_callback=" + Uri.EscapeDataString(twitterCallbackUrl));
|
||||
string sigBaseString = "GET&" + Uri.EscapeDataString(twitterUrl) + "&" + Uri.EscapeDataString(sigBaseStringParams);
|
||||
string signature = _signatureManager.GetSignature(sigBaseString, _tokens.ConsumerSecret, true);
|
||||
|
||||
twitterUrl += "?" + sigBaseStringParams + "&oauth_signature=" + Uri.EscapeDataString(signature);
|
||||
|
||||
string getResponse;
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, new Uri(twitterUrl)))
|
||||
{
|
||||
using var response = await _client.SendAsync(request).ConfigureAwait(false);
|
||||
var data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
getResponse = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("HttpHelper call failed trying to retrieve Twitter Request Tokens. Message: {0}", data);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var callbackConfirmed = ExtractTokenFromResponse(getResponse, TwitterOAuthTokenType.OAuthCallbackConfirmed);
|
||||
if (Convert.ToBoolean(callbackConfirmed) != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_tokens.RequestToken = ExtractTokenFromResponse(getResponse, TwitterOAuthTokenType.OAuthRequestOrAccessToken);
|
||||
_tokens.RequestTokenSecret = ExtractTokenFromResponse(getResponse, TwitterOAuthTokenType.OAuthRequestOrAccessTokenSecret);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build signature base string.
|
||||
/// </summary>
|
||||
/// <param name="consumerKey">Consumer Key.</param>
|
||||
/// <param name="nonce">Nonce.</param>
|
||||
/// <param name="timeStamp">Timestamp.</param>
|
||||
/// <param name="additionalParameters">Any additional parameter name/values that need appending to base string.</param>
|
||||
/// <returns>Signature base string.</returns>
|
||||
private string GetSignatureBaseStringParams(string consumerKey, string nonce, string timeStamp, string additionalParameters = "")
|
||||
{
|
||||
string sigBaseStringParams = additionalParameters;
|
||||
sigBaseStringParams += "&" + "oauth_consumer_key=" + consumerKey;
|
||||
sigBaseStringParams += "&" + "oauth_nonce=" + nonce;
|
||||
sigBaseStringParams += "&" + "oauth_signature_method=HMAC-SHA1";
|
||||
sigBaseStringParams += "&" + "oauth_timestamp=" + timeStamp;
|
||||
sigBaseStringParams += "&" + "oauth_version=1.0";
|
||||
|
||||
return sigBaseStringParams;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract and initialize access tokens.
|
||||
/// </summary>
|
||||
/// <param name="webAuthResultResponseData">WAB data containing appropriate tokens.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
private async Task<bool> ExchangeRequestTokenForAccessTokenAsync(string webAuthResultResponseData)
|
||||
{
|
||||
string responseData = webAuthResultResponseData.Substring(webAuthResultResponseData.IndexOf("oauth_token"));
|
||||
string requestToken = ExtractTokenFromResponse(responseData, TwitterOAuthTokenType.OAuthRequestOrAccessToken);
|
||||
|
||||
// Ensure requestToken matches accessToken per Twitter documentation.
|
||||
if (requestToken != _tokens.RequestToken)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string oAuthVerifier = ExtractTokenFromResponse(responseData, TwitterOAuthTokenType.OAuthVerifier);
|
||||
|
||||
string twitterUrl = $"{OAuthBaseUrl}/access_token";
|
||||
|
||||
string timeStamp = GetTimeStamp();
|
||||
string nonce = GetNonce();
|
||||
|
||||
string sigBaseStringParams = GetSignatureBaseStringParams(_tokens.ConsumerKey, nonce, timeStamp, "oauth_token=" + requestToken);
|
||||
|
||||
string sigBaseString = "POST&";
|
||||
sigBaseString += Uri.EscapeDataString(twitterUrl) + "&" + Uri.EscapeDataString(sigBaseStringParams);
|
||||
|
||||
string signature = _signatureManager.GetSignature(sigBaseString, _tokens.ConsumerSecret);
|
||||
string data = null;
|
||||
|
||||
string authorizationHeaderParams = "oauth_consumer_key=\"" + _tokens.ConsumerKey + "\", oauth_nonce=\"" + nonce + "\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"" + Uri.EscapeDataString(signature) + "\", oauth_timestamp=\"" + timeStamp + "\", oauth_token=\"" + Uri.EscapeDataString(requestToken) + "\", oauth_verifier=\"" + Uri.EscapeUriString(oAuthVerifier) + "\" , oauth_version=\"1.0\"";
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Post, new Uri(twitterUrl)))
|
||||
{
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("OAuth", authorizationHeaderParams);
|
||||
|
||||
using var response = await _client.SendAsync(request).ConfigureAwait(false);
|
||||
data = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var screenName = ExtractTokenFromResponse(data, TwitterOAuthTokenType.ScreenName);
|
||||
var accessToken = ExtractTokenFromResponse(data, TwitterOAuthTokenType.OAuthRequestOrAccessToken);
|
||||
var accessTokenSecret = ExtractTokenFromResponse(data, TwitterOAuthTokenType.OAuthRequestOrAccessTokenSecret);
|
||||
|
||||
UserScreenName = screenName;
|
||||
_tokens.AccessToken = accessToken;
|
||||
_tokens.AccessTokenSecret = accessTokenSecret;
|
||||
|
||||
_passwordManager.Store("TwitterAccessToken", new PasswordCredential { UserName = accessToken, Password = accessTokenSecret });
|
||||
await _storageManager.SetAsync("TwitterScreenName", screenName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate nonce.
|
||||
/// </summary>
|
||||
/// <returns>Nonce.</returns>
|
||||
private string GetNonce()
|
||||
{
|
||||
Random rand = new Random();
|
||||
int nonce = rand.Next(1000000000);
|
||||
return nonce.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate timestamp.
|
||||
/// </summary>
|
||||
/// <returns>Timestamp.</returns>
|
||||
private string GetTimeStamp()
|
||||
{
|
||||
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
|
||||
return Math.Round(sinceEpoch.TotalSeconds).ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter User type.
|
||||
/// </summary>
|
||||
public class TwitterDirectMessage : ITwitterResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the direct message id.
|
||||
/// </summary>
|
||||
/// <value>The direct message id.</value>
|
||||
[JsonPropertyName("id")]
|
||||
public decimal Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender id.
|
||||
/// </summary>
|
||||
/// <value>The sender id.</value>
|
||||
[JsonPropertyName("sender_id")]
|
||||
public decimal SenderId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the direct message text.
|
||||
/// </summary>
|
||||
/// <value>The direct message text.</value>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recipient id.
|
||||
/// </summary>
|
||||
/// <value>The recipient id.</value>
|
||||
[JsonPropertyName("recipient_id")]
|
||||
public decimal RecipientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the created date.
|
||||
/// </summary>
|
||||
/// <value>The created date.</value>
|
||||
[JsonPropertyName("created_at")]
|
||||
public string CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the sender screen.
|
||||
/// </summary>
|
||||
/// <value>The name of the sender screen.</value>
|
||||
[JsonPropertyName("sender_screen_name")]
|
||||
public string SenderScreenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the recipient screen.
|
||||
/// </summary>
|
||||
/// <value>The name of the recipient screen.</value>
|
||||
[JsonPropertyName("recipient_screen_name")]
|
||||
public string RecipientScreenName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sender.
|
||||
/// </summary>
|
||||
/// <value>The sender.</value>
|
||||
[JsonPropertyName("sender")]
|
||||
public TwitterUser Sender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recipient.
|
||||
/// </summary>
|
||||
/// <value>The recipient.</value>
|
||||
[JsonPropertyName("recipient")]
|
||||
public TwitterUser Recipient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the entities.
|
||||
/// </summary>
|
||||
/// <value>The entities.</value>
|
||||
[JsonPropertyName("entities")]
|
||||
public TwitterEntities Entities { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation date
|
||||
/// </summary>
|
||||
public DateTime CreationDate
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime dt;
|
||||
if (!DateTime.TryParseExact(CreatedAt, "ddd MMM dd HH:mm:ss zzzz yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
|
||||
{
|
||||
dt = DateTime.Today;
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Entities containing Twitter entities object tweet
|
||||
/// </summary>
|
||||
public class TwitterEntities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets Hashtags array of the tweet.
|
||||
/// This array will be empty if no Hashtags are present.
|
||||
/// </summary>
|
||||
[JsonPropertyName("Hashtags")]
|
||||
public TwitterHashtag[] Hashtags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Symbols array of the tweet.
|
||||
/// This array will be empty if no Symbols are present.
|
||||
/// </summary>
|
||||
[JsonPropertyName("Symbols")]
|
||||
public TwitterSymbol[] Symbols { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Media array of the tweet.
|
||||
/// This array will not exist if no media is present.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media")]
|
||||
public TwitterMedia[] Media { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Urls array of the tweet.
|
||||
/// This array will be empty if no Urls are present.
|
||||
/// </summary>
|
||||
[JsonPropertyName("urls")]
|
||||
public TwitterUrl[] Urls { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets array of usernames mentioned in the tweet.
|
||||
/// This array will be empty if no usernames are mentioned.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user_mentions")]
|
||||
public TwitterUserMention[] UserMentions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the poll in a tweet.
|
||||
/// This array will not exist if no poll is present.
|
||||
/// This array will always have one poll.
|
||||
/// </summary>
|
||||
[JsonPropertyName("polls")]
|
||||
public TwitterPoll Poll { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter error type
|
||||
/// </summary>
|
||||
public class TwitterError
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets error code
|
||||
/// </summary>
|
||||
[JsonPropertyName("code")]
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets error message
|
||||
/// </summary>
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter errors type.
|
||||
/// </summary>
|
||||
public class TwitterErrors
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the list of errors
|
||||
/// </summary>
|
||||
[JsonPropertyName("errors")]
|
||||
public TwitterError[] Errors { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter specific exception.
|
||||
/// </summary>
|
||||
public class TwitterException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the errors returned by Twitter
|
||||
/// </summary>
|
||||
public TwitterErrors Errors { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter User type.
|
||||
/// </summary>
|
||||
public class TwitterExtended
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the text of the tweet (280 characters).
|
||||
/// </summary>
|
||||
[JsonPropertyName("full_text")]
|
||||
public string FullText { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Extended Entities containing an array of TwitterMedia.
|
||||
/// </summary>
|
||||
public class TwitterExtendedEntities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets Media of the tweet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media")]
|
||||
public TwitterMedia[] Media { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// A class to contain the latitude and longitude of a tweet.
|
||||
/// </summary>
|
||||
public class TwitterGeoData
|
||||
{
|
||||
private const int LatitudeIndex = 0;
|
||||
private const int LongitudeIndex = 1;
|
||||
private const string PointType = "Point";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of data
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latitude and longitude in a coordinate format.
|
||||
/// </summary>
|
||||
public string DisplayCoordinates
|
||||
{
|
||||
get
|
||||
{
|
||||
string result = null;
|
||||
|
||||
if (Coordinates != null)
|
||||
{
|
||||
result = $"({Coordinates[LatitudeIndex]}, {Coordinates[LongitudeIndex]})";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinates of the geographic data
|
||||
/// </summary>
|
||||
[JsonPropertyName("coordinates")]
|
||||
public string[] Coordinates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numeric latitude (null if the value could not be converted)
|
||||
/// </summary>
|
||||
public double? Latitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return ParseCoordinate(LatitudeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numeric longitude (null if the value could not be converted)
|
||||
/// </summary>
|
||||
public double? Longitude
|
||||
{
|
||||
get
|
||||
{
|
||||
return ParseCoordinate(LongitudeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private double? ParseCoordinate(int index)
|
||||
{
|
||||
double? result = null;
|
||||
double parsed;
|
||||
|
||||
if (DataType == PointType
|
||||
&& Coordinates != null
|
||||
&& !string.IsNullOrEmpty(Coordinates[index])
|
||||
&& double.TryParse(Coordinates[index], NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out parsed))
|
||||
{
|
||||
result = parsed;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Hashtag object containing all hashtags in a tweet.
|
||||
/// </summary>
|
||||
public class TwitterHashtag
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets indices of hashtag location in tweet string.
|
||||
/// </summary>
|
||||
[JsonPropertyName("indices")]
|
||||
public int[] Indices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hashtag text, excluding #.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Media
|
||||
/// </summary>
|
||||
public class TwitterMedia
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets ID as string.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id_str")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets indices array.
|
||||
/// </summary>
|
||||
[JsonPropertyName("indices")]
|
||||
public int[] Indices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets MediaUrl (direct link to image).
|
||||
/// </summary>
|
||||
[JsonPropertyName("media_url")]
|
||||
public string MediaUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets HTTPS MediaUrl.
|
||||
/// </summary>
|
||||
[JsonPropertyName("media_url_https")]
|
||||
public string MediaUrlHttps { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets t.co shortened tweet Url.
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets DisplayUrl (pics.twitter.com Url).
|
||||
/// </summary>
|
||||
[JsonPropertyName("display_url")]
|
||||
public string DisplayUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets DisplayUrl (pics.twitter.com Url).
|
||||
/// </summary>
|
||||
[JsonPropertyName("expanded_url")]
|
||||
public string ExpandedUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets MediaType - photo, animated_gif, or video
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets size array
|
||||
/// </summary>
|
||||
[JsonPropertyName("sizes")]
|
||||
public TwitterMediaSizes Sizes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SourceId - tweet ID of media's original tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("source_status_id_str")]
|
||||
public string SourceIdStr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets metadata for video attached to tweet
|
||||
/// </summary>
|
||||
[JsonPropertyName("video_info")]
|
||||
public TwitterMediaVideoInfo VideoInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets extended metadata for video attached to tweet.
|
||||
/// </summary>
|
||||
[JsonPropertyName("additional_media_info")]
|
||||
public TwitterMediaAdditionalInfo AdditionalMediaInfo { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Media Info
|
||||
/// </summary>
|
||||
public class TwitterMediaAdditionalInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets title of video
|
||||
/// </summary>
|
||||
[JsonPropertyName("title")]
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets description of video
|
||||
/// </summary>
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether video is embeddable
|
||||
/// </summary>
|
||||
[JsonPropertyName("embeddable")]
|
||||
public bool Embeddable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether "monetizable"
|
||||
/// </summary>
|
||||
[JsonPropertyName("monetizable")]
|
||||
public bool Monetizable { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Entities containing size details for each size of an image.
|
||||
/// </summary>
|
||||
public class TwitterMediaSizeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets width integer.
|
||||
/// </summary>
|
||||
[JsonPropertyName("w")]
|
||||
public int Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets height integer.
|
||||
/// </summary>
|
||||
[JsonPropertyName("h")]
|
||||
public int Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets resize string.
|
||||
/// </summary>
|
||||
[JsonPropertyName("resize")]
|
||||
public string Resize { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Media Sizes containing size data for different image sizes.
|
||||
/// </summary>
|
||||
public class TwitterMediaSizes
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets small metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("small")]
|
||||
public TwitterMediaSizeData Small { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets thumbnail metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("thumb")]
|
||||
public TwitterMediaSizeData Thumb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets large metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("large")]
|
||||
public TwitterMediaSizeData Large { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets medium metadata.
|
||||
/// </summary>
|
||||
[JsonPropertyName("medium")]
|
||||
public TwitterMediaSizeData Medium { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Video information
|
||||
/// </summary>
|
||||
public class TwitterMediaVideoInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets video aspect ratio (width, height)
|
||||
/// </summary>
|
||||
[JsonPropertyName("aspect_ratio")]
|
||||
public int[] AspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets duration of video in milliseconds
|
||||
/// </summary>
|
||||
[JsonPropertyName("duration_millis")]
|
||||
public int Duration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets video variants for different codecs, bitrates, etc.
|
||||
/// </summary>
|
||||
[JsonPropertyName("variants")]
|
||||
public TwitterMediaVideoVariants[] Variants { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Video properties
|
||||
/// </summary>
|
||||
public class TwitterMediaVideoVariants
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets video bitrate in bits-per-second
|
||||
/// </summary>
|
||||
[JsonPropertyName("bitrate")]
|
||||
public int Bitrate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MIME type of the video
|
||||
/// </summary>
|
||||
[JsonPropertyName("content_type")]
|
||||
public string ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the direct URL for the video variant
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth request.
|
||||
/// </summary>
|
||||
internal class TwitterOAuthRequest
|
||||
{
|
||||
private static HttpClient client;
|
||||
|
||||
private bool _abort;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterOAuthRequest"/> class.
|
||||
/// </summary>
|
||||
public TwitterOAuthRequest()
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
HttpClientHandler handler = new HttpClientHandler();
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip;
|
||||
client = new HttpClient(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Get request to specified Uri.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Uri to make OAuth request.</param>
|
||||
/// <param name="tokens">Tokens to pass in request.</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth request</param>
|
||||
/// <returns>String result.</returns>
|
||||
public async Task<string> ExecuteGetAsync(Uri requestUri, TwitterOAuthTokens tokens, ISignatureManager signatureManager)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "GET");
|
||||
|
||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
|
||||
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
response.ThrowIfNotValid();
|
||||
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Get request for stream service.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Uri to make OAuth request.</param>
|
||||
/// <param name="tokens">Tokens to pass in request.</param>
|
||||
/// <param name="callback">Function invoked when stream available.</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth requests</param>
|
||||
/// <returns>awaitable task</returns>
|
||||
public async Task ExecuteGetStreamAsync(Uri requestUri, TwitterOAuthTokens tokens, TwitterStreamCallbacks.RawJsonCallback callback, ISignatureManager signatureManager)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager);
|
||||
|
||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
|
||||
|
||||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
response.ThrowIfNotValid();
|
||||
using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
using var reader = new StreamReader(responseStream);
|
||||
while (!_abort && !reader.EndOfStream)
|
||||
{
|
||||
var result = reader.ReadLine();
|
||||
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
callback?.Invoke(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop reading stream
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
{
|
||||
_abort = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Post request to specified Uri.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Uri to make OAuth request.</param>
|
||||
/// <param name="tokens">Tokens to pass in request.</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth requests</param>
|
||||
/// <returns>String result.</returns>
|
||||
public async Task<string> ExecutePostAsync(Uri requestUri, TwitterOAuthTokens tokens, ISignatureManager signatureManager)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
|
||||
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
|
||||
|
||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
|
||||
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
response.ThrowIfNotValid();
|
||||
return ProcessErrors(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP Post request to specified Uri.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Uri to make OAuth request.</param>
|
||||
/// <param name="tokens">Tokens to pass in request.</param>
|
||||
/// <param name="boundary">Boundary used to separate data.</param>
|
||||
/// <param name="content">Data to post to server.</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth requests</param>
|
||||
/// <returns>String result.</returns>
|
||||
public async Task<string> ExecutePostMultipartAsync(Uri requestUri, TwitterOAuthTokens tokens, string boundary, byte[] content, ISignatureManager signatureManager)
|
||||
{
|
||||
JsonElement mediaId = default;
|
||||
|
||||
try
|
||||
{
|
||||
using var multipartFormDataContent = new MultipartFormDataContent(boundary);
|
||||
using var byteContent = new ByteArrayContent(content);
|
||||
multipartFormDataContent.Add(byteContent, "media");
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
|
||||
var requestBuilder = new TwitterOAuthRequestBuilder(requestUri, tokens, signatureManager, "POST");
|
||||
|
||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(requestBuilder.AuthorizationHeader);
|
||||
|
||||
request.Content = multipartFormDataContent;
|
||||
|
||||
using var response = await client.SendAsync(request).ConfigureAwait(false);
|
||||
response.ThrowIfNotValid();
|
||||
using var jsonResult = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
var jObj = await JsonDocument.ParseAsync(jsonResult).ConfigureAwait(false);
|
||||
mediaId = jObj.RootElement.GetProperty("media_id_string");
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// known issue
|
||||
// http://stackoverflow.com/questions/39109060/httpmultipartformdatacontent-dispose-throws-objectdisposedexception
|
||||
}
|
||||
|
||||
return mediaId.ToString();
|
||||
}
|
||||
|
||||
private string ProcessErrors(string content)
|
||||
{
|
||||
if (content.StartsWith("{\"errors\":"))
|
||||
{
|
||||
var errors = JsonSerializer.Deserialize<TwitterErrors>(content);
|
||||
|
||||
throw new TwitterException { Errors = errors };
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,251 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
using Microsoft.Toolkit.Services.OAuth;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth request builder.
|
||||
/// </summary>
|
||||
internal class TwitterOAuthRequestBuilder
|
||||
{
|
||||
private ISignatureManager _signatureManager;
|
||||
|
||||
/// <summary>
|
||||
/// Realm for request.
|
||||
/// </summary>
|
||||
public const string Realm = "Twitter API";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets HTTP verb for request.
|
||||
/// </summary>
|
||||
public string Verb { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets encoded Request Uri.
|
||||
/// </summary>
|
||||
public Uri EncodedRequestUri { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets request Uri without query.
|
||||
/// </summary>
|
||||
public Uri RequestUriWithoutQuery { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets list of query parameters.
|
||||
/// </summary>
|
||||
public IEnumerable<OAuthParameter> QueryParams { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets version.
|
||||
/// </summary>
|
||||
public OAuthParameter Version { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets nonce.
|
||||
/// </summary>
|
||||
public OAuthParameter Nonce { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets timestamp.
|
||||
/// </summary>
|
||||
public OAuthParameter Timestamp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets signature method.
|
||||
/// </summary>
|
||||
public OAuthParameter SignatureMethod { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets consumer key.
|
||||
/// </summary>
|
||||
public OAuthParameter ConsumerKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets consumer secret.
|
||||
/// </summary>
|
||||
public OAuthParameter ConsumerSecret { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets access token.
|
||||
/// </summary>
|
||||
public OAuthParameter Token { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets access token secret.
|
||||
/// </summary>
|
||||
public OAuthParameter TokenSecret { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets signature getter.
|
||||
/// </summary>
|
||||
public OAuthParameter Signature => new OAuthParameter("oauth_signature", GenerateSignature());
|
||||
|
||||
/// <summary>
|
||||
/// Gets authorization header getter.
|
||||
/// </summary>
|
||||
public string AuthorizationHeader => GenerateAuthorizationHeader();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterOAuthRequestBuilder"/> class.
|
||||
/// Authorization request builder.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Request Uri.</param>
|
||||
/// <param name="tokens">Tokens to form request.</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth request</param>
|
||||
/// <param name="method">Method to use with request.</param>
|
||||
public TwitterOAuthRequestBuilder(Uri requestUri, TwitterOAuthTokens tokens, ISignatureManager signatureManager, string method = "GET")
|
||||
{
|
||||
_signatureManager = signatureManager;
|
||||
Verb = method;
|
||||
|
||||
RequestUriWithoutQuery = new Uri(requestUri.AbsoluteWithoutQuery());
|
||||
|
||||
if (!string.IsNullOrEmpty(requestUri.Query))
|
||||
{
|
||||
QueryParams = requestUri.GetQueryParams()
|
||||
.Select(p => new OAuthParameter(p.Key, Uri.UnescapeDataString(p.Value)))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
QueryParams = new List<OAuthParameter>();
|
||||
}
|
||||
|
||||
EncodedRequestUri = GetEncodedUri(requestUri, QueryParams);
|
||||
|
||||
Version = new OAuthParameter("oauth_version", "1.0");
|
||||
Nonce = new OAuthParameter("oauth_nonce", GenerateNonce());
|
||||
Timestamp = new OAuthParameter("oauth_timestamp", GenerateTimeStamp());
|
||||
SignatureMethod = new OAuthParameter("oauth_signature_method", "HMAC-SHA1");
|
||||
ConsumerKey = new OAuthParameter("oauth_consumer_key", tokens.ConsumerKey);
|
||||
ConsumerSecret = new OAuthParameter("oauth_consumer_secret", tokens.ConsumerSecret);
|
||||
Token = new OAuthParameter("oauth_token", tokens.AccessToken);
|
||||
TokenSecret = new OAuthParameter("oauth_token_secret", tokens.AccessTokenSecret);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get encoded Uri.
|
||||
/// </summary>
|
||||
/// <param name="requestUri">Request uri.</param>
|
||||
/// <param name="parameters">List of parameters.</param>
|
||||
/// <returns>Encoded Uri.</returns>
|
||||
private static Uri GetEncodedUri(Uri requestUri, IEnumerable<OAuthParameter> parameters)
|
||||
{
|
||||
StringBuilder requestParametersBuilder = new StringBuilder(requestUri.AbsoluteWithoutQuery());
|
||||
var oAuthParameters = parameters as OAuthParameter[] ?? parameters.ToArray();
|
||||
if (oAuthParameters.Any())
|
||||
{
|
||||
requestParametersBuilder.Append("?");
|
||||
|
||||
foreach (var queryParam in oAuthParameters)
|
||||
{
|
||||
requestParametersBuilder.AppendFormat("{0}&", queryParam.ToString());
|
||||
}
|
||||
|
||||
requestParametersBuilder.Remove(requestParametersBuilder.Length - 1, 1);
|
||||
}
|
||||
|
||||
return new Uri(requestParametersBuilder.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate nonce.
|
||||
/// </summary>
|
||||
/// <returns>String nonce.</returns>
|
||||
private static string GenerateNonce()
|
||||
{
|
||||
return new Random()
|
||||
.Next(123400, int.MaxValue)
|
||||
.ToString("X", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate timestamp string.
|
||||
/// </summary>
|
||||
/// <returns>Timestamp string.</returns>
|
||||
private static string GenerateTimeStamp()
|
||||
{
|
||||
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
|
||||
return Convert.ToInt64(ts.TotalSeconds, CultureInfo.CurrentCulture).ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate signature.
|
||||
/// </summary>
|
||||
/// <returns>Generated signature string.</returns>
|
||||
private string GenerateSignature()
|
||||
{
|
||||
string signatureBaseString = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{2}&{0}&{1}",
|
||||
OAuthEncoder.UrlEncode(RequestUriWithoutQuery.Normalize()),
|
||||
OAuthEncoder.UrlEncode(GetSignParameters()),
|
||||
Verb);
|
||||
|
||||
string key = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}&{1}",
|
||||
OAuthEncoder.UrlEncode(ConsumerSecret.Value),
|
||||
OAuthEncoder.UrlEncode(TokenSecret.Value));
|
||||
|
||||
return _signatureManager.GetSignature(signatureBaseString, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate authorization header.
|
||||
/// </summary>
|
||||
/// <returns>Generated authorization header string.</returns>
|
||||
private string GenerateAuthorizationHeader()
|
||||
{
|
||||
StringBuilder authHeaderBuilder = new StringBuilder();
|
||||
|
||||
authHeaderBuilder.AppendFormat("OAuth realm=\"{0}\",", Realm);
|
||||
authHeaderBuilder.Append(string.Join(",", GetAuthHeaderParameters().OrderBy(p => p.Key).Select(p => p.ToString(true)).ToArray()));
|
||||
authHeaderBuilder.AppendFormat(",{0}", Signature.ToString(true));
|
||||
|
||||
return authHeaderBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of sign parameters.
|
||||
/// </summary>
|
||||
/// <returns>List of sign parameters.</returns>
|
||||
private IEnumerable<OAuthParameter> GetSignParameters()
|
||||
{
|
||||
foreach (var queryParam in QueryParams)
|
||||
{
|
||||
yield return queryParam;
|
||||
}
|
||||
|
||||
yield return Version;
|
||||
yield return Nonce;
|
||||
yield return Timestamp;
|
||||
yield return SignatureMethod;
|
||||
yield return ConsumerKey;
|
||||
yield return Token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get list of auth header parameters.
|
||||
/// </summary>
|
||||
/// <returns>List of auth header parameters.</returns>
|
||||
private IEnumerable<OAuthParameter> GetAuthHeaderParameters()
|
||||
{
|
||||
yield return Version;
|
||||
yield return Nonce;
|
||||
yield return Timestamp;
|
||||
yield return SignatureMethod;
|
||||
yield return ConsumerKey;
|
||||
yield return Token;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Oauth request extensions to add utilities for internal use.
|
||||
/// </summary>
|
||||
internal static class TwitterOAuthRequestExtensions
|
||||
{
|
||||
public static void ThrowIfNotValid(this HttpResponseMessage response)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
if ((int)response.StatusCode == 429)
|
||||
{
|
||||
throw new TooManyRequestsException();
|
||||
}
|
||||
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new OAuthKeysRevokedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// OAuth token types
|
||||
/// </summary>
|
||||
public enum TwitterOAuthTokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// Request or access token
|
||||
/// </summary>
|
||||
OAuthRequestOrAccessToken,
|
||||
|
||||
/// <summary>
|
||||
/// Request or access token secret
|
||||
/// </summary>
|
||||
OAuthRequestOrAccessTokenSecret,
|
||||
|
||||
/// <summary>
|
||||
/// Verifier
|
||||
/// </summary>
|
||||
OAuthVerifier,
|
||||
|
||||
/// <summary>
|
||||
/// Callback confirmed
|
||||
/// </summary>
|
||||
OAuthCallbackConfirmed,
|
||||
|
||||
/// <summary>
|
||||
/// Screen name
|
||||
/// </summary>
|
||||
ScreenName
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter OAuth tokens.
|
||||
/// </summary>
|
||||
public class TwitterOAuthTokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets consumer Key.
|
||||
/// </summary>
|
||||
public string ConsumerKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets consumer Secret.
|
||||
/// </summary>
|
||||
public string ConsumerSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access token.
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access token Secret.
|
||||
/// </summary>
|
||||
public string AccessTokenSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access Request Token.
|
||||
/// </summary>
|
||||
public string RequestToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets access Request Token Secret.
|
||||
/// </summary>
|
||||
public string RequestTokenSecret { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets callback Uri.
|
||||
/// </summary>
|
||||
public string CallbackUri { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Parser.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to parse in to.</typeparam>
|
||||
public class TwitterParser<T> : Toolkit.Parsers.IParser<T>
|
||||
where T : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse string data into strongly typed list.
|
||||
/// </summary>
|
||||
/// <param name="data">Input string.</param>
|
||||
/// <returns>List of strongly typed objects.</returns>
|
||||
IEnumerable<T> Toolkit.Parsers.IParser<T>.Parse(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<T>>(data);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
List<T> items = new List<T>();
|
||||
items.Add(JsonSerializer.Deserialize<T>(data));
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Place
|
||||
/// </summary>
|
||||
public class TwitterPlace
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the place
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL of additional place metadata
|
||||
/// </summary>
|
||||
[JsonPropertyName("url")]
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the place type.
|
||||
/// </summary>
|
||||
[JsonPropertyName("place_type")]
|
||||
public string PlaceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the place name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the full, human-readable place name.
|
||||
/// </summary>
|
||||
[JsonPropertyName("full_name")]
|
||||
public string FullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shortened country code (e.g. US) for the place.
|
||||
/// </summary>
|
||||
[JsonPropertyName("country_code")]
|
||||
public string CountryCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the country for the place.
|
||||
/// </summary>
|
||||
[JsonPropertyName("country")]
|
||||
public string Country { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bounding box coordinates of a location.
|
||||
/// </summary>
|
||||
[JsonPropertyName("bounding_box")]
|
||||
public TwitterPlaceBoundingBox BoundingBox { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Place Bounding Box
|
||||
/// </summary>
|
||||
public class TwitterPlaceBoundingBox
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the bounding box coordinates of the tweet's geolocation data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("coordinates")]
|
||||
public List<List<float[]>> Coordinates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the coordinate type. Polygon for a bounding box, Point for an exact coordinate.
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the coordinates array of the tweet's geolocation data
|
||||
/// </summary>
|
||||
public List<float[]> CoordinatesArray
|
||||
{
|
||||
get
|
||||
{
|
||||
List<float[]> result = null;
|
||||
|
||||
if (Coordinates != null)
|
||||
{
|
||||
result = Coordinates[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter poll object containing poll data.
|
||||
/// </summary>
|
||||
public class TwitterPoll
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets poll questions.
|
||||
/// </summary>
|
||||
[JsonPropertyName("options")]
|
||||
public TwitterPollOptions[] Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets end timestamp as a string.
|
||||
/// </summary>
|
||||
[JsonPropertyName("end_datetime")]
|
||||
public string EndDateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets duration of the poll in minutes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("duration_minutes")]
|
||||
public string DurationMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets end timestamp as a DateTime object.
|
||||
/// </summary>
|
||||
public DateTime PollEnd
|
||||
{
|
||||
get { return FormatDate(EndDateTime); }
|
||||
}
|
||||
|
||||
private DateTime FormatDate(string input)
|
||||
{
|
||||
DateTime formattedDateTime;
|
||||
if (!DateTime.TryParseExact(input, "ddd MMM dd HH:mm:ss zzzz yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out formattedDateTime))
|
||||
{
|
||||
formattedDateTime = DateTime.Today;
|
||||
}
|
||||
|
||||
return formattedDateTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter poll options object containing poll questions.
|
||||
/// </summary>
|
||||
public class TwitterPollOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets int value of the poll position.
|
||||
/// </summary>
|
||||
[JsonPropertyName("position")]
|
||||
public int Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets text of the poll question.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public string Text { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of Twitter Query.
|
||||
/// </summary>
|
||||
public enum TwitterQueryType
|
||||
{
|
||||
/// <summary>
|
||||
/// Home
|
||||
/// </summary>
|
||||
Home,
|
||||
|
||||
/// <summary>
|
||||
/// User
|
||||
/// </summary>
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// Search
|
||||
/// </summary>
|
||||
Search,
|
||||
|
||||
/// <summary>
|
||||
/// Custom
|
||||
/// </summary>
|
||||
Custom
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Search Parser.
|
||||
/// </summary>
|
||||
public class TwitterSearchParser : Parsers.IParser<Tweet>
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse string into strong typed list.
|
||||
/// </summary>
|
||||
/// <param name="data">Input string.</param>
|
||||
/// <returns>Strong typed list.</returns>
|
||||
public IEnumerable<Tweet> Parse(string data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = JsonSerializer.Deserialize<TwitterSearchResult>(data);
|
||||
|
||||
return result.Statuses.ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Twitter Search Results.
|
||||
/// </summary>
|
||||
internal class TwitterSearchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets array of timeline statuses.
|
||||
/// </summary>
|
||||
public Tweet[] Statuses { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,440 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Toolkit.Services.Core;
|
||||
|
||||
#if WINRT
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.Uwp;
|
||||
using Windows.Storage.Streams;
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
using Microsoft.Toolkit.Services.PlatformSpecific.NetFramework;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for connecting to Twitter.
|
||||
/// </summary>
|
||||
public class TwitterService
|
||||
{
|
||||
/// <summary>
|
||||
/// Private field for TwitterDataProvider.
|
||||
/// </summary>
|
||||
private TwitterDataProvider twitterDataProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Field for tracking oAuthTokens.
|
||||
/// </summary>
|
||||
private TwitterOAuthTokens tokens;
|
||||
|
||||
private IPasswordManager passwordManager;
|
||||
private IStorageManager storageManager;
|
||||
private IAuthenticationBroker authenticationBroker;
|
||||
private ISignatureManager signatureManager;
|
||||
|
||||
/// <summary>
|
||||
/// Field for tracking initialization status.
|
||||
/// </summary>
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TwitterService"/> class.
|
||||
/// </summary>
|
||||
public TwitterService()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Private singleton field.
|
||||
/// </summary>
|
||||
private static TwitterService instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets public singleton property.
|
||||
/// </summary>
|
||||
public static TwitterService Instance => instance ?? (instance = new TwitterService());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current logged in user screen name.
|
||||
/// </summary>
|
||||
public string UserScreenName => Provider.UserScreenName;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="consumerKey">Consumer key.</param>
|
||||
/// <param name="consumerSecret">Consumer secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at apps.twitter.com (can be arbitrary).</param>
|
||||
/// <param name="authenticationBroker">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Password Manager interface, store the password.</param>
|
||||
/// <param name="storageManager">Storage Manager interface</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth request</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string consumerKey, string consumerSecret, string callbackUri, IAuthenticationBroker authenticationBroker, IPasswordManager passwordManager, IStorageManager storageManager, ISignatureManager signatureManager)
|
||||
{
|
||||
if (string.IsNullOrEmpty(consumerKey))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(consumerKey));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(consumerSecret))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(consumerSecret));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(callbackUri))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(callbackUri));
|
||||
}
|
||||
|
||||
if (authenticationBroker == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(authenticationBroker));
|
||||
}
|
||||
|
||||
if (passwordManager == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(passwordManager));
|
||||
}
|
||||
|
||||
if (storageManager == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(storageManager));
|
||||
}
|
||||
|
||||
if (signatureManager == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(signatureManager));
|
||||
}
|
||||
|
||||
var oAuthTokens = new TwitterOAuthTokens
|
||||
{
|
||||
ConsumerKey = consumerKey,
|
||||
ConsumerSecret = consumerSecret,
|
||||
CallbackUri = callbackUri
|
||||
};
|
||||
|
||||
return Initialize(oAuthTokens, authenticationBroker, passwordManager, storageManager, signatureManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <param name="authenticationBroker">Authentication result interface.</param>
|
||||
/// <param name="passwordManager">Password Manager interface, store the password.</param>
|
||||
/// <param name="storageManager">Storage Manager interface</param>
|
||||
/// <param name="signatureManager">Signature manager to sign the OAuth request</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(TwitterOAuthTokens oAuthTokens, IAuthenticationBroker authenticationBroker, IPasswordManager passwordManager, IStorageManager storageManager, ISignatureManager signatureManager)
|
||||
{
|
||||
tokens = oAuthTokens ?? throw new ArgumentNullException(nameof(oAuthTokens));
|
||||
this.authenticationBroker = authenticationBroker ?? throw new ArgumentNullException(nameof(authenticationBroker));
|
||||
this.passwordManager = passwordManager ?? throw new ArgumentNullException(nameof(passwordManager));
|
||||
this.storageManager = storageManager ?? throw new ArgumentNullException(nameof(storageManager));
|
||||
this.signatureManager = signatureManager ?? throw new ArgumentNullException(nameof(signatureManager));
|
||||
|
||||
isInitialized = true;
|
||||
|
||||
twitterDataProvider = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if WINRT
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information for UWP.
|
||||
/// </summary>
|
||||
/// <param name="consumerKey">Consumer key.</param>
|
||||
/// <param name="consumerSecret">Consumer secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at apps.twitter.com (can be arbitrary).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string consumerKey, string consumerSecret, string callbackUri)
|
||||
{
|
||||
return Initialize(consumerKey, consumerSecret, callbackUri, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager(), new UwpSignatureManager());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(TwitterOAuthTokens oAuthTokens)
|
||||
{
|
||||
return Initialize(oAuthTokens, new UwpAuthenticationBroker(), new UwpPasswordManager(), new UwpStorageManager(), new UwpSignatureManager());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NET462
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information for UWP.
|
||||
/// </summary>
|
||||
/// <param name="consumerKey">Consumer key.</param>
|
||||
/// <param name="consumerSecret">Consumer secret.</param>
|
||||
/// <param name="callbackUri">Callback URI. Has to match callback URI defined at apps.twitter.com (can be arbitrary).</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(string consumerKey, string consumerSecret, string callbackUri)
|
||||
{
|
||||
return Initialize(consumerKey, consumerSecret, callbackUri, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager(), new NetFrameworkSignatureManager());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize underlying provider with relevant token information.
|
||||
/// </summary>
|
||||
/// <param name="oAuthTokens">Token instance.</param>
|
||||
/// <returns>Success or failure.</returns>
|
||||
public bool Initialize(TwitterOAuthTokens oAuthTokens)
|
||||
{
|
||||
return Initialize(oAuthTokens, new NetFrameworkAuthenticationBroker(), new NetFrameworkPasswordManager(), new NetFrameworkStorageManager(), new NetFrameworkSignatureManager());
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to an instance of the underlying data provider.
|
||||
/// </summary>
|
||||
public TwitterDataProvider Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("Provider not initialized.");
|
||||
}
|
||||
|
||||
return twitterDataProvider ?? (twitterDataProvider = new TwitterDataProvider(tokens, authenticationBroker, passwordManager, storageManager, signatureManager));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for specific hash tag.
|
||||
/// </summary>
|
||||
/// <param name="hashTag">Hash tag.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <returns>Returns strongly typed list of results.</returns>
|
||||
public async Task<IEnumerable<Tweet>> SearchAsync(string hashTag, int maxRecords = 20)
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
return await Provider.SearchAsync(hashTag, maxRecords, new TwitterSearchParser());
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await SearchAsync(hashTag, maxRecords);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve user data.
|
||||
/// </summary>
|
||||
/// <param name="screenName">User screen name or null for current logged user.</param>
|
||||
/// <returns>Returns user data.</returns>
|
||||
public async Task<TwitterUser> GetUserAsync(string screenName = null)
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
return await Provider.GetUserAsync(screenName);
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await GetUserAsync(screenName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve user timeline data.
|
||||
/// </summary>
|
||||
/// <param name="screenName">User screen name.</param>
|
||||
/// <param name="maxRecords">Upper record limit.</param>
|
||||
/// <returns>Returns strongly typed list of results.</returns>
|
||||
public async Task<IEnumerable<Tweet>> GetUserTimeLineAsync(string screenName, int maxRecords = 20)
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
return await Provider.GetUserTimeLineAsync(screenName, maxRecords, new TweetParser());
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await GetUserTimeLineAsync(screenName, maxRecords);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post a Tweet with associated pictures.
|
||||
/// </summary>
|
||||
/// <param name="message">Tweet message.</param>
|
||||
/// <returns>Returns success or failure of post request.</returns>
|
||||
public async Task<bool> TweetStatusAsync(string message)
|
||||
{
|
||||
return await TweetStatusAsync(new TwitterStatus { Message = message });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post a Tweet with associated pictures.
|
||||
/// </summary>
|
||||
/// <param name="message">Tweet message.</param>
|
||||
/// <param name="pictures">Pictures to attach to the tweet (up to 4).</param>
|
||||
/// <returns>Returns success or failure of post request.</returns>
|
||||
public async Task<bool> TweetStatusAsync(string message, params Stream[] pictures)
|
||||
{
|
||||
return await TweetStatusAsync(new TwitterStatus { Message = message }, pictures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post a Tweet with associated pictures.
|
||||
/// </summary>
|
||||
/// <param name="status">The tweet information.</param>
|
||||
/// <returns>Returns success or failure of post request.</returns>
|
||||
public async Task<bool> TweetStatusAsync(TwitterStatus status)
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
return await Provider.TweetStatusAsync(status);
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await TweetStatusAsync(status);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post a Tweet with associated pictures.
|
||||
/// </summary>
|
||||
/// <param name="status">The tweet information.</param>
|
||||
/// <param name="pictures">Pictures to attach to the tweet (up to 4).</param>
|
||||
/// <returns>Returns success or failure of post request.</returns>
|
||||
public async Task<bool> TweetStatusAsync(TwitterStatus status, params Stream[] pictures)
|
||||
{
|
||||
if (pictures.Length > 4)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(pictures));
|
||||
}
|
||||
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
return await Provider.TweetStatusAsync(status, pictures);
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await TweetStatusAsync(status, pictures);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request list data from service provider based upon a given config / query.
|
||||
/// </summary>
|
||||
/// <param name="config">TwitterDataConfig instance.</param>
|
||||
/// <param name="maxRecords">Upper limit of records to return. Up to a maximum of 200 per distinct request.</param>
|
||||
/// <returns>Strongly typed list of data returned from the service.</returns>
|
||||
public async Task<List<Tweet>> RequestAsync(TwitterDataConfig config, int maxRecords = 20)
|
||||
{
|
||||
return await RequestAsync<Tweet>(config, maxRecords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request list data from service provider based upon a given config / query.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Model type expected back - e.g. Tweet.</typeparam>
|
||||
/// <param name="config">TwitterDataConfig instance.</param>
|
||||
/// <param name="maxRecords">Upper limit of records to return. Up to a maximum of 200 per distinct request.</param>
|
||||
/// <returns>Strongly typed list of data returned from the service.</returns>
|
||||
public async Task<List<T>> RequestAsync<T>(TwitterDataConfig config, int maxRecords = 20)
|
||||
where T : Toolkit.Parsers.SchemaBase
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
List<T> queryResults = new List<T>();
|
||||
|
||||
var results = await Provider.LoadDataAsync<T>(config, maxRecords, 0, new TwitterParser<T>());
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
queryResults.Add(result);
|
||||
}
|
||||
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
return await RequestAsync<T>(config, maxRecords);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user in to Twitter.
|
||||
/// </summary>
|
||||
/// <returns>Returns success or failure of login attempt.</returns>
|
||||
public Task<bool> LoginAsync()
|
||||
{
|
||||
return Provider.LoginAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log user out of Twitter.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public Task LogoutAsync()
|
||||
{
|
||||
return Provider.LogoutAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open a connection to user's stream service
|
||||
/// </summary>
|
||||
/// <param name="callback">Method called each time a tweet arrives</param>
|
||||
/// <returns>Task</returns>
|
||||
public async Task StartUserStreamAsync(TwitterStreamCallbacks.TwitterStreamCallback callback)
|
||||
{
|
||||
if (Provider.LoggedIn)
|
||||
{
|
||||
await Provider.StartUserStreamAsync(new TwitterUserStreamParser(), callback);
|
||||
return;
|
||||
}
|
||||
|
||||
var isLoggedIn = await LoginAsync();
|
||||
if (isLoggedIn)
|
||||
{
|
||||
await StartUserStreamAsync(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the connection to user's stream service
|
||||
/// </summary>
|
||||
public void StopUserStream()
|
||||
{
|
||||
Provider.StopStream();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// A class for Twitter status other than the pictures
|
||||
/// NOTE: Can be extended to handle the other pieces of the Twitter Status REST API.
|
||||
/// https://dev.twitter.com/rest/reference/post/statuses/update
|
||||
/// Validation COULD be added to the Lat/Long, but since Twitter ignores these if they are invalid then no big deal.
|
||||
/// </summary>
|
||||
public class TwitterStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the explicit latitude and longitude of the "tweet" message is displayed.
|
||||
/// NOTE: Whether or not to put a pin on the exact coordinates a Tweet has been sent from.
|
||||
/// </summary>
|
||||
public bool DisplayCoordinates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ID of the original tweet.
|
||||
/// </summary>
|
||||
public string InReplyToStatusId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the latitude of the "tweet" message.
|
||||
/// NOTE: This parameter will be ignored unless it is inside the range -90.0 to +90.0 (North is positive) inclusive.
|
||||
/// It will also be ignored if there isn’t a corresponding long parameter.
|
||||
/// </summary>
|
||||
public double? Latitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the longitude of the "tweet" message.
|
||||
/// NOTE: The valid ranges for longitude is -180.0 to +180.0 (East is positive) inclusive.
|
||||
/// This parameter will be ignored if outside that range, if it is not a number, if geo_enabled is disabled,
|
||||
/// or if there not a corresponding lat parameter.
|
||||
/// </summary>
|
||||
public double? Longitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text of the Tweet message.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text of the Tweet message.
|
||||
/// </summary>
|
||||
public string PlaceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the Tweet contains sensitive content (such as nudity, etc.).
|
||||
/// </summary>
|
||||
public bool PossiblySensitive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request parameters
|
||||
/// </summary>
|
||||
public string RequestParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
string result = $"status={Uri.EscapeDataString(Message)}";
|
||||
|
||||
if (Latitude.HasValue && Longitude.HasValue)
|
||||
{
|
||||
result = $"{result}&lat={Latitude.Value.ToString(CultureInfo.InvariantCulture)}&long={Longitude.Value.ToString(CultureInfo.InvariantCulture)}";
|
||||
result = AddRequestParameter(result, "display_coordinates", DisplayCoordinates);
|
||||
}
|
||||
|
||||
result = AddRequestParameter(result, "in_reply_to_status_id", InReplyToStatusId);
|
||||
result = AddRequestParameter(result, "place_id", PlaceId);
|
||||
result = AddRequestParameter(result, "possibly_sensitive", PossiblySensitive);
|
||||
result = AddRequestParameter(result, "trim_user", TrimUser);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the Tweet returned in a timeline will include a user object including only the status authors numerical ID.
|
||||
/// </summary>
|
||||
public bool TrimUser { get; set; }
|
||||
|
||||
private string AddRequestParameter(string request, string parameterName, bool value)
|
||||
{
|
||||
var result = request;
|
||||
|
||||
if (value)
|
||||
{
|
||||
result = $"{result}&{parameterName}=true";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string AddRequestParameter(string request, string parameterName, string value)
|
||||
{
|
||||
var result = request;
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
result = $"{result}&{parameterName}={Uri.EscapeDataString(value)}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.Toolkit.Services.Twitter
|
||||
{
|
||||
/// <summary>
|
||||
/// Callbacks used for Twitter streams.
|
||||
/// </summary>
|
||||
public class TwitterStreamCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback converting json to Tweet
|
||||
/// </summary>
|
||||
/// <param name="json">Raw Json from Twitter API</param>
|
||||
public delegate void RawJsonCallback(string json);
|
||||
|
||||
/// <summary>
|
||||
/// Callback returning the parsed tweet
|
||||
/// </summary>
|
||||
/// <param name="tweet">Strongly typed tweet</param>
|
||||
public delegate void TwitterStreamCallback(ITwitterResult tweet);
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче