public override async ValueTask <TypeParserResult <RestUserMessage> > ParseAsync(Parameter parameter, string value, CommandContext ctx) { var context = (AdminCommandContext)ctx; var match = JumpLinkRegex.Match(value); if (match.Success && Snowflake.TryParse(match.Groups[2].Value, out var channelId) && Snowflake.TryParse(match.Groups[3].Value, out var messageId) && context.Guild.GetTextChannel(channelId) is ITextChannel channel) { return(!(await channel.GetMessageAsync(messageId) is RestUserMessage message) ? TypeParserResult <RestUserMessage> .Unsuccessful(context.Localize("jumplinkparser_nomessage")) : TypeParserResult <RestUserMessage> .Successful(message)); } return(TypeParserResult <RestUserMessage> .Unsuccessful(context.Localize("jumplinkparser_format"))); }
/// <inheritdoc /> public override async ValueTask <RetrieveEntityResult <IUser> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var userID)) { return(RetrieveEntityResult <IUser> .FromError($"Failed to parse \"{value}\" as a user ID.")); } var getUser = await _userAPI.GetUserAsync(userID.Value, ct); if (!getUser.IsSuccess) { return(RetrieveEntityResult <IUser> .FromError(getUser)); } return(getUser.IsSuccess ? RetrieveEntityResult <IUser> .FromSuccess(getUser.Entity) : RetrieveEntityResult <IUser> .FromError("No user with that ID could be found.")); }
public override ValueTask <TypeParserResult <TUser> > ParseAsync(Parameter parameter, string value, CommandContext ctx) { var context = (AdminCommandContext)ctx; if (context.IsPrivate) { return(TypeParserResult <TUser> .Unsuccessful(context.Localize("requirecontext_guild"))); } TUser user = null; // Parse by ID or mention if (Snowflake.TryParse(value, out var id) || Discord.TryParseUserMention(value, out id)) { user = context.Guild.GetMember(id) as TUser; } // Parse by user#discrim if (user is null) { user = context.Guild.Members.Values.FirstOrDefault(x => x.Tag.Equals(value, StringComparison.OrdinalIgnoreCase)) as TUser; } // Parse by exact username/nickname match if (user is null) { var matches = context.Guild.Members.Values.Where(x => x.Name?.Equals(value, StringComparison.OrdinalIgnoreCase) == true || x.Nick?.Equals(value, StringComparison.OrdinalIgnoreCase) == true) .ToList(); if (matches.Count > 1) { return(TypeParserResult <TUser> .Unsuccessful(context.Localize("userparser_multiple"))); } user = matches.FirstOrDefault() as TUser; } return(!(user is null) ? TypeParserResult <TUser> .Successful(user) : TypeParserResult <TUser> .Unsuccessful(context.Localize("userparser_notfound"))); }
public override ValueTask <TypeParserResult <CachedRole> > ParseAsync(Parameter parameter, string value, RiasCommandContext context) { var localization = context.ServiceProvider.GetRequiredService <Localization>(); if (context.Guild is null) { return(TypeParserResult <CachedRole> .Unsuccessful(localization.GetText(context.Guild?.Id, Localization.TypeParserCachedRoleNotGuild))); } CachedRole role; if (Discord.TryParseRoleMention(value, out var roleId)) { role = context.Guild.GetRole(roleId); if (role != null) { return(TypeParserResult <CachedRole> .Successful(role)); } } if (Snowflake.TryParse(value, out var id)) { role = context.Guild.GetRole(id); if (role != null) { return(TypeParserResult <CachedRole> .Successful(role)); } } role = context.Guild.Roles.FirstOrDefault(x => string.Equals(x.Value.Name, value, StringComparison.OrdinalIgnoreCase)).Value; if (role != null) { return(TypeParserResult <CachedRole> .Successful(role)); } if (parameter.IsOptional) { return(TypeParserResult <CachedRole> .Successful((CachedRole)parameter.DefaultValue)); } return(TypeParserResult <CachedRole> .Unsuccessful(localization.GetText(context.Guild?.Id, Localization.AdministrationRoleNotFound))); }
public override ValueTask <TypeParserResult <CachedTextChannel> > ParseAsync(Parameter parameter, string value, RiasCommandContext context) { var localization = context.ServiceProvider.GetRequiredService <Localization>(); if (context.Guild is null) { return(TypeParserResult <CachedTextChannel> .Unsuccessful(localization.GetText(context.Guild?.Id, Localization.TypeParserCachedTextChannelNotGuild))); } CachedTextChannel channel; if (Discord.TryParseChannelMention(value, out var channelId)) { channel = context.Guild.GetTextChannel(channelId); if (channel != null) { return(TypeParserResult <CachedTextChannel> .Successful(channel)); } } if (Snowflake.TryParse(value, out var id)) { channel = context.Guild.GetTextChannel(id); if (channel != null) { return(TypeParserResult <CachedTextChannel> .Successful(channel)); } } channel = context.Guild.TextChannels.FirstOrDefault(c => string.Equals(c.Value.Name, value, StringComparison.OrdinalIgnoreCase)).Value; if (channel != null) { return(TypeParserResult <CachedTextChannel> .Successful(channel)); } if (parameter.IsOptional) { return(TypeParserResult <CachedTextChannel> .Successful((CachedTextChannel)parameter.DefaultValue)); } return(TypeParserResult <CachedTextChannel> .Unsuccessful(localization.GetText(context.Guild?.Id, Localization.AdministrationTextChannelNotFound))); }
private void btnSnowflake_Click(Object sender, EventArgs e) { var v = rtSource.Text.ToLong(); if (v <= 0) { return; } var snow = new Snowflake(); // 指定基准时间 if (!rtPass.Text.IsNullOrEmpty()) { var baseTime = rtPass.Text.ToDateTime(); if (baseTime.Year > 1000) { snow.StartTimestamp = baseTime; } } // 计算结果 { if (!snow.TryParse(v, out var time, out var workerId, out var sequence)) { throw new Exception("解码失败!"); } // 初始化一次,用于获取本机workerId snow.NewId(); var t = (Int64)(time - snow.StartTimestamp).TotalMilliseconds; SetResult( $"十六:{v:X16}", $"编码:{(t << 2):X8} {workerId:X2} {sequence:X3}", $"基准:{snow.StartTimestamp:yyyy-MM-dd}", $"时间:{time.ToFullString()} ({t} / {(t << 22):X8})", $"节点:{workerId} ({workerId:X4})", $"序号:{sequence} ({sequence:X4})", $"本机:{snow.WorkerId} ({snow.WorkerId:X4})"); } }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) { Result checkSlashSupport = _slashService.SupportsSlashCommands(); if (!checkSlashSupport.IsSuccess) { LogSlashCommandsNotSupported(checkSlashSupport.Error.Message); return; } _ = Snowflake.TryParse(_options.DiscordDevServerId, out Snowflake? guild); Result updateSlash = await _slashService.UpdateSlashCommandsAsync(guild, null, stoppingToken); if (!updateSlash.IsSuccess) { LogSlashCommandsUpdateError(updateSlash.Error.Message); return; } Result runResult = await _gatewayClient.RunAsync(stoppingToken); if (!runResult.IsSuccess && !stoppingToken.IsCancellationRequested) { switch (runResult.Error) { case ExceptionError exe: LogGatewayConnectionException(exe.Exception, exe.Message); break; case GatewayWebSocketError: case GatewayDiscordError: LogGatewayError(runResult.Error.Message); break; default: LogUnknownError(runResult.Error.Message); break; } } }
/// <inheritdoc /> public override async ValueTask <Result <IRole> > TryParse(string value, CancellationToken ct) { var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(Result <IRole> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(new InvalidOperationError("You're not in a guild channel, so I can't get any roles.")); } var getRoles = await _guildAPI.GetGuildRolesAsync(channel.GuildID.Value, ct); if (!getRoles.IsSuccess) { return(Result <IRole> .FromError(getRoles)); } var roles = getRoles.Entity; if (!Snowflake.TryParse(value.Unmention(), out var roleID)) { // Try a name-based lookup var roleByName = roles.FirstOrDefault(r => r.Name.Equals(value, StringComparison.OrdinalIgnoreCase)); return(roleByName is not null ? Result <IRole> .FromSuccess(roleByName) : new ParsingError <IRole>(value.Unmention())); } var role = roles.FirstOrDefault(r => r.ID.Equals(roleID)); return(role is not null ? Result <IRole> .FromSuccess(role) : new ParsingError <IRole>("No role with that ID could be found.")); }
/// <inheritdoc /> public override async ValueTask <RetrieveEntityResult <IRole> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var roleID)) { return(RetrieveEntityResult <IRole> .FromError($"Failed to parse \"{value}\" as a role ID.")); } var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(RetrieveEntityResult <IRole> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(RetrieveEntityResult <IRole> .FromError ( "You're not in a guild channel, so I can't get any roles." )); } var getRoles = await _guildAPI.GetGuildRolesAsync(channel.GuildID.Value, ct); if (!getRoles.IsSuccess) { return(RetrieveEntityResult <IRole> .FromError(getRoles)); } var roles = getRoles.Entity; var role = roles.FirstOrDefault(r => r.ID.Equals(roleID)); return(role is not null ? RetrieveEntityResult <IRole> .FromSuccess(role) : RetrieveEntityResult <IRole> .FromError("No role with that ID could be found.")); }
/// <inheritdoc /> public override async ValueTask <RetrieveEntityResult <IGuildMember> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var guildMemberID)) { return(RetrieveEntityResult <IGuildMember> .FromError ( $"Failed to parse \"{value}\" as a guild member ID." )); } var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(RetrieveEntityResult <IGuildMember> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(RetrieveEntityResult <IGuildMember> .FromError ( "You're not in a guild channel, so I can't get any guild members." )); } var getGuildMember = await _guildAPI.GetGuildMemberAsync(channel.GuildID.Value, guildMemberID.Value, ct); if (!getGuildMember.IsSuccess) { return(RetrieveEntityResult <IGuildMember> .FromError(getGuildMember)); } return(getGuildMember.IsSuccess ? RetrieveEntityResult <IGuildMember> .FromSuccess(getGuildMember.Entity) : RetrieveEntityResult <IGuildMember> .FromError("No guild member with that ID could be found.")); }
public override async ValueTask <TypeParserResult <IMember> > ParseAsync( Parameter parameter, string value, CommandContext context) { if (!(context is DiscordCommandContext ctx)) { return(TypeParserResult <IMember> .Unsuccessful("Context not of of type " + typeof(DiscordCommandContext) + " found " + context.GetType().Name)); } if (ctx.Guild == null) { return(TypeParserResult <IMember> .Unsuccessful("Context not within guild")); } // Attempt pulling member from cache before requesting rest member info var cachedResult = await _CachedMemberParser.ParseAsync(parameter, value, context); if (cachedResult.IsSuccessful) { return(TypeParserResult <IMember> .Successful(cachedResult.Value)); } // Since cache parser had no hit, parse user id and request directly if (!Discord.TryParseUserMention(value, out var id) && !Snowflake.TryParse(value, out id)) { return(TypeParserResult <IMember> .Unsuccessful("Member snowflake not able to be parsed")); } var member = await ctx.Guild.GetMemberAsync(id); if (member == null) { return(TypeParserResult <IMember> .Unsuccessful("Member not found")); } return(TypeParserResult <IMember> .Successful(member)); }
protected override MarkdownNode VisitMention(MentionNode mention) { var mentionId = Snowflake.TryParse(mention.Id); if (mention.Kind == MentionKind.Meta) { _buffer.Append($"@{mention.Id}"); } else if (mention.Kind == MentionKind.User) { var member = mentionId?.Pipe(_context.TryGetMember); var name = member?.User.Name ?? "Unknown"; _buffer.Append($"@{name}"); } else if (mention.Kind == MentionKind.Channel) { var channel = mentionId?.Pipe(_context.TryGetChannel); var name = channel?.Name ?? "deleted-channel"; _buffer.Append($"#{name}"); // Voice channel marker if (channel?.IsVoiceChannel == true) { _buffer.Append(" [voice]"); } } else if (mention.Kind == MentionKind.Role) { var role = mentionId?.Pipe(_context.TryGetRole); var name = role?.Name ?? "deleted-role"; _buffer.Append($"@{name}"); } return(base.VisitMention(mention)); }
public override async ValueTask <TypeParserResult <IMember> > ParseAsync( Parameter parameter, string value, EspeonCommandContext context) { var cachedResult = await this._cachedParser.ParseAsync(parameter, value, context); if (cachedResult.IsSuccessful) { return(TypeParserResult <IMember> .Successful(cachedResult.Value)); } if (!Disqord.Discord.TryParseUserMention(value, out var id) && !Snowflake.TryParse(value, out id)) { return(new EspeonTypeParserFailedResult <IMember>(MEMBER_NOT_IN_CACHE_NO_MENTION)); } var member = await context.Guild.GetMemberAsync(id); return(member is null ? new EspeonTypeParserFailedResult <IMember>(MEMBER_NOT_IN_GUILD) : TypeParserResult <IMember> .Successful(member)); }
/// <inheritdoc /> public override async ValueTask <Result <IGuildMember> > TryParse(string value, CancellationToken ct) { if (!Snowflake.TryParse(value.Unmention(), out var guildMemberID)) { return(new ParsingError <IGuildMember>(value)); } var getChannel = await _channelAPI.GetChannelAsync(_context.ChannelID, ct); if (!getChannel.IsSuccess) { return(Result <IGuildMember> .FromError(getChannel)); } var channel = getChannel.Entity; if (!channel.GuildID.HasValue) { return(new InvalidOperationError("You're not in a guild channel, so I can't get any guild members.")); } return(await _guildAPI.GetGuildMemberAsync(channel.GuildID.Value, guildMemberID.Value, ct)); }
/// <inheritdoc/> public bool TryFind(IGatewayUserMessage message, out string output) { var contentSpan = message.Content.AsSpan(); if (contentSpan.Length > 17 && contentSpan[0] == '<' && contentSpan[1] == '@') { var closingBracketIndex = contentSpan.IndexOf('>'); if (closingBracketIndex != -1) { var idSpan = contentSpan[2] == '!' ? contentSpan.Slice(3, closingBracketIndex - 3) : contentSpan.Slice(2, closingBracketIndex - 2); if (Snowflake.TryParse(idSpan, out var id) && id == UserId) { output = new string(contentSpan.Slice(closingBracketIndex + 1)); return(true); } } } output = null; return(false); }
public async Task LeaveGuildAsync(string name) { var guild = Snowflake.TryParse(name, out var guildId) ? RiasBot.GetGuild(guildId) : RiasBot.Guilds.FirstOrDefault(x => string.Equals(x.Value.Name, name)).Value; if (guild is null) { await ReplyErrorAsync(Localization.BotGuildNotFound); return; } var embed = new LocalEmbedBuilder() { Color = RiasUtilities.ConfirmColor, Description = GetText(Localization.BotLeftGuild, guild.Name) }.AddField(GetText(Localization.CommonId), guild.Id, true).AddField(GetText(Localization.CommonUsers), guild.MemberCount, true); await ReplyAsync(embed); await guild.LeaveAsync(); }
public override ValueTask <TypeParserResult <CachedMember> > ParseAsync(Parameter parameter, string value, RiasCommandContext context) { var localization = context.ServiceProvider.GetRequiredService <Localization>(); if (context.Guild is null) { return(TypeParserResult <CachedMember> .Unsuccessful(localization.GetText(context.Guild?.Id, Localization.TypeParserCachedMemberNotGuild))); } CachedMember member; if (Discord.TryParseUserMention(value, out var userId)) { member = context.Guild.GetMember(userId); if (member != null) { return(TypeParserResult <CachedMember> .Successful(member)); } } if (Snowflake.TryParse(value, out var id)) { member = context.Guild.GetMember(id); if (member != null) { return(TypeParserResult <CachedMember> .Successful(member)); } } var members = context.Guild.Members; var index = value.LastIndexOf("#", StringComparison.Ordinal); if (index > 0) { var username = value[..index];
/// <summary> /// The main entrypoint of the program. /// </summary> /// <param name="args">The command-line arguments.</param> /// <returns>A <see cref="Task"/> representing the asynchronous program execution.</returns> public static async Task Main(string[] args) { var cancellationSource = new CancellationTokenSource(); Console.CancelKeyPress += (_, eventArgs) => { eventArgs.Cancel = true; cancellationSource.Cancel(); }; var botToken = Environment.GetEnvironmentVariable("REMORA_BOT_TOKEN") ?? throw new InvalidOperationException ( "No bot token has been provided. Set the REMORA_BOT_TOKEN environment variable to a valid token." ); var serviceCollection = new ServiceCollection() .AddLogging ( c => c .AddConsole() .AddFilter("System.Net.Http.HttpClient.*.LogicalHandler", LogLevel.Warning) .AddFilter("System.Net.Http.HttpClient.*.ClientHandler", LogLevel.Warning) ) .AddDiscordGateway(_ => botToken) .AddDiscordCommands(true) .AddCommandGroup <HttpCatCommands>() .AddDiscordCaching(); serviceCollection.AddHttpClient(); var services = serviceCollection.BuildServiceProvider(true); var log = services.GetRequiredService <ILogger <Program> >(); Snowflake?debugServer = null; #if DEBUG var debugServerString = Environment.GetEnvironmentVariable("REMORA_DEBUG_SERVER"); if (debugServerString is not null) { if (!Snowflake.TryParse(debugServerString, out debugServer)) { log.LogWarning("Failed to parse debug server from environment"); } } #endif var slashService = services.GetRequiredService <SlashService>(); var checkSlashSupport = slashService.SupportsSlashCommands(); if (!checkSlashSupport.IsSuccess) { log.LogWarning ( "The registered commands of the bot don't support slash commands: {Reason}", checkSlashSupport.Unwrap().Message ); } else { var updateSlash = await slashService.UpdateSlashCommandsAsync(debugServer, cancellationSource.Token); if (!updateSlash.IsSuccess) { log.LogWarning("Failed to update slash commands: {Reason}", updateSlash.Unwrap().Message); } } var gatewayClient = services.GetRequiredService <DiscordGatewayClient>(); var runResult = await gatewayClient.RunAsync(cancellationSource.Token); if (!runResult.IsSuccess) { switch (runResult.Error) { case ExceptionError exe: { log.LogError ( exe.Exception, "Exception during gateway connection: {ExceptionMessage}", exe.Message ); break; } case GatewayWebSocketError: case GatewayDiscordError: { log.LogError("Gateway error: {Message}", runResult.Unwrap().Message); break; } default: { log.LogError("Unknown error: {Message}", runResult.Unwrap().Message); break; } } } log.LogInformation("Bye bye"); }
/// <summary> /// The main entry point of the program. /// </summary> /// <returns>A task.</returns> public static async Task Main() { var cancellationSource = new CancellationTokenSource(); Console.CancelKeyPress += (_, eventArgs) => { eventArgs.Cancel = true; cancellationSource.Cancel(); }; // Configure logging const string configurationName = "DIGOS.Ambassador.log4net.config"; var logConfig = new XmlDocument(); await using (var configStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(configurationName)) { if (configStream is null) { throw new InvalidOperationException("The log4net configuration stream could not be found."); } logConfig.Load(configStream); } var repo = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(Hierarchy)); XmlConfigurator.Configure(repo, logConfig["log4net"]); var contentFileSystem = FileSystemFactory.CreateContentFileSystem(); var contentService = new ContentService(contentFileSystem); var getBotToken = await contentService.GetBotTokenAsync(); if (!getBotToken.IsSuccess) { throw new InvalidOperationException("No bot token available."); } var token = getBotToken.Entity.Trim(); var options = Options.Create(new PluginServiceOptions(Array.Empty <string>())); var pluginService = new PluginService(options); var plugins = pluginService.LoadPluginTree(); var hostBuilder = Host.CreateDefaultBuilder() .AddDiscordService(_ => token) .UseSystemd() .ConfigureServices(services => { services.Configure <DiscordGatewayClientOptions>(o => { o.Intents |= GatewayIntents.MessageContents; }); services.Configure <ServiceProviderOptions>(s => { s.ValidateScopes = true; s.ValidateOnBuild = true; }); services.Configure <CommandResponderOptions>(o => o.Prefix = "!"); services.AddSingleton <BehaviourService>(); services .AddSingleton(pluginService) .AddSingleton(contentService) .AddSingleton(contentFileSystem) .AddSingleton <Random>(); services .AddDiscordCommands(true) .AddDiscordCaching(); // Configure cache times services.Configure <CacheSettings>(settings => { settings.SetAbsoluteExpiration <IGuildMember>(TimeSpan.FromDays(1)); settings.SetAbsoluteExpiration <IMessage>(TimeSpan.FromDays(1)); }); // Set up the feedback theme var theme = (FeedbackTheme) FeedbackTheme.DiscordDark with { Secondary = Color.MediumPurple }; services.AddSingleton <IFeedbackTheme>(theme); // Add execution events services .AddPreExecutionEvent <ConsentCheckingPreExecutionEvent>() .AddPostExecutionEvent <MessageRelayingPostExecutionEvent>(); // Ensure we're automatically joining created threads services.AddResponder <ThreadJoinResponder>(); // Override the default responders services.Replace(ServiceDescriptor.Scoped <CommandResponder, AmbassadorCommandResponder>()); services.Replace(ServiceDescriptor.Scoped <InteractionResponder, AmbassadorInteractionResponder>()); services .AddParser <MessageReader>() .AddParser <HumanTimeSpanReader>(); var configurePlugins = plugins.ConfigureServices(services); if (!configurePlugins.IsSuccess) { throw new InvalidOperationException(); } }) .ConfigureLogging(l => { l.ClearProviders(); l.AddLog4Net() .AddFilter("Microsoft.EntityFrameworkCore.Infrastructure", LogLevel.Warning) .AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Critical) .AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Warning) .AddFilter("Microsoft.EntityFrameworkCore.Update", LogLevel.Critical); }); var host = hostBuilder.Build(); var hostServices = host.Services; var log = hostServices.GetRequiredService <ILogger <Program> >(); log.LogInformation("Running on {Framework}", RuntimeInformation.FrameworkDescription); Snowflake?debugServer = null; var debugServerString = Environment.GetEnvironmentVariable("REMORA_DEBUG_SERVER"); if (debugServerString is not null) { if (!Snowflake.TryParse(debugServerString, out debugServer)) { log.LogWarning("Failed to parse debug server from environment"); } } var slashService = hostServices.GetRequiredService <SlashService>(); var checkSlashSupport = slashService.SupportsSlashCommands(); if (!checkSlashSupport.IsSuccess) { var error = checkSlashSupport.Error; if (error is UnsupportedFeatureError ufe) { var location = ufe.Node is not null ? GetCommandLocation(ufe.Node) : "unknown"; log.LogWarning ( "The registered commands of the bot don't support slash commands: {Reason} ({Location})", error.Message, location ); } else { log.LogError("Failed to check slash command compatibility: {Reason}", error.Message); return; } } else { var updateSlash = await slashService.UpdateSlashCommandsAsync(debugServer, ct : cancellationSource.Token); if (!updateSlash.IsSuccess) { log.LogWarning("Failed to update slash commands: {Reason}", updateSlash.Error.Message); } } log.LogInformation("Initializing plugins..."); var initializePlugins = await plugins.InitializeAsync(hostServices, cancellationSource.Token); if (!initializePlugins.IsSuccess) { log.LogError("Failed to initialize the plugin tree"); if (initializePlugins.Error is AggregateError a) { foreach (var error in a.Errors) { if (error.IsSuccess) { continue; } log.LogError("Initialization error: {Error}", error.Error !.Message); } } else { log.LogError("Initialization error: {Error}", initializePlugins.Error); } return; } log.LogInformation("Migrating plugins..."); var migratePlugins = await plugins.MigrateAsync(hostServices, cancellationSource.Token); if (!migratePlugins.IsSuccess) { log.LogError("Failed to initialize the plugin tree"); if (migratePlugins.Error is AggregateError a) { foreach (var error in a.Errors) { if (error.IsSuccess) { continue; } log.LogError("Migration error: {Error}", error.Error !.Message); } } else { log.LogError("Migration error: {Error}", migratePlugins.Error); } return; } var behaviourService = hostServices.GetRequiredService <BehaviourService>(); await behaviourService.StartBehavioursAsync(); await host.RunAsync(cancellationSource.Token); await behaviourService.StopBehavioursAsync(); }
public void ReturnsTrueForValidDiscordSnowflake() { var value = "143867839282020352"; Assert.True(Snowflake.TryParse(value, out _)); }
public void ReturnsFalseForStringWithLetters() { var value = "i4e867839282020e52"; Assert.False(Snowflake.TryParse(value, out _)); }
public void ReturnsFalseForEmptyString() { var value = string.Empty; Assert.False(Snowflake.TryParse(value, out _)); }
public void ReturnsFalseForSnowflakeBeforeDiscordEpoch() { var value = "-1"; Assert.False(Snowflake.TryParse(value, out _)); }
public async Task <Stream> Post(ExportOptions options) { var parsed = Snowflake.TryParse(options.ChannelId); var channelId = parsed ?? Snowflake.Zero; var token = new AuthToken(AuthTokenType.Bot, options.Token); var client = new DiscordClient(token); Channel channel; try { channel = await client.GetChannelAsync(channelId); } catch (DiscordChatExporterException e) { _logger.LogError($"{e}"); var isUnauthorized = e.Message.Contains("Authentication"); var content = isUnauthorized ? "Invalid Discord token provided." : "Please provide a valid channel"; Response.ContentType = "application/json"; Response.StatusCode = isUnauthorized ? 401 : 409; return(GenerateStreamFromString("{ \"error\": \"" + content + "\" }")); } var guild = await client.GetGuildAsync(channel.GuildId); using var req = new HttpRequestMessage(HttpMethod.Get, new Uri("https://discord.com/api/v8/users/@me")); req.Headers.Authorization = token.GetAuthorizationHeader(); var res = await _httpclient.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); var text = await res.Content.ReadAsStringAsync(); var me = DiscordChatExporter.Domain.Discord.Models.User.Parse(JsonDocument.Parse(text).RootElement.Clone()); _logger.LogInformation($"[{me.FullName} ({me.Id})] Exporting #{channel.Name} ({channel.Id}) within {guild.Name} ({guild.Id})"); var path = GetPath(channel.Id.ToString()); var request = new ExportRequest( guild, channel, path, ExportFormat.HtmlDark, null, null, null, false, false, "dd-MMM-yy hh:mm tt" ); var exporter = new ChannelExporter(client); await exporter.ExportChannelAsync(request); var stream = new FileStream(path, FileMode.Open); Response.ContentType = "text/html; charset=UTF-8"; Response.StatusCode = 200; deleteFile(path); return(stream); }
public override async Task CreateExport(CreateExportRequest options, IServerStreamWriter <CreateExportResponse> responseStream, ServerCallContext context) { var ef = options.ExportFormat; var exportFormat = (DiscordChatExporter.Core.Exporting.ExportFormat)ef; var parsed = Snowflake.TryParse(options.ChannelId); var channelId = parsed ?? Snowflake.Zero; var client = new DiscordClient(options.Token); client._tokenKind = TokenKind.Bot; Channel channel; try { channel = await client.GetChannelAsync(channelId); } catch (DiscordChatExporterException e) { if (e.Message.Contains("Authentication")) { throw new RpcException(new Status(StatusCode.PermissionDenied, "An invalid Discord token was provided.")); } if (e.Message.Contains("Requested resource does not exist")) { throw new RpcException(new Status(StatusCode.NotFound, "A channel with the provided ID was not found.")); } throw new RpcException(new Status(StatusCode.Unknown, $"An unknown error occurred: {e.Message}")); } var guild = await client.GetGuildAsync(channel.GuildId); var res = await client.GetJsonResponseAsync("users/@me"); var me = DiscordChatExporter.Core.Discord.Data.User.Parse(res); var path = GetPath(channel.Id.ToString(), exportFormat); _logger.LogInformation($"[{me.FullName} ({me.Id})] Exporting #{channel.Name} ({channel.Id}) within {guild.Name} ({guild.Id}) to {path}"); var request = new ExportRequest( guild, channel, path, exportFormat, Snowflake.TryParse(options.After), Snowflake.TryParse(options.Before), PartitionLimit.Null, MessageFilter.Null, false, false, options.DateFormat ); var exporter = new ChannelExporter(client); _logger.LogInformation("Starting export"); var progress = new Progress <double>(p => responseStream.WriteAsync(new CreateExportResponse { Progress = p })); var messageCount = await exporter.ExportChannelAsync(request, progress); _logger.LogInformation("Finished exporting"); var buffer = new byte[ChunkSize]; await using var readStream = File.OpenRead(path); while (true) { var count = await readStream.ReadAsync(buffer); if (count == 0) { break; } Console.WriteLine("Sending file data chunk of length " + count); await responseStream.WriteAsync(new CreateExportResponse { Data = new ExportComplete { MessageCount = messageCount, Data = UnsafeByteOperations.UnsafeWrap(buffer.AsMemory(0, count)) } }); } deleteFile(path); }
public override ValueTask <TypeParserResult <CachedUser> > ParseAsync(Parameter parameter, string value, CommandContext _) { var context = (DiscordCommandContext)_; if (context.Guild != null) { var memberParser = _memberParser ?? (_memberParser = parameter.Service.GetSpecificTypeParser <CachedMember, CachedMemberTypeParser>() ?? new CachedMemberTypeParser(_comparison)); var memberParserResult = _memberParser.ParseAsync(parameter, value, _).Result; return(memberParserResult.IsSuccessful ? TypeParserResult <CachedUser> .Successful(memberParserResult.Value) : TypeParserResult <CachedUser> .Unsuccessful(memberParserResult.Reason)); } IReadOnlyDictionary <Snowflake, CachedUser> users; if (context.Channel is CachedDmChannel dmChannel) { users = new Dictionary <Snowflake, CachedUser> { [dmChannel.Recipient.Id] = dmChannel.Recipient, [context.Bot.CurrentUser.Id] = context.Bot.CurrentUser }; } else if (context.Channel is CachedGroupChannel groupChannel) { var dictionary = groupChannel.Recipients.ToDictionary(x => x.Key, x => x.Value); dictionary[context.Bot.CurrentUser.Id] = context.Bot.CurrentUser; users = dictionary; } else { throw new InvalidOperationException("Unknown channel type."); } CachedUser user = null; if (Discord.TryParseUserMention(value, out var id) || Snowflake.TryParse(value, out id)) { users.TryGetValue(id, out user); } if (user == null) { var values = users.Values; var hashIndex = value.LastIndexOf('#'); if (hashIndex != -1 && hashIndex + 5 == value.Length) { user = values.FirstOrDefault(x => { var valueSpan = value.AsSpan(); var nameSpan = valueSpan.Slice(0, value.Length - 5); var discriminatorSpan = valueSpan.Slice(hashIndex + 1); return(x.Name.AsSpan().Equals(nameSpan, _comparison) && x.Discriminator.AsSpan().Equals(discriminatorSpan, default)); }); } if (user == null) { // TODO: custom result type returning the users? var matchingUsers = values.Where(x => x.Name.Equals(value, _comparison) || x is CachedMember member && member.Nick != null && member.Nick.Equals(value, _comparison)).ToArray(); if (matchingUsers.Length > 1) { return(TypeParserResult <CachedUser> .Unsuccessful("Multiple users found. Mention the user or use their tag or ID.")); } if (matchingUsers.Length == 1) { user = matchingUsers[0]; } } } return(user == null ? TypeParserResult <CachedUser> .Unsuccessful("No user found matching the input.") : TypeParserResult <CachedUser> .Successful(user)); }
public async Task <Stream> Post(ExportOptions options) { Stream SendJsonError(string error, int status) { Response.ContentType = "application/json"; Response.StatusCode = status; return(GenerateStreamFromString("{ \"error\": \"" + error + "\" }")); } if (!Enum.IsDefined(typeof(ExportFormat), options.export_format)) { return(SendJsonError($"An export format with the id '{options.export_format}' was not found.", 400)); } var parsed = Snowflake.TryParse(options.channel_id); var channelId = parsed ?? Snowflake.Zero; var client = new DiscordClient(options.token); client._tokenKind = TokenKind.Bot; Channel channel; try { channel = await client.GetChannelAsync(channelId); } catch (DiscordChatExporterException e) { if (e.Message.Contains("Authentication")) { return(SendJsonError("An invalid Discord token was provided.", 401)); } if (e.Message.Contains("Requested resource does not exist")) { return(SendJsonError("A channel with the provided ID was not found.", 404)); } return(SendJsonError($"An unknown error occurred: {e.Message}", 500)); } var guild = await client.GetGuildAsync(channel.GuildId); var res = await client.GetJsonResponseAsync("users/@me"); var me = DiscordChatExporter.Core.Discord.Data.User.Parse(res); var path = GetPath(channel.Id.ToString(), options.export_format); _logger.LogInformation($"[{me.FullName} ({me.Id})] Exporting #{channel.Name} ({channel.Id}) within {guild.Name} ({guild.Id}) to {path}"); var request = new ExportRequest( guild, channel, path, options.export_format, Snowflake.TryParse(options.after), Snowflake.TryParse(options.before), PartitionLimit.Null, MessageFilter.Null, false, false, options.date_format ); var exporter = new ChannelExporter(client); _logger.LogInformation("Starting export"); var messageCount = await exporter.ExportChannelAsync(request); _logger.LogInformation("Finished exporting"); var stream = new FileStream(path, FileMode.Open); var ext = options.export_format.GetFileExtension(); if (ext == "txt") { ext = "plain"; } Response.ContentType = $"text/{ext}; charset=UTF-8"; Response.Headers.Add("X-Message-Count", messageCount.ToString()); Response.StatusCode = 200; deleteFile(path); return(stream); }
/// <inheritdoc/> public override async ValueTask <TypeParserResult <IMember> > ParseAsync(Parameter parameter, string value, DiscordGuildCommandContext context) { IMember member; if (Snowflake.TryParse(value, out var id) || Mention.TryParseUser(value, out id)) { // The value is a mention or an ID. // We look up the cache first. if (!context.Guild.Members.TryGetValue(id, out member)) { // This means it's either an invalid ID or the member isn't cached. // We don't know which one it is, so we have to query the guild. await using (context.BeginYield()) { // Check if the gateway is/will be rate-limited. if (context.Bot.GetShard(context.GuildId).RateLimiter.GetRemainingRequests() < 3) { // Use a REST call instead. member = await context.Bot.FetchMemberAsync(context.GuildId, id).ConfigureAwait(false); } else { // Otherwise use gateway member chunking. var members = await context.Bot.Chunker.QueryAsync(context.GuildId, new[] { id }).ConfigureAwait(false); member = members.GetValueOrDefault(id); } } } } else { // The value is possibly a tag, name, or nick. // So let's check for a '#', indicating a tag. string name, discriminator; var hashIndex = value.LastIndexOf('#'); if (hashIndex != -1 && hashIndex + 5 == value.Length) { // The value is a tag (Name#0000); name = value.Substring(0, value.Length - 5); discriminator = value.Substring(hashIndex + 1); } else { // The value is possibly a name or a nick. name = value; discriminator = null; } // The predicate checks for the given tag or name/nick accordingly. Func <IMember, bool> predicate = discriminator != null ? x => x.Name == name && x.Discriminator == discriminator : x => x.Name == name || x.Nick == name; // We look up the cache first. member = context.Guild.Members.Values.FirstOrDefault(predicate); if (member == null) { // This means it's either an invalid input or the member isn't cached. await using (context.BeginYield()) { // We don't know which one it is, so we have to query the guild. // Check if the gateway is/will be rate-limited. // TODO: swap these two around? IEnumerable <IMember> members; if (context.Bot.GetShard(context.GuildId).RateLimiter.GetRemainingRequests() < 3) { members = await context.Bot.SearchMembersAsync(context.GuildId, name); } else { members = (await context.Bot.Chunker.QueryAsync(context.GuildId, name).ConfigureAwait(false)).Values; } member = members.FirstOrDefault(predicate); } } } if (member != null) { return(Success(member)); } return(Failure("No member found matching the input.")); }