diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 387ceda6a..5a1d48082 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,18 +16,23 @@ jobs: pool: vmImage: 'ubuntu-16.04' steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: 'sdk' + version: '3.x' - template: azure/build.yml - job: Windows_build pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2019' condition: ne(variables['Build.SourceBranch'], 'refs/heads/dev') steps: - template: azure/build.yml - job: Windows_deploy pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2019' condition: | and ( succeeded(), diff --git a/azure/deploy.yml b/azure/deploy.yml index f2affe667..61994299e 100644 --- a/azure/deploy.yml +++ b/azure/deploy.yml @@ -1,32 +1,35 @@ steps: - script: | - dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) - dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "../../artifacts/" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Core\Discord.Net.Core.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Rest\Discord.Net.Rest.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Commands\Discord.Net.Commands.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Webhook\Discord.Net.Webhook.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Providers.WS4Net\Discord.Net.Providers.WS4Net.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) + dotnet pack "src\Discord.Net.Analyzers\Discord.Net.Analyzers.csproj" --no-restore --no-build -v minimal -c $(buildConfiguration) -o "$(Build.ArtifactStagingDirectory)" /p:BuildNumber=$(buildNumber) /p:IsTagBuild=$(buildTag) displayName: Pack projects -- task: NuGet@0 - inputs: - command: pack - arguments: src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="" +- task: NuGetCommand@2 displayName: Pack metapackage (release mode) condition: eq(variables['buildTag'], True) - -- task: NuGet@0 inputs: - command: pack - arguments: src/Discord.Net/Discord.Net.nuspec -OutputDirectory "artifacts" -properties suffix="-$(buildNumber)" + command: 'pack' + packagesToPack: 'src/Discord.Net/Discord.Net.nuspec' + versioningScheme: 'off' + +- task: NuGetCommand@2 displayName: Pack metapackage condition: eq(variables['buildTag'], False) + inputs: + command: 'pack' + packagesToPack: 'src/Discord.Net/Discord.Net.nuspec' + versioningScheme: 'off' + buildProperties: 'suffix=-$(buildNumber)' - task: NuGetCommand@2 displayName: Push to NuGet inputs: command: push nuGetFeedType: external - packagesToPush: 'artifacts/*.nupkg' + packagesToPush: '$(Build.ArtifactStagingDirectory)/*.nupkg' publishFeedCredentials: myget-discord diff --git a/azure/docs.bat b/azure/docs.bat index 572534cee..504e12991 100644 --- a/azure/docs.bat +++ b/azure/docs.bat @@ -5,7 +5,8 @@ ECHO remove old 'latest' ECHO Y | RMDIR /S docs-static\latest || EXIT /B 1 ECHO build docs -docfx.console\tools\docfx.exe docs/docfx.json -o docs-static/latest/ || EXIT /B 1 +docfx.console\tools\docfx.exe docs/docfx.json -o docs-staging || EXIT /B 1 +ROBOCOPY docs-staging\_site docs-static\latest /MIR ECHO commit and deploy git config --global user.name "Discord.Net CI Robot" && git config --global user.email "robot@foxbot.me" diff --git a/docs/guides/commands/intro.md b/docs/guides/commands/intro.md index c815175bd..f8ff1b596 100644 --- a/docs/guides/commands/intro.md +++ b/docs/guides/commands/intro.md @@ -111,7 +111,7 @@ optional, give it a default value (i.e., `int num = 0`). #### Parameters with Spaces -To accept a comma-separated list, set the parameter to `params Type[]`. +To accept a space-separated list, set the parameter to `params Type[]`. Should a parameter include spaces, the parameter **must** be wrapped in quotes. For example, for a command with a parameter @@ -218,4 +218,4 @@ Submodules are "modules" that reside within another one. Typically, submodules are used to create nested groups (although not required to create nested groups). -[!code-csharp[Groups and Submodules](samples/intro/groups.cs)] \ No newline at end of file +[!code-csharp[Groups and Submodules](samples/intro/groups.cs)] diff --git a/samples/01_basic_ping_bot/01_basic_ping_bot.csproj b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj index 5484e3d55..4b4e35e3f 100644 --- a/samples/01_basic_ping_bot/01_basic_ping_bot.csproj +++ b/samples/01_basic_ping_bot/01_basic_ping_bot.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.0 diff --git a/samples/02_commands_framework/02_commands_framework.csproj b/samples/02_commands_framework/02_commands_framework.csproj index f479ee0b0..84b30aa99 100644 --- a/samples/02_commands_framework/02_commands_framework.csproj +++ b/samples/02_commands_framework/02_commands_framework.csproj @@ -2,11 +2,11 @@ Exe - netcoreapp2.0 + netcoreapp3.0 - + diff --git a/samples/03_sharded_client/03_sharded_client.csproj b/samples/03_sharded_client/03_sharded_client.csproj index 5d76961cd..a6599c117 100644 --- a/samples/03_sharded_client/03_sharded_client.csproj +++ b/samples/03_sharded_client/03_sharded_client.csproj @@ -2,10 +2,14 @@ Exe - netcoreapp2.1 + netcoreapp3.0 _03_sharded_client + + + + diff --git a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj index 5da3d506d..1b2ee45bf 100644 --- a/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj +++ b/src/Discord.Net.Analyzers/Discord.Net.Analyzers.csproj @@ -1,13 +1,13 @@ - + Discord.Net.Analyzers Discord.Analyzers A Discord.Net extension adding support for design-time analysis of the API usage. - netstandard1.3 + netstandard2.0;netstandard2.1 - + diff --git a/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs b/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs index 0760d019f..38d3f39d4 100644 --- a/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs +++ b/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -24,6 +24,8 @@ namespace Discord.Analyzers public override void Initialize(AnalysisContext context) { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression); } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 3a7da3862..d5c060fe4 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -49,7 +49,7 @@ namespace Discord.Commands private readonly ConcurrentDictionary _typedModuleDefs; private readonly ConcurrentDictionary> _typeReaders; private readonly ConcurrentDictionary _defaultTypeReaders; - private readonly ImmutableList> _entityTypeReaders; //TODO: Candidate for C#7 Tuple + private readonly ImmutableList<(Type EntityType, Type TypeReaderType)> _entityTypeReaders; private readonly HashSet _moduleDefs; private readonly CommandMap _map; @@ -124,11 +124,11 @@ namespace Discord.Commands _defaultTypeReaders[typeof(string)] = new PrimitiveTypeReader((string x, out string y) => { y = x; return true; }, 0); - var entityTypeReaders = ImmutableList.CreateBuilder>(); - entityTypeReaders.Add(new Tuple(typeof(IMessage), typeof(MessageTypeReader<>))); - entityTypeReaders.Add(new Tuple(typeof(IChannel), typeof(ChannelTypeReader<>))); - entityTypeReaders.Add(new Tuple(typeof(IRole), typeof(RoleTypeReader<>))); - entityTypeReaders.Add(new Tuple(typeof(IUser), typeof(UserTypeReader<>))); + var entityTypeReaders = ImmutableList.CreateBuilder<(Type, Type)>(); + entityTypeReaders.Add((typeof(IMessage), typeof(MessageTypeReader<>))); + entityTypeReaders.Add((typeof(IChannel), typeof(ChannelTypeReader<>))); + entityTypeReaders.Add((typeof(IRole), typeof(RoleTypeReader<>))); + entityTypeReaders.Add((typeof(IUser), typeof(UserTypeReader<>))); _entityTypeReaders = entityTypeReaders.ToImmutable(); } @@ -408,7 +408,7 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (typeInfo.IsEnum) return true; - return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); + return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType)); } internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { @@ -439,9 +439,9 @@ namespace Discord.Commands //Is this an entity? for (int i = 0; i < _entityTypeReaders.Count; i++) { - if (type == _entityTypeReaders[i].Item1 || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].Item1)) + if (type == _entityTypeReaders[i].EntityType || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].EntityType)) { - reader = Activator.CreateInstance(_entityTypeReaders[i].Item2.MakeGenericType(type)) as TypeReader; + reader = Activator.CreateInstance(_entityTypeReaders[i].TypeReaderType.MakeGenericType(type)) as TypeReader; _defaultTypeReaders[type] = reader; return reader; } diff --git a/src/Discord.Net.Commands/Discord.Net.Commands.csproj b/src/Discord.Net.Commands/Discord.Net.Commands.csproj index 1bef1bfea..95e7db491 100644 --- a/src/Discord.Net.Commands/Discord.Net.Commands.csproj +++ b/src/Discord.Net.Commands/Discord.Net.Commands.csproj @@ -4,16 +4,11 @@ Discord.Net.Commands Discord.Commands A Discord.Net extension adding support for bot commands. - net46;netstandard1.3;netstandard2.0 - netstandard1.3;netstandard2.0 + net461;netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1 - - - - - - + diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 6d9f1dd8c..c0104e341 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -49,7 +49,7 @@ namespace Discord.Commands string username = input.Substring(0, index); if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator)) { - var channelUser = await channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && + var channelUser = await channelUsers.FirstOrDefaultAsync(x => x.DiscriminatorValue == discriminator && string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); AddResult(results, channelUser as T, channelUser?.Username == username ? 0.85f : 0.75f); diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj index 20e57d346..dd2f2afe3 100644 --- a/src/Discord.Net.Core/Discord.Net.Core.csproj +++ b/src/Discord.Net.Core/Discord.Net.Core.csproj @@ -4,13 +4,13 @@ Discord.Net.Core Discord The core components for the Discord.Net library. - net46;netstandard1.3;netstandard2.0 - netstandard1.3;netstandard2.0 + net461;netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1 - + - + all diff --git a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs index b603e27a4..8c44f49e3 100644 --- a/src/Discord.Net.Core/Entities/Activities/ActivityType.cs +++ b/src/Discord.Net.Core/Entities/Activities/ActivityType.cs @@ -20,6 +20,10 @@ namespace Discord /// /// The user is watching some form of media. /// - Watching = 3 + Watching = 3, + /// + /// The user has set a custom status. + /// + CustomStatus = 4, } } diff --git a/src/Discord.Net.Core/Entities/Activities/CustomStatusGame.cs b/src/Discord.Net.Core/Entities/Activities/CustomStatusGame.cs new file mode 100644 index 000000000..7bd2664a2 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Activities/CustomStatusGame.cs @@ -0,0 +1,40 @@ +using System; +using System.Diagnostics; + +namespace Discord +{ + /// + /// A user's activity for their custom status. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class CustomStatusGame : Game + { + internal CustomStatusGame() { } + + /// + /// Gets the emote, if it is set. + /// + /// + /// An containing the or set by the user. + /// + public IEmote Emote { get; internal set; } + + /// + /// Gets the timestamp of when this status was created. + /// + /// + /// A containing the time when this status was created. + /// + public DateTimeOffset CreatedAt { get; internal set; } + + /// + /// Gets the state of the status. + /// + public string State { get; internal set; } + + public override string ToString() + => $"{Emote} {State}"; + + private string DebuggerDisplay => $"{Name}"; + } +} diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index e13536a97..ec31019af 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -1,3 +1,5 @@ +using System.Globalization; + namespace Discord { /// @@ -84,5 +86,23 @@ namespace Discord /// are enabled, without the need to manipulate the logic of the flag. /// public Optional SystemChannelFlags { get; set; } + /// + /// Gets or sets the preferred locale of the guild in IETF BCP 47 language tag format. + /// + /// + /// This property takes precedence over . + /// When it is set, the value of + /// will not be used. + /// + public Optional PreferredLocale { get; set; } + /// + /// Gets or sets the preferred locale of the guild. + /// + /// + /// The property takes precedence + /// over this property. When is set, + /// the value of will be unused. + /// + public Optional PreferredCulture { get; set; } } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index d463d86df..213091ad4 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -1,6 +1,7 @@ using Discord.Audio; using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; namespace Discord @@ -249,6 +250,24 @@ namespace Discord /// int PremiumSubscriptionCount { get; } + /// + /// Gets the preferred locale of this guild in IETF BCP 47 + /// language tag format. + /// + /// + /// The preferred locale of the guild in IETF BCP 47 + /// language tag format. + /// + string PreferredLocale { get; } + + /// + /// Gets the preferred culture of this guild. + /// + /// + /// The preferred culture information of this guild. + /// + CultureInfo PreferredCulture { get; } + /// /// Modifies this guild. /// diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 1eba1e076..05f505269 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -140,6 +140,18 @@ namespace Discord /// MessageApplication Application { get; } + /// + /// Gets the reference to the original message if it was crossposted. + /// + /// + /// Sent with Cross-posted messages, meaning they were published from news channels + /// and received by subscriber channels. + /// + /// + /// A message's reference, if any is associated. + /// + MessageReference Reference { get; } + /// /// Gets all reactions included in this message. /// diff --git a/src/Discord.Net.Core/Entities/Messages/MessageReference.cs b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs new file mode 100644 index 000000000..57a508a7c --- /dev/null +++ b/src/Discord.Net.Core/Entities/Messages/MessageReference.cs @@ -0,0 +1,33 @@ +using System.Diagnostics; + +namespace Discord +{ + /// + /// Contains the IDs sent from a crossposted message. + /// + [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + public class MessageReference + { + /// + /// Gets the Message ID of the original message. + /// + public Optional MessageId { get; internal set; } + + /// + /// Gets the Channel ID of the original message. + /// + public ulong ChannelId { get; internal set; } + + /// + /// Gets the Guild ID of the original message. + /// + public Optional GuildId { get; internal set; } + + private string DebuggerDisplay + => $"Channel ID: ({ChannelId}){(GuildId.IsSpecified ? $", Guild ID: ({GuildId.Value})" : "")}" + + $"{(MessageId.IsSpecified ? $", Message ID: ({MessageId.Value})" : "")}"; + + public override string ToString() + => DebuggerDisplay; + } +} diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 6bfead3d6..b522ae47e 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics; -#if NETSTANDARD2_0 || NET45 using StandardColor = System.Drawing.Color; -#endif namespace Discord { @@ -190,12 +188,10 @@ namespace Discord public override int GetHashCode() => RawValue.GetHashCode(); -#if NETSTANDARD2_0 || NET45 public static implicit operator StandardColor(Color color) => StandardColor.FromArgb((int)color.RawValue); public static explicit operator Color(StandardColor color) => new Color((uint)color.ToArgb() << 8 >> 8); -#endif /// /// Gets the hexadecimal representation of the color (e.g. #000ccc). diff --git a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs index 282d20517..d96076259 100644 --- a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs +++ b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs @@ -15,7 +15,7 @@ namespace Discord /// Flattens the specified pages into one asynchronously. public static async Task> FlattenAsync(this IAsyncEnumerable> source) { - return await source.Flatten().ToArray().ConfigureAwait(false); + return await source.Flatten().ToArrayAsync().ConfigureAwait(false); } /// Flattens the specified pages into one . public static IAsyncEnumerable Flatten(this IAsyncEnumerable> source) diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index a31721875..84209902a 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -25,26 +25,28 @@ namespace Discord _nextPage = nextPage; } - public IAsyncEnumerator> GetEnumerator() => new Enumerator(this); + public IAsyncEnumerator> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken()) => new Enumerator(this, cancellationToken); internal class Enumerator : IAsyncEnumerator> { private readonly PagedAsyncEnumerable _source; + private readonly CancellationToken _token; private readonly PageInfo _info; public IReadOnlyCollection Current { get; private set; } - public Enumerator(PagedAsyncEnumerable source) + public Enumerator(PagedAsyncEnumerable source, CancellationToken token) { _source = source; + _token = token; _info = new PageInfo(source._start, source._count, source.PageSize); } - public async Task MoveNext(CancellationToken cancelToken) + public async ValueTask MoveNextAsync() { if (_info.Remaining == 0) return false; - var data = await _source._getPage(_info, cancelToken).ConfigureAwait(false); + var data = await _source._getPage(_info, _token).ConfigureAwait(false); Current = new Page(_info, data); _info.Page++; @@ -71,7 +73,11 @@ namespace Discord return true; } - public void Dispose() { Current = null; } + public ValueTask DisposeAsync() + { + Current = null; + return default; + } } } } diff --git a/src/Discord.Net.Examples/Discord.Net.Examples.csproj b/src/Discord.Net.Examples/Discord.Net.Examples.csproj index b02d2e6d8..ec0253428 100644 --- a/src/Discord.Net.Examples/Discord.Net.Examples.csproj +++ b/src/Discord.Net.Examples/Discord.Net.Examples.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -15,7 +15,7 @@ - + diff --git a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj index bfd0983ce..e143340e1 100644 --- a/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj +++ b/src/Discord.Net.Providers.WS4Net/Discord.Net.Providers.WS4Net.csproj @@ -1,10 +1,10 @@ - + Discord.Net.Providers.WS4Net Discord.Providers.WS4Net An optional WebSocket client provider for Discord.Net using WebSocket4Net - netstandard1.3 + netstandard2.0 diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetProvider.cs b/src/Discord.Net.Providers.WS4Net/WS4NetProvider.cs index 166e767d0..b56f3b4f0 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetProvider.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetProvider.cs @@ -1,4 +1,4 @@ -using Discord.Net.WebSockets; +using Discord.Net.WebSockets; namespace Discord.Net.Providers.WS4Net { diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index 2ec1e3846..d3a618697 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -35,6 +35,12 @@ namespace Discord.API public Optional SessionId { get; set; } [JsonProperty("Flags")] public Optional Flags { get; set; } + [JsonProperty("id")] + public Optional Id { get; set; } + [JsonProperty("emoji")] + public Optional Emoji { get; set; } + [JsonProperty("created_at")] + public Optional CreatedAt { get; set; } [OnError] internal void OnError(StreamingContext context, ErrorContext errorContext) diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index 343d5b12c..56bd841ea 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -58,5 +58,7 @@ namespace Discord.API public SystemChannelMessageDeny SystemChannelFlags { get; set; } [JsonProperty("premium_subscription_count")] public int? PremiumSubscriptionCount { get; set; } + [JsonProperty("preferred_locale")] + public string PreferredLocale { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index a5016f923..f20035685 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -50,6 +50,8 @@ namespace Discord.API // sent with Rich Presence-related chat embeds [JsonProperty("application")] public Optional Application { get; set; } + [JsonProperty("message_reference")] + public Optional Reference { get; set; } [JsonProperty("flags")] public Optional Flags { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/MessageReference.cs b/src/Discord.Net.Rest/API/Common/MessageReference.cs new file mode 100644 index 000000000..8c0f8fe14 --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/MessageReference.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API +{ + internal class MessageReference + { + [JsonProperty("message_id")] + public Optional MessageId { get; set; } + + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } + } +} diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs index 9c519d3a8..6341b63b6 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs @@ -32,5 +32,7 @@ namespace Discord.API.Rest public Optional ExplicitContentFilter { get; set; } [JsonProperty("system_channel_flags")] public Optional SystemChannelFlags { get; set; } + [JsonProperty("preferred_locale")] + public string PreferredLocale { get; set; } } } diff --git a/src/Discord.Net.Rest/Discord.Net.Rest.csproj b/src/Discord.Net.Rest/Discord.Net.Rest.csproj index 75b69bd04..b9592f18d 100644 --- a/src/Discord.Net.Rest/Discord.Net.Rest.csproj +++ b/src/Discord.Net.Rest/Discord.Net.Rest.csproj @@ -4,16 +4,13 @@ Discord.Net.Rest Discord.Rest A core Discord.Net library containing the REST client and models. - net46;netstandard1.3;netstandard2.0 - netstandard1.3;netstandard2.0 + net461;netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1 - - - - + diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index d20aed431..7730a9cc3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -68,6 +68,12 @@ namespace Discord.Rest if (args.SystemChannelFlags.IsSpecified) apiArgs.SystemChannelFlags = args.SystemChannelFlags.Value; + // PreferredLocale takes precedence over PreferredCulture + if (args.PreferredLocale.IsSpecified) + apiArgs.PreferredLocale = args.PreferredLocale.Value; + else if (args.PreferredCulture.IsSpecified) + apiArgs.PreferredLocale = args.PreferredCulture.Value.Name; + return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } /// is null. diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index cd142b184..e9e4d3290 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using EmbedModel = Discord.API.GuildEmbed; @@ -64,6 +65,11 @@ namespace Discord.Rest public string Description { get; private set; } /// public int PremiumSubscriptionCount { get; private set; } + /// + public string PreferredLocale { get; private set; } + + /// + public CultureInfo PreferredCulture { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -124,6 +130,8 @@ namespace Discord.Rest SystemChannelFlags = model.SystemChannelFlags; Description = model.Description; PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); + PreferredLocale = model.PreferredLocale; + PreferredCulture = new CultureInfo(PreferredLocale); if (model.Emojis != null) { diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index ef6d9a14c..75892defb 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -11,6 +11,16 @@ namespace Discord.Rest { internal static class MessageHelper { + /// + /// Regex used to check if some text is formatted as inline code. + /// + private static readonly Regex InlineCodeRegex = new Regex(@"[^\\]?(`).+?[^\\](`)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); + + /// + /// Regex used to check if some text is formatted as a code block. + /// + private static readonly Regex BlockCodeRegex = new Regex(@"[^\\]?(```).+?[^\\](```)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); + /// Only the author of a message may modify the message. /// Message content is too long, length must be less or equal to . public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, @@ -112,9 +122,6 @@ namespace Discord.Rest int index = 0; var codeIndex = 0; - var inlineRegex = new Regex(@"[^\\]?(`).+?[^\\](`)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); - var blockRegex = new Regex(@"[^\\]?(```).+?[^\\](```)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline); - // checks if the tag being parsed is wrapped in code blocks bool CheckWrappedCode() { @@ -125,7 +132,7 @@ namespace Discord.Rest // loop through all code blocks that are before the start of the tag while (codeIndex < index) { - var blockMatch = blockRegex.Match(text, codeIndex); + var blockMatch = BlockCodeRegex.Match(text, codeIndex); if (blockMatch.Success) { if (EnclosedInBlock(blockMatch)) @@ -136,7 +143,7 @@ namespace Discord.Rest continue; return false; } - var inlineMatch = inlineRegex.Match(text, codeIndex); + var inlineMatch = InlineCodeRegex.Match(text, codeIndex); if (inlineMatch.Success) { if (EnclosedInBlock(inlineMatch)) diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 29a9c9bd2..f457f4f7a 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -62,6 +62,8 @@ namespace Discord.Rest public MessageActivity Activity { get; private set; } /// public MessageApplication Application { get; private set; } + /// + public MessageReference Reference { get; private set; } internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) : base(discord, id) @@ -108,6 +110,17 @@ namespace Discord.Rest }; } + if(model.Reference.IsSpecified) + { + // Creates a new Reference from the API model + Reference = new MessageReference + { + GuildId = model.Reference.Value.GuildId, + ChannelId = model.Reference.Value.ChannelId, + MessageId = model.Reference.Value.MessageId + }; + } + if (model.Reactions.IsSpecified) { var value = model.Reactions.Value; diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 4d164df96..e265f991f 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -5,6 +5,13 @@ namespace Discord.Rest { internal static class EntityExtensions { + public static IEmote ToIEmote(this API.Emoji model) + { + if (model.Id.HasValue) + return model.ToEntity(); + return new Emoji(model.Name); + } + public static GuildEmote ToEntity(this API.Emoji model) => new GuildEmote(model.Id.Value, model.Name, diff --git a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj index ddd3b7954..26a249097 100644 --- a/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj +++ b/src/Discord.Net.WebSocket/Discord.Net.WebSocket.csproj @@ -4,15 +4,12 @@ Discord.Net.WebSocket Discord.WebSocket A core Discord.Net library containing the WebSocket client and models. - net46;netstandard1.3;netstandard2.0 - netstandard1.3;netstandard2.0 + net461;netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1 true - - - diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index cd0ab3db2..054348ef1 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -105,6 +106,11 @@ namespace Discord.WebSocket public string Description { get; private set; } /// public int PremiumSubscriptionCount { get; private set; } + /// + public string PreferredLocale { get; private set; } + + /// + public CultureInfo PreferredCulture { get; private set; } /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); @@ -374,6 +380,8 @@ namespace Discord.WebSocket SystemChannelFlags = model.SystemChannelFlags; Description = model.Description; PremiumSubscriptionCount = model.PremiumSubscriptionCount.GetValueOrDefault(); + PreferredLocale = model.PreferredLocale; + PreferredCulture = new CultureInfo(PreferredLocale); if (model.Emojis != null) { diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index ae42d9d61..7900b7ee7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -53,6 +53,9 @@ namespace Discord.WebSocket /// public MessageApplication Application { get; private set; } + /// + public MessageReference Reference { get; private set; } + /// /// Returns all attachments included in this message. /// @@ -140,6 +143,17 @@ namespace Discord.WebSocket PartyId = model.Activity.Value.PartyId.Value }; } + + if (model.Reference.IsSpecified) + { + // Creates a new Reference from the API model + Reference = new MessageReference + { + GuildId = model.Reference.Value.GuildId, + ChannelId = model.Reference.Value.ChannelId, + MessageId = model.Reference.Value.MessageId + }; + } } /// diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index fd91ba987..bad72aaea 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -1,3 +1,5 @@ +using Discord.Rest; +using System; using System.Collections.Immutable; using System.Linq; @@ -7,6 +9,19 @@ namespace Discord.WebSocket { public static IActivity ToEntity(this API.Game model) { + // Custom Status Game + if (model.Id.IsSpecified) + { + return new CustomStatusGame() + { + Type = ActivityType.CustomStatus, + Name = model.Name, + State = model.State.IsSpecified ? model.State.Value : null, + Emote = model.Emoji.IsSpecified ? model.Emoji.Value.ToIEmote() : null, + CreatedAt = DateTimeOffset.FromUnixTimeMilliseconds(model.CreatedAt.Value), + }; + } + // Spotify Game if (model.SyncId.IsSpecified) { diff --git a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj index 58282d85b..f1db66363 100644 --- a/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj +++ b/src/Discord.Net.Webhook/Discord.Net.Webhook.csproj @@ -4,7 +4,7 @@ Discord.Net.Webhook Discord.Webhook A core Discord.Net library containing the Webhook client and models. - netstandard1.3 + netstandard2.0;netstandard2.1 diff --git a/src/Discord.Net/Discord.Net.nuspec b/src/Discord.Net/Discord.Net.nuspec index 3aa0d6add..4b7717e58 100644 --- a/src/Discord.Net/Discord.Net.nuspec +++ b/src/Discord.Net/Discord.Net.nuspec @@ -13,21 +13,21 @@ false https://github.com/RogueException/Discord.Net/raw/dev/docs/marketing/logo/PackageLogo.png - + - - + + - + diff --git a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj index 1ee986e8a..bc587657c 100644 --- a/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj +++ b/test/Discord.Net.Analyzers.Tests/Discord.Net.Analyzers.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp3.0 false @@ -15,10 +15,13 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Discord.Net.Analyzers.Tests/Extensions/AppDomainPolyfill.cs b/test/Discord.Net.Analyzers.Tests/Extensions/AppDomainPolyfill.cs deleted file mode 100644 index 729bc385c..000000000 --- a/test/Discord.Net.Analyzers.Tests/Extensions/AppDomainPolyfill.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Linq; -using System.Reflection; -using Microsoft.DotNet.PlatformAbstractions; -using Microsoft.Extensions.DependencyModel; - -namespace System -{ - /// Polyfill of the AppDomain class from full framework. - internal class AppDomain - { - public static AppDomain CurrentDomain { get; private set; } - - private AppDomain() - { - } - - static AppDomain() - { - CurrentDomain = new AppDomain(); - } - - public Assembly[] GetAssemblies() - { - var rid = RuntimeEnvironment.GetRuntimeIdentifier(); - var ass = DependencyContext.Default.GetRuntimeAssemblyNames(rid); - - return ass.Select(xan => Assembly.Load(xan)).ToArray(); - } - } -} \ No newline at end of file diff --git a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj index cd4aafac0..c571059ef 100644 --- a/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj +++ b/test/Discord.Net.Tests.Integration/Discord.Net.Tests.Integration.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 @@ -15,9 +15,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj index 4a7898b14..357bf9531 100644 --- a/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj +++ b/test/Discord.Net.Tests.Unit/Discord.Net.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1 + netcoreapp3.0 false @@ -13,9 +13,12 @@ - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs index 573ca57c3..8b4e8b0d0 100644 --- a/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs +++ b/test/Discord.Net.Tests.Unit/MockedEntities/MockedGroupChannel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; using System.Threading.Tasks; using Discord.Audio;