Esempio n. 1
0
        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")));
        }
Esempio n. 2
0
        /// <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."));
        }
Esempio n. 3
0
        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)));
        }
Esempio n. 6
0
        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})");
            }
        }
Esempio n. 7
0
    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;
            }
        }
    }
Esempio n. 8
0
        /// <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."));
        }
Esempio n. 9
0
        /// <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."));
        }
Esempio n. 11
0
        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));
    }
Esempio n. 13
0
        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));
        }
Esempio n. 14
0
        /// <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));
        }
Esempio n. 15
0
        /// <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);
        }
Esempio n. 16
0
        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];
Esempio n. 18
0
        /// <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");
        }
Esempio n. 19
0
    /// <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();
    }
Esempio n. 20
0
            public void ReturnsTrueForValidDiscordSnowflake()
            {
                var value = "143867839282020352";

                Assert.True(Snowflake.TryParse(value, out _));
            }
Esempio n. 21
0
            public void ReturnsFalseForStringWithLetters()
            {
                var value = "i4e867839282020e52";

                Assert.False(Snowflake.TryParse(value, out _));
            }
Esempio n. 22
0
            public void ReturnsFalseForEmptyString()
            {
                var value = string.Empty;

                Assert.False(Snowflake.TryParse(value, out _));
            }
Esempio n. 23
0
            public void ReturnsFalseForSnowflakeBeforeDiscordEpoch()
            {
                var value = "-1";

                Assert.False(Snowflake.TryParse(value, out _));
            }
Esempio n. 24
0
        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);
        }
Esempio n. 25
0
    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);
    }
Esempio n. 26
0
        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));
        }
Esempio n. 27
0
        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);
        }
Esempio n. 28
0
        /// <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."));
        }