Adding Entity guides, flowcharts, better sample system. (#2054)

* initial

* Interaction glossary entry

* Sharded Interaction sample

* Renames into solution

* Debugging samples

* Modify target location for webhookclient

* Finalizing docs work, resolving docfx errors.

* Adding threaduser to user chart

* Add branch info to readme.

* Edits to user chart

* Resolve format for glossary entries

* Patch sln target

* Issue with file naming fixed

* Patch 1/x for builds

* Appending suggestions
This commit is contained in:
Armano den Boef
2022-01-27 14:50:49 +01:00
committed by GitHub
parent b0f59e3eb9
commit b14af1c008
57 changed files with 1059 additions and 344 deletions

View File

@@ -1,77 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
namespace _01_basic_ping_bot
{
// This is a minimal, bare-bones example of using Discord.Net
//
// If writing a bot with commands, we recommend using the Discord.Net.Commands
// framework, rather than handling commands yourself, like we do in this sample.
//
// You can find samples of using the command framework:
// - Here, under the 02_commands_framework sample
// - https://github.com/foxbot/DiscordBotBase - a bare-bones bot template
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
class Program
{
private readonly DiscordSocketClient _client;
// Discord.Net heavily utilizes TAP for async, so we create
// an asynchronous context from the beginning.
static void Main(string[] args)
{
new Program().MainAsync().GetAwaiter().GetResult();
}
public Program()
{
// It is recommended to Dispose of a client when you are finished
// using it, at the end of your app's lifetime.
_client = new DiscordSocketClient();
_client.Log += LogAsync;
_client.Ready += ReadyAsync;
_client.MessageReceived += MessageReceivedAsync;
}
public async Task MainAsync()
{
// Tokens should be considered secret data, and never hard-coded.
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await _client.StartAsync();
// Block the program until it is closed.
await Task.Delay(Timeout.Infinite);
}
private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
// The Ready event indicates that the client has opened a
// connection and it is now safe to access the cache.
private Task ReadyAsync()
{
Console.WriteLine($"{_client.CurrentUser} is connected!");
return Task.CompletedTask;
}
// This is not the recommended way to write a bot - consider
// reading over the Commands Framework sample.
private async Task MessageReceivedAsync(SocketMessage message)
{
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;
if (message.Content == "!ping")
await message.Channel.SendMessageAsync("pong!");
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _04_interactions_framework
{
public enum ExampleEnum
{
First,
Second,
Third,
Fourth
}
}

112
samples/BasicBot/Program.cs Normal file
View File

@@ -0,0 +1,112 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
namespace BasicBot
{
// This is a minimal, bare-bones example of using Discord.Net.
//
// If writing a bot with commands/interactions, we recommend using the Discord.Net.Commands/Discord.Net.Interactions
// framework, rather than handling them yourself, like we do in this sample.
//
// You can find samples of using the command framework:
// - Here, under the TextCommandFramework sample
// - At the guides: https://discordnet.dev/guides/text_commands/intro.html
//
// You can find samples of using the interaction framework:
// - Here, under the InteractionFramework sample
// - At the guides: https://discordnet.dev/guides/int_framework/intro.html
class Program
{
// Non-static readonly fields can only be assigned in a constructor.
// If you want to assign it elsewhere, consider removing the readonly keyword.
private readonly DiscordSocketClient _client;
// Discord.Net heavily utilizes TAP for async, so we create
// an asynchronous context from the beginning.
static void Main(string[] args)
=> new Program()
.MainAsync()
.GetAwaiter()
.GetResult();
public Program()
{
// It is recommended to Dispose of a client when you are finished
// using it, at the end of your app's lifetime.
_client = new DiscordSocketClient();
// Subscribing to client events, so that we may receive them whenever they're invoked.
_client.Log += LogAsync;
_client.Ready += ReadyAsync;
_client.MessageReceived += MessageReceivedAsync;
_client.InteractionCreated += InteractionCreatedAsync;
}
public async Task MainAsync()
{
// Tokens should be considered secret data, and never hard-coded.
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
// Different approaches to making your token a secret is by putting them in local .json, .yaml, .xml or .txt files, then reading them on startup.
await _client.StartAsync();
// Block the program until it is closed.
await Task.Delay(Timeout.Infinite);
}
private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
// The Ready event indicates that the client has opened a
// connection and it is now safe to access the cache.
private Task ReadyAsync()
{
Console.WriteLine($"{_client.CurrentUser} is connected!");
return Task.CompletedTask;
}
// This is not the recommended way to write a bot - consider
// reading over the Commands Framework sample.
private async Task MessageReceivedAsync(SocketMessage message)
{
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;
if (message.Content == "!ping")
{
// Create a new componentbuilder, in which dropdowns & buttons can be created.
var cb = new ComponentBuilder()
.WithButton("Click me!", "unique-id", ButtonStyle.Primary);
// Send a message with content 'pong', including a button.
// This button needs to be build by calling .Build() before being passed into the call.
await message.Channel.SendMessageAsync("pong!", components: cb.Build());
}
}
// For better functionality & a more developer-friendly approach to handling any kind of interaction, refer to:
// https://discordnet.dev/guides/int_framework/intro.html
private async Task InteractionCreatedAsync(SocketInteraction interaction)
{
// safety-casting is the best way to prevent something being cast from being null.
// If this check does not pass, it could not be cast to said type.
if (interaction is SocketMessageComponent component)
{
// Check for the ID created in the button mentioned above.
if (component.Data.CustomId == "unique-id")
await interaction.RespondAsync("Thank you for clicking my button!");
else Console.WriteLine("An ID has been received that has no handler!");
}
}
}
}

View File

@@ -0,0 +1,37 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System;
using System.Threading.Tasks;
namespace InteractionFramework.Attributes
{
internal class DoUserCheck : PreconditionAttribute
{
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services)
{
// Check if the component matches the target properly.
if (context.Interaction is not SocketMessageComponent componentContext)
return Task.FromResult(PreconditionResult.FromError("Context unrecognized as component context."));
else
{
// The approach here entirely depends on how you construct your custom ID. In this case, the format is:
// unique-name:*,*
// here the name and wildcards are split by ':'
var param = componentContext.Data.CustomId.Split(':');
// here we determine that we should always check for the first ',' present.
// This will deal with additional wildcards by always selecting the first wildcard present.
if (param.Length > 1 && ulong.TryParse(param[1].Split(',')[0], out ulong id))
return (context.User.Id == id)
// If the user ID
? Task.FromResult(PreconditionResult.FromSuccess())
: Task.FromResult(PreconditionResult.FromError("User ID does not match component ID!"));
else return Task.FromResult(PreconditionResult.FromError("Parse cannot be done if no userID exists."));
}
}
}
}

View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _04_interactions_framework
namespace InteractionFramework.Attributes
{
public class RequireOwnerAttribute : PreconditionAttribute
{

View File

@@ -2,13 +2,10 @@ using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace _04_interactions_framework
namespace InteractionFramework
{
public class CommandHandler
{
@@ -27,6 +24,9 @@ namespace _04_interactions_framework
{
// Add the public modules that inherit InteractionModuleBase<T> to the InteractionService
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
// Another approach to get the assembly of a specific type is:
// typeof(CommandHandler).Assembly
// Process the InteractionCreated payloads to execute Interactions commands
_client.InteractionCreated += HandleInteraction;
@@ -37,6 +37,8 @@ namespace _04_interactions_framework
_commands.ComponentCommandExecuted += ComponentCommandExecuted;
}
# region Error Handling
private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3)
{
if (!arg3.IsSuccess)
@@ -123,6 +125,9 @@ namespace _04_interactions_framework
return Task.CompletedTask;
}
# endregion
# region Execution
private async Task HandleInteraction (SocketInteraction arg)
{
@@ -142,5 +147,6 @@ namespace _04_interactions_framework
await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync());
}
}
# endregion
}
}

View File

@@ -0,0 +1,10 @@
namespace InteractionFramework
{
public enum ExampleEnum
{
First,
Second,
Third,
Fourth
}
}

View File

@@ -0,0 +1,18 @@
using Discord.Interactions;
using Discord.WebSocket;
using InteractionFramework.Attributes;
using System.Threading.Tasks;
namespace InteractionFramework
{
// As with all other modules, we create the context by defining what type of interaction this module is supposed to target.
internal class ComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>>
{
// With the Attribute DoUserCheck you can make sure that only the user this button targets can click it. This is defined by the first wildcard: *.
// See Attributes/DoUserCheckAttribute.cs for elaboration.
[DoUserCheck]
[ComponentInteraction("myButton:*")]
public async Task ClickButtonAsync(string userId)
=> await RespondAsync(text: ":thumbsup: Clicked!");
}
}

View File

@@ -1,16 +1,11 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _04_interactions_framework.Modules
namespace InteractionFramework.Modules
{
// Interation modules must be public and inherit from an IInterationModuleBase
public class UtilityModule : InteractionModuleBase<SocketInteractionContext>
public class GeneralModule : InteractionModuleBase<SocketInteractionContext>
{
// Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider
public InteractionService Commands { get; set; }
@@ -18,7 +13,7 @@ namespace _04_interactions_framework.Modules
private CommandHandler _handler;
// Constructor injection is also a valid way to access the dependecies
public UtilityModule ( CommandHandler handler )
public GeneralModule(CommandHandler handler)
{
_handler = handler;
}
@@ -65,7 +60,7 @@ namespace _04_interactions_framework.Modules
// Message Commands can only have one parameter, which must be a type of SocketMessage
[MessageCommand("Delete")]
[RequireOwner]
[Attributes.RequireOwner]
public async Task DeleteMesage(IMessage message)
{
await message.DeleteAsync();

View File

@@ -0,0 +1,30 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System.Threading.Tasks;
namespace InteractionFramework.Modules
{
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
internal class MessageCommandModule : InteractionModuleBase<SocketInteractionContext<SocketMessageCommand>>
{
// Pins a message in the channel it is in.
[MessageCommand("pin")]
public async Task PinMessageAsync(IMessage message)
{
// make a safety cast to check if the message is ISystem- or IUserMessage
if (message is not IUserMessage userMessage)
await RespondAsync(text: ":x: You cant pin system messages!");
// if the pins in this channel are equal to or above 50, no more messages can be pinned.
else if ((await Context.Channel.GetPinnedMessagesAsync()).Count >= 50)
await RespondAsync(text: ":x: You cant pin any more messages, the max has already been reached in this channel!");
else
{
await userMessage.PinAsync();
await RespondAsync(":white_check_mark: Successfully pinned message!");
}
}
}
}

View File

@@ -0,0 +1,51 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System;
using System.Threading.Tasks;
namespace InteractionFramework.Modules
{
public enum Hobby
{
Gaming,
Art,
Reading
}
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
class SlashCommandModule : InteractionModuleBase<SocketInteractionContext<SocketSlashCommand>>
{
// Will be called before execution. Here you can populate several entities you may want to retrieve before executing a command.
// I.E. database objects
public override void BeforeExecute(ICommandInfo command)
{
// Anything
throw new NotImplementedException();
}
// Will be called after execution
public override void AfterExecute(ICommandInfo command)
{
// Anything
throw new NotImplementedException();
}
[SlashCommand("ping", "Pings the bot and returns its latency.")]
public async Task GreetUserAsync()
=> await RespondAsync(text: $":ping_pong: It took me {Context.Client.Latency}ms to respond to you!", ephemeral: true);
[SlashCommand("hobby", "Choose your hobby from the list!")]
public async Task ChooseAsync(Hobby hobby)
=> await RespondAsync(text: $":thumbsup: Your hobby is: {hobby}.");
[SlashCommand("bitrate", "Gets the bitrate of a specific voice channel.")]
public async Task GetBitrateAsync([ChannelTypes(ChannelType.Voice, ChannelType.Stage)] IChannel channel)
{
var voiceChannel = channel as IVoiceChannel;
await RespondAsync(text: $"This voice channel has a bitrate of {voiceChannel.Bitrate}");
}
}
}

View File

@@ -0,0 +1,17 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System.Threading.Tasks;
namespace InteractionFramework.Modules
{
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
class UserCommandModule : InteractionModuleBase<SocketInteractionContext<SocketUserCommand>>
{
// This command will greet target user in the channel this was executed in.
[UserCommand("greet")]
public async Task GreetUserAsync(IUser user)
=> await RespondAsync(text: $":wave: {Context.User} said hi to you, <@{user.Id}>!");
}
}

View File

@@ -4,14 +4,14 @@ using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace _04_interactions_framework
namespace InteractionFramework
{
class Program
{
// Entry point of the program.
static void Main ( string[] args )
{
// One of the more flexable ways to access the configuration data is to use the Microsoft's Configuration model,
@@ -24,7 +24,7 @@ namespace _04_interactions_framework
RunAsync(config).GetAwaiter().GetResult();
}
static async Task RunAsync (IConfiguration configuration )
static async Task RunAsync (IConfiguration configuration)
{
// Dependency injection is a key part of the Interactions framework but it needs to be disposed at the end of the app's lifetime.
using var services = ConfigureServices(configuration);
@@ -64,14 +64,12 @@ namespace _04_interactions_framework
}
static ServiceProvider ConfigureServices ( IConfiguration configuration )
{
return new ServiceCollection()
=> new ServiceCollection()
.AddSingleton(configuration)
.AddSingleton<DiscordSocketClient>()
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()))
.AddSingleton<CommandHandler>()
.BuildServiceProvider();
}
static bool IsDebug ( )
{

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_04_interactions_framework</RootNamespace>
<RootNamespace>InteractionFramework</RootNamespace>
<StartupObject></StartupObject>
</PropertyGroup>

View File

@@ -0,0 +1,18 @@
using Discord.Interactions;
using Discord.WebSocket;
using System.Threading.Tasks;
namespace ShardedClient.Modules
{
// A display of portability, which shows how minimal the difference between the 2 frameworks is.
public class InteractionModule : InteractionModuleBase<ShardedInteractionContext<SocketSlashCommand>>
{
[SlashCommand("info", "Information about this shard.")]
public async Task InfoAsync()
{
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards!
This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}";
await RespondAsync(msg);
}
}
}

View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Discord.Commands;
using System.Threading.Tasks;
namespace _03_sharded_client.Modules
namespace ShardedClient.Modules
{
// Remember to make your module reference the ShardedCommandContext
public class PublicModule : ModuleBase<ShardedCommandContext>

View File

@@ -1,13 +1,14 @@
using Discord;
using Discord.Commands;
using Discord.Interactions;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using ShardedClient.Services;
using System;
using System.Threading;
using System.Threading.Tasks;
using _03_sharded_client.Services;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
namespace _03_sharded_client
namespace ShardedClient
{
// This is a minimal example of using Discord.Net's Sharded Client
// The provided DiscordShardedClient class simplifies having multiple
@@ -15,7 +16,11 @@ namespace _03_sharded_client
class Program
{
static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
=> new Program()
.MainAsync()
.GetAwaiter()
.GetResult();
public async Task MainAsync()
{
// You specify the amount of shards you'd like to have with the
@@ -40,6 +45,7 @@ namespace _03_sharded_client
client.ShardReady += ReadyAsync;
client.Log += LogAsync;
await services.GetRequiredService<InteractionHandlingService>().InitializeAsync();
await services.GetRequiredService<CommandHandlingService>().InitializeAsync();
// Tokens should be considered secret data, and never hard-coded.
@@ -51,13 +57,13 @@ namespace _03_sharded_client
}
private ServiceProvider ConfigureServices(DiscordSocketConfig config)
{
return new ServiceCollection()
=> new ServiceCollection()
.AddSingleton(new DiscordShardedClient(config))
.AddSingleton<CommandService>()
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordShardedClient>()))
.AddSingleton<CommandHandlingService>()
.AddSingleton<InteractionHandlingService>()
.BuildServiceProvider();
}
private Task ReadyAsync(DiscordSocketClient shard)

View File

@@ -1,12 +1,12 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace _03_sharded_client.Services
namespace ShardedClient.Services
{
public class CommandHandlingService
{
@@ -33,7 +33,7 @@ namespace _03_sharded_client.Services
public async Task MessageReceivedAsync(SocketMessage rawMessage)
{
// Ignore system messages, or messages from other bots
if (!(rawMessage is SocketUserMessage message))
if (rawMessage is not SocketUserMessage message)
return;
if (message.Source != MessageSource.User)
return;
@@ -59,7 +59,7 @@ namespace _03_sharded_client.Services
return;
// the command failed, let's notify the user that something happened.
await context.Channel.SendMessageAsync($"error: {result.ToString()}");
await context.Channel.SendMessageAsync($"error: {result}");
}
private Task LogAsync(LogMessage log)

View File

@@ -0,0 +1,57 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ShardedClient.Services
{
public class InteractionHandlingService
{
private readonly InteractionService _service;
private readonly DiscordShardedClient _client;
private readonly IServiceProvider _provider;
public InteractionHandlingService(IServiceProvider services)
{
_service = services.GetRequiredService<InteractionService>();
_client = services.GetRequiredService<DiscordShardedClient>();
_provider = services;
_service.Log += LogAsync;
_client.InteractionCreated += OnInteractionAsync;
// For examples on how to handle post execution,
// see the InteractionFramework samples.
}
// Register all modules, and add the commands from these modules to either guild or globally depending on the build state.
public async Task InitializeAsync()
{
await _service.AddModulesAsync(typeof(InteractionHandlingService).Assembly, _provider);
#if DEBUG
await _service.AddCommandsToGuildAsync(_client.Guilds.First(x => x.Id == 1));
#else
await _service.AddCommandsGloballyAsync();
#endif
}
private async Task OnInteractionAsync(SocketInteraction interaction)
{
_ = Task.Run(async () =>
{
var context = new ShardedInteractionContext(_client, interaction);
await _service.ExecuteCommandAsync(context, _provider);
});
await Task.CompletedTask;
}
private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
}
}

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>ShardedClient</RootNamespace>
</PropertyGroup>
<ItemGroup>
@@ -11,6 +12,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Discord.Net.Commands\Discord.Net.Commands.csproj" />
<ProjectReference Include="..\..\src\Discord.Net.Interactions\Discord.Net.Interactions.csproj" />
<ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
</ItemGroup>

View File

@@ -1,10 +1,10 @@
using System.IO;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using _02_commands_framework.Services;
using System.IO;
using System.Threading.Tasks;
using TextCommandFramework.Services;
namespace _02_commands_framework.Modules
namespace TextCommandFramework.Modules
{
// Modules must be public and inherit from an IModuleBase
public class PublicModule : ModuleBase<SocketCommandContext>

View File

@@ -1,14 +1,14 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
using _02_commands_framework.Services;
using TextCommandFramework.Services;
namespace _02_commands_framework
namespace TextCommandFramework
{
// This is a minimal example of using Discord.Net's command
// framework - by no means does it show everything the framework

View File

@@ -1,12 +1,12 @@
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using System.Threading.Tasks;
namespace _02_commands_framework.Services
namespace TextCommandFramework.Services
{
public class CommandHandlingService
{
@@ -36,21 +36,24 @@ namespace _02_commands_framework.Services
public async Task MessageReceivedAsync(SocketMessage rawMessage)
{
// Ignore system messages, or messages from other bots
if (!(rawMessage is SocketUserMessage message)) return;
if (message.Source != MessageSource.User) return;
if (!(rawMessage is SocketUserMessage message))
return;
if (message.Source != MessageSource.User)
return;
// This value holds the offset where the prefix ends
var argPos = 0;
// Perform prefix check. You may want to replace this with
// (!message.HasCharPrefix('!', ref argPos))
// for a more traditional command format like !help.
if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos)) return;
if (!message.HasMentionPrefix(_discord.CurrentUser, ref argPos))
return;
var context = new SocketCommandContext(_discord, message);
// Perform the execution of the command. In this method,
// the command service will perform precondition and parsing check
// then execute the command if one is matched.
await _commands.ExecuteAsync(context, argPos, _services);
await _commands.ExecuteAsync(context, argPos, _services);
// Note that normally a result will be returned by this format, but here
// we will handle the result in CommandExecutedAsync,
}

View File

@@ -2,7 +2,7 @@ using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace _02_commands_framework.Services
namespace TextCommandFramework.Services
{
public class PictureService
{

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_03_sharded_client</RootNamespace>
<RootNamespace>TextCommandFramework</RootNamespace>
</PropertyGroup>
<ItemGroup>

View File

@@ -2,7 +2,7 @@ using Discord;
using Discord.Webhook;
using System.Threading.Tasks;
namespace _04_webhook_client
namespace WebHookClient
{
// This is a minimal example of using Discord.Net's Webhook Client
// Webhooks are send-only components of Discord that allow you to make a POST request

View File

@@ -2,8 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>_04_webhook_client</RootNamespace>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>WebHookClient</RootNamespace>
</PropertyGroup>
<ItemGroup>

74
samples/_idn/Inspector.cs Normal file
View File

@@ -0,0 +1,74 @@
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Idn
{
public static class Inspector
{
public static string Inspect(object value)
{
var builder = new StringBuilder();
if (value != null)
{
var type = value.GetType().GetTypeInfo();
builder.AppendLine($"[{type.Namespace}.{type.Name}]");
builder.AppendLine($"{InspectProperty(value)}");
if (value is IEnumerable)
{
var items = (value as IEnumerable).Cast<object>().ToArray();
if (items.Length > 0)
{
builder.AppendLine();
foreach (var item in items)
builder.AppendLine($"- {InspectProperty(item)}");
}
}
else
{
var groups = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.GetIndexParameters().Length == 0)
.GroupBy(x => x.Name)
.OrderBy(x => x.Key)
.ToArray();
if (groups.Length > 0)
{
builder.AppendLine();
int pad = groups.Max(x => x.Key.Length) + 1;
foreach (var group in groups)
builder.AppendLine($"{group.Key.PadRight(pad, ' ')}{InspectProperty(group.First().GetValue(value))}");
}
}
}
else
builder.AppendLine("null");
return builder.ToString();
}
private static string InspectProperty(object obj)
{
if (obj == null)
return "null";
var type = obj.GetType();
var debuggerDisplay = type.GetProperty("DebuggerDisplay", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (debuggerDisplay != null)
return debuggerDisplay.GetValue(obj).ToString();
var toString = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(x => x.Name == "ToString" && x.DeclaringType != typeof(object))
.FirstOrDefault();
if (toString != null)
return obj.ToString();
var count = type.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (count != null)
return $"[{count.GetValue(obj)} Items]";
return obj.ToString();
}
}
}

152
samples/_idn/Program.cs Normal file
View File

@@ -0,0 +1,152 @@
using Discord;
using Discord.WebSocket;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Idn
{
public class Program
{
public static readonly string[] Imports =
{
"System",
"System.Collections.Generic",
"System.Linq",
"System.Threading.Tasks",
"System.Diagnostics",
"System.IO",
"Discord",
"Discord.Rest",
"Discord.WebSocket",
"idn"
};
static async Task Main(string[] args)
{
var token = File.ReadAllText("token.ignore");
var client = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = LogSeverity.Debug });
var logQueue = new ConcurrentQueue<LogMessage>();
var logCancelToken = new CancellationTokenSource();
int presenceUpdates = 0;
client.Log += msg =>
{
logQueue.Enqueue(msg);
return Task.CompletedTask;
};
Console.CancelKeyPress += (_ev, _s) =>
{
logCancelToken.Cancel();
};
var logTask = Task.Run(async () =>
{
var fs = new FileStream("idn.log", FileMode.Append);
var logStringBuilder = new StringBuilder(200);
string logString = "";
byte[] helloBytes = Encoding.UTF8.GetBytes($"### new log session: {DateTime.Now} ###\n\n");
await fs.WriteAsync(helloBytes);
while (!logCancelToken.IsCancellationRequested)
{
if (logQueue.TryDequeue(out var msg))
{
if (msg.Message?.IndexOf("PRESENCE_UPDATE)") > 0)
{
presenceUpdates++;
continue;
}
_ = msg.ToString(builder: logStringBuilder);
logStringBuilder.AppendLine();
logString = logStringBuilder.ToString();
Debug.Write(logString, "DNET");
await fs.WriteAsync(Encoding.UTF8.GetBytes(logString));
}
await fs.FlushAsync();
try
{
await Task.Delay(100, logCancelToken.Token);
}
finally { }
}
byte[] goodbyeBytes = Encoding.UTF8.GetBytes($"#!! end log session: {DateTime.Now} !!#\n\n\n");
await fs.WriteAsync(goodbyeBytes);
await fs.DisposeAsync();
});
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
var options = ScriptOptions.Default
.AddReferences(GetAssemblies().ToArray())
.AddImports(Imports);
var globals = new ScriptGlobals
{
Client = client,
PUCount = -1,
};
while (true)
{
Console.Write("> ");
string input = Console.ReadLine();
if (input == "quit!")
{
break;
}
object eval;
try
{
globals.PUCount = presenceUpdates;
eval = await CSharpScript.EvaluateAsync(input, options, globals);
}
catch (Exception e)
{
eval = e;
}
Console.WriteLine(Inspector.Inspect(eval));
}
await client.StopAsync();
client.Dispose();
logCancelToken.Cancel();
try
{ await logTask; }
finally { Console.WriteLine("goodbye!"); }
}
static IEnumerable<Assembly> GetAssemblies()
{
var Assemblies = Assembly.GetEntryAssembly().GetReferencedAssemblies();
foreach (var a in Assemblies)
{
var asm = Assembly.Load(a);
yield return asm;
}
yield return Assembly.GetEntryAssembly();
}
public class ScriptGlobals
{
public DiscordSocketClient Client { get; set; }
public int PUCount { get; set; }
}
}
}

16
samples/_idn/idn.csproj Normal file
View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.11.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
</ItemGroup>
</Project>

1
samples/_idn/logview.ps1 Normal file
View File

@@ -0,0 +1 @@
Get-Content .\bin\Debug\netcoreapp3.1\idn.log -Tail 3 -Wait

View File

@@ -1,17 +1,17 @@
using Discord;
using Discord.WebSocket;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Discord;
using Discord.WebSocket;
using System.Collections.Concurrent;
using System.Threading;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace Idn
{