public virtual void Run()
        {
            IMessage?message = null;

            try
            {
                _dispatching.Set(true);
                message = Dequeue();
                if (message != null)
                {
                    _listener?.HandleMessage(message);
                }
            }
            catch (Exception e)
            {
                // TODO: Log
                if (message != null && _deadLettersQueue != null)
                {
                    _deadLettersQueue.Enqueue(message);
                }
                Console.WriteLine($"AsyncMessageQueue: Dispatch to listener has failed because: {e.Message}");
                Console.WriteLine(e.StackTrace);
            }
            finally
            {
                _dispatching.Set(false);
                _endDispatchingEvent.Set();
            }
        }
예제 #2
0
        public TResponse SendRequest <TRequest, TResponse>(TRequest request)
            where TRequest : IRequest
            where TResponse : IResponse
        {
            try
            {
                IObservable <IMessage> responseObservable = this.Observable.Where(( IMessage message ) =>
                {
                    return(message.Id.Equals(request.Id));
                });

                IObservable <IMessage> timeoutObservable = responseObservable.Timeout(this.MessageRoundTripTimeout);

                IObservable <IMessage> waitObservable = timeoutObservable.FirstOrDefaultAsync();

                this.SendMessage(request);

                IMessage?response = waitObservable.Wait();

                if (response is null)
                {
                    throw Assert.Exception(new MessageTransmissionException($"Sending of '{ request.Name }' with id '{ request.Id }' received no response."));
                }

                return(( TResponse )(response));
            }catch (TimeoutException ex)
            {
                throw Assert.Exception(new MessageTransmissionException($"Sending of '{ request.Name }' with id '{ request.Id }' timed out.", ex));
            }catch (Exception ex)
                when(ex is not MessageTransmissionException)
                {
                    throw Assert.Exception(new MessageTransmissionException($"Sending of '{ request.Name }' with id '{ request.Id }' failed.", ex));
                }
        }
예제 #3
0
 /// <summary>
 /// Initializes a new instance of the <see cref="GrpcAgentResult"/> class that is considered successful.
 /// </summary>
 /// <param name="status">The <see cref="Status"/>.</param>
 /// <param name="trailers">The trailers <see cref="Metadata"/>.</param>
 /// <param name="request">The gRPC request value.</param>
 /// <param name="response">The gRPC response value (optional).</param>
 public GrpcAgentResult(Status status, Metadata trailers, IMessage?request, object?response = null)
 {
     IsSuccess        = true;
     Status           = status;
     ResponseTrailers = trailers;
     Request          = request;
     Response         = response;
 }
        public async Task <RuntimeResult> IncludePreviousMessagesAsync
        (
            [RequireEntityOwnerOrPermission(typeof(EditRoleplay), PermissionTarget.Other)]
            Roleplay roleplay,
            [OverrideTypeReader(typeof(UncachedMessageTypeReader <IMessage>))]
            IMessage startMessage,
            [OverrideTypeReader(typeof(UncachedMessageTypeReader <IMessage>))]
            IMessage?finalMessage = null
        )
        {
            finalMessage ??= this.Context.Message;

            if (startMessage.Channel != finalMessage.Channel)
            {
                return(RuntimeCommandResult.FromError("The messages are not in the same channel."));
            }

            var addedOrUpdatedMessageCount = 0;

            var latestMessage = startMessage;

            while (latestMessage.Timestamp < finalMessage.Timestamp)
            {
                var messages = (await this.Context.Channel.GetMessagesAsync
                                (
                                    latestMessage, Direction.After
                                ).FlattenAsync()).OrderBy(m => m.Timestamp).ToList();

                latestMessage = messages.Last();

                foreach (var message in messages)
                {
                    // Jump out if we've passed the final message
                    if (message.Timestamp > finalMessage.Timestamp)
                    {
                        break;
                    }

                    if (!(message is IUserMessage userMessage))
                    {
                        continue;
                    }

                    var modifyResult = await _discordRoleplays.ConsumeMessageAsync(userMessage);

                    if (modifyResult.IsSuccess)
                    {
                        ++addedOrUpdatedMessageCount;
                    }
                }
            }

            return(RuntimeCommandResult.FromSuccess
                   (
                       $"{addedOrUpdatedMessageCount} messages added to \"{roleplay.Name}\"."
                   ));
        }
예제 #5
0
 public static Task SafeDelete(this IMessage?message)
 {
     try
     {
         return(message?.DeleteAsync());
     }
     catch (Exception)
     {
         return(Task.CompletedTask);
     }
 }
        private async Task TryTrackMessageAsync(
            IGuild?guild,
            IMessage?message,
            Func <ulong, Task> asyncTrackAction,
            CancellationToken cancellationToken)
        {
            if (guild is null)
            {
                MessageLogMessages.IgnoringNonGuildMessage(_logger);
                return;
            }

            if (message is { })
        private async Task TryLogAsync(
            ISocketGuild?guild,
            IMessage?oldMessage,
            IMessage?newMessage,
            IISocketMessageChannel channel,
            Func <Embed> renderLogMessage,
            CancellationToken cancellationToken)
        {
            if (guild is null)
            {
                MessageLoggingLogMessages.IgnoringNonGuildMessage(_logger);
                return;
            }

            // I.E. we have content for both messages and can see for sure it hasn't changed, E.G. Embed changes
            if ((oldMessage?.Content == newMessage?.Content) &&
                (oldMessage is { }) &&
예제 #8
0
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            byte[]? data = bindingContext.HttpContext.GetData();
            if (data == null)
            {
                return(Task.CompletedTask);
            }
            Type     type    = bindingContext.ModelType;
            IMessage?message = data.ToObject(type);

            if (message == null)
            {
                return(Task.CompletedTask);
            }
            bindingContext.Result = ModelBindingResult.Success(message);
            return(Task.CompletedTask);
        }
예제 #9
0
        /// <summary>
        /// Initializes a new instance of the <see cref="GrpcAgentResult"/> class that is considered unsuccessful.
        /// </summary>
        /// <param name="rex">The <see cref="RpcException"/>.</param>
        /// <param name="request">The gRPC request value.</param>
        public GrpcAgentResult(RpcException rex, IMessage?request)
        {
            Exception        = Check.NotNull(rex, nameof(rex));
            Status           = Exception.Status;
            ErrorMessage     = Exception.Status.Detail;
            ResponseTrailers = Exception.Trailers;
            Request          = request;

            if (ResponseTrailers.Count > 0)
            {
                var t = ResponseTrailers.Where(x => x.Key == GrpcConsts.ErrorTypeHeaderName).SingleOrDefault();
                if (System.Enum.TryParse <ErrorType>(t.Value, out var et))
                {
                    ErrorType = et;
                }
            }

            IsSuccess = false;
        }
예제 #10
0
        /// <inheritdoc />
        public bool Equals(IMessage?other)
        {
            return(other is not null &&
                   string.Equals(Id, other.Id, StringComparison.Ordinal) &&
                   string.Equals(Body, other.Body, StringComparison.Ordinal) &&
                   string.Equals(ChatId, other.ChatId, StringComparison.Ordinal) &&
                   string.Equals(Author, other.Author, StringComparison.Ordinal) &&
                   string.Equals(SenderName, other.SenderName, StringComparison.Ordinal) &&
                   string.Equals(QuotedMessageId, other.QuotedMessageId, StringComparison.Ordinal) &&
                   string.Equals(QuotedMessageBody, other.QuotedMessageBody, StringComparison.Ordinal) &&

                   Type == other.Type &&
                   FromMe == other.FromMe &&
                   Time == other.Time &&
                   MessageNumber == other.MessageNumber &&
                   Self == other.Self &&
                   IsForwarded == other.IsForwarded &&
                   QuotedMessageId == other.QuotedMessageId &&
                   QuotedMessageType == other.QuotedMessageType &&
                   ChatName == other.ChatName);
        }
예제 #11
0
        /// <summary>
        /// Ensures the parameters for executing stray methods are valid
        /// </summary>
        /// <param name="info">The method info</param>
        /// <param name="user">The user executing the method</param>
        /// <param name="channel">The channel the method is executing for</param>
        /// <param name="message">The message the method is executing for</param>
        /// <returns>An array of all of the parameters for the method</returns>
        public object?[] Parameters(MethodInfo info, IUser user, IChannel channel, IMessage?message)
        {
            var paras = info.GetParameters();

            if (paras.Length == 0)
            {
                return(Array.Empty <object>());
            }

            var outPars = new object?[paras.Length];

            for (var i = 0; i < paras.Length; i++)
            {
                var par = paras[i];
                if (typeof(IUser).IsAssignableFrom(par.ParameterType))
                {
                    outPars[i] = user;
                    continue;
                }

                if (typeof(IChannel).IsAssignableFrom(par.ParameterType))
                {
                    outPars[i] = channel;
                    continue;
                }


                if (typeof(IMessage).IsAssignableFrom(par.ParameterType))
                {
                    outPars[i] = message;
                    continue;
                }

                var inter = _provider.GetService(par.ParameterType);
                outPars[i] = inter;
            }

            return(outPars);
        }
예제 #12
0
        private async Task HandleNotificationAsync(IMessageDelete notification, IMessage?previousMessage, CancellationToken cancellationToken)
        {
            if (!notification.GuildID.HasValue)
            {
                return;
            }

            var channelId = notification.ChannelID;

            var isThreadChannel = await _threadSvc.IsThreadChannelAsync(channelId, cancellationToken);

            if (!isThreadChannel)
            {
                return;
            }

            var guild = notification.GuildID;

            using var logScope = MessageLoggingLogMessages.BeginMessageNotificationScope(_logger, guild.Value.Value, notification.ChannelID.Value, notification.ID.Value);

            MessageLoggingLogMessages.MessageDeletedHandling(_logger);

            await TryLogAsync(
                guild,
                previousMessage?.Author.ID,
                null,
                null,
                notification.ChannelID,
                () =>
            {
                var fields = Enumerable.Empty <IEmbedField>()
                             .Concat(FormatMessageContent(previousMessage?.Content ?? "[Message was not in cache]")
                                     .EnumerateLongTextAsFieldBuilders("**Content**"))
                             .Append(new EmbedField("Channel ID", notification.ChannelID.Value.ToString(), true))
                             .Append(new EmbedField("Message ID", notification.ID.Value.ToString(), true));
                //                                ↓ This character is a "wastebasket", don't worry
                return(new Embed(Description: $"\\🗑️ **Message deleted in <#{notification.ChannelID.Value}>**",
                                 Footer: new EmbedFooter("", default, default),
예제 #13
0
        private static async Task <Task> ClearReactionsAfterDelay(ISocketMessageChannel channel)
        {
            while (activeHelpEmbeds.Count > 0)
            {
                Log.Write("Checking For Inactive Help Windows from total of " + activeHelpEmbeds.Count, "Bot");

                DateTime runTime = DateTime.Now;

                foreach (KeyValuePair <ulong, ActiveHelp> help in activeHelpEmbeds)
                {
                    if ((runTime - help.Value.LastInteractedWith).TotalSeconds > 20)
                    {
                        IMessage?cmdMessage = null;
                        IMessage message    = await channel.GetMessageAsync(help.Key);

                        if (message.Reference != null && message.Reference.MessageId.IsSpecified)
                        {
                            cmdMessage = await channel.GetMessageAsync(message.Reference.MessageId.Value);
                        }

                        await message.DeleteAsync();

                        if (cmdMessage != null)
                        {
                            await cmdMessage.DeleteAsync();
                        }

                        activeHelpEmbeds.Remove(help.Key);
                    }
                }

                await Task.Delay(15000);
            }

            Log.Write("All Help Windows Inactive", "Bot");
            return(Task.CompletedTask);
        }
        private async Task <(IMessage?requestMessage, StatusCode statusCode, string?errorMessage)> CreateMessage(HttpRequest request)
        {
            IMessage?requestMessage;

            if (_bodyDescriptor != null)
            {
                if (request.ContentType == null ||
                    !request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
                {
                    return(null, StatusCode.InvalidArgument, "Request content-type of application/json is required.");
                }

                if (!request.Body.CanSeek)
                {
                    // JsonParser does synchronous reads. In order to avoid blocking on the stream, we asynchronously
                    // read everything into a buffer, and then seek back to the beginning.
                    request.EnableBuffering();
                    Debug.Assert(request.Body.CanSeek);

                    await request.Body.DrainAsync(CancellationToken.None);

                    request.Body.Seek(0L, SeekOrigin.Begin);
                }

                var encoding = RequestEncoding.SelectCharacterEncoding(request);
                // TODO: Handle unsupported encoding

                using (var requestReader = new HttpRequestStreamReader(request.Body, encoding))
                {
                    if (_bodyDescriptorRepeated)
                    {
                        var containingMessage = ParseRepeatedContent(requestReader);

                        if (_resolvedBodyFieldDescriptors !.Count > 0)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _resolvedBodyFieldDescriptors,
                                                                       containingMessage);
                        }
                        else
                        {
                            requestMessage = containingMessage;
                        }
                    }
                    else
                    {
                        IMessage bodyContent;

                        try
                        {
                            bodyContent = JsonParser.Default.Parse(requestReader, _bodyDescriptor);
                        }
                        catch (InvalidJsonException)
                        {
                            return(null, StatusCode.InvalidArgument, "Request JSON payload is not correctly formatted.");
                        }
                        catch (InvalidProtocolBufferException exception)
                        {
                            return(null, StatusCode.InvalidArgument, exception.Message);
                        }

                        if (_bodyFieldDescriptors != null)
                        {
                            requestMessage = (IMessage)Activator.CreateInstance <TRequest>();
                            ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, _bodyFieldDescriptors, bodyContent);
                        }
                        else
                        {
                            requestMessage = bodyContent;
                        }
                    }
                }
예제 #15
0
        /// <summary>
        /// Invokes the gRPC call with no result asynchronously.
        /// </summary>
        /// <param name="func">The <paramref name="func"/> to perform the gRPC call.</param>
        /// <param name="request">The gRPC request value (for auditing).</param>
        /// <param name="requestOptions">The optional <see cref="GrpcRequestOptions"/>.</param>
        /// <param name="memberName">The method or property name of the caller to the method.</param>
        /// <param name="filePath">The full path of the source file that contains the caller.</param>
        /// <param name="lineNumber">The line number in the source file at which the method is called.</param>
        /// <returns>The <see cref="GrpcAgentResult"/>.</returns>
        public Task <GrpcAgentResult> InvokeAsync(Func <TClient, CallOptions, AsyncUnaryCall <Empty> > func, IMessage?request, GrpcRequestOptions?requestOptions = null, [CallerMemberName] string?memberName = null, [CallerFilePath] string?filePath = null, [CallerLineNumber] int lineNumber = 0)
        {
            if (requestOptions?.ETag != null)
            {
                throw new NotImplementedException();
            }

            return(GrpcAgentInvoker.Current.InvokeAsync(this, async() =>
            {
                try
                {
                    var options = new CallOptions(CreateRequestHeaders());
                    using var call = Check.NotNull(func, nameof(func)).Invoke(Client, options);
                    await call.ResponseAsync.ConfigureAwait(false);
                    return new GrpcAgentResult(call.GetStatus(), call.GetTrailers(), request);
                }
                catch (RpcException rex)
                {
                    return new GrpcAgentResult(rex, request);
                }
            }, null !, memberName, filePath, lineNumber));
예제 #16
0
 /// <summary>
 /// Generates the message components for the given interaction
 /// </summary>
 /// <typeparam name="T">The class type that handles the component interactions</typeparam>
 /// <param name="channel">The channel the interaction will happen in</param>
 /// <param name="user">The user that triggered the interaction</param>
 /// <param name="message">The message that triggered the interaction</param>
 /// <returns>The message components to be sent to discord</returns>
 public Task <MessageComponent> Components <T>(IChannel channel, IUser user, IMessage?message = null) where T : ComponentHandler => Components(typeof(T), channel, user, message);
예제 #17
0
 public MessageHandlerResult(IMessage?response = null, bool isDone = false)
 {
     Response = response;
     IsDone   = isDone;
 }
예제 #18
0
        /// <summary>
        /// Generates the message components for the given interaction
        /// </summary>
        /// <param name="type">The class type that handles the component interactions</param>
        /// <param name="channel">The channel the interaction will happen in</param>
        /// <param name="user">The user that triggered the interaction</param>
        /// <param name="message">The message that triggered the interaction</param>
        /// <returns>The message components to be sent to discord</returns>
        public async Task <MessageComponent> Components(Type type, IChannel channel, IUser user, IMessage?message)
        {
            if (!typeof(ComponentHandler).IsAssignableFrom(type))
            {
                throw new ArgumentException($"Type does not implement `{nameof(ComponentHandler)}`", type.FullName);
            }

            var builder = new ComponentBuilder();
            var methods = type.GetMethods();

            foreach (var method in methods)
            {
                var compAtr = method.GetCustomAttribute <ComponentAttribute>();
                if (compAtr == null)
                {
                    continue;
                }

                if (compAtr is ButtonAttribute btn)
                {
                    HandleButton(btn, method, builder);
                    continue;
                }

                if (compAtr is not SelectMenuAttribute sm)
                {
                    _logger.LogWarning($"Unknown `{nameof(ComponentAttribute)}`: {compAtr.GetType().Name}");
                    continue;
                }

                await HandleSelectMenu(sm, method, type, builder, channel, user, message);
            }

            return(builder.Build());
        }
예제 #19
0
        private static async IAsyncEnumerable <IMessage> ScrapeChannel(IMessageChannel channel, IMessage?start = null)
        {
            //If start message is not set then get the latest message in the channel now
            start ??= (await channel.GetMessagesAsync(1).FlattenAsync()).SingleOrDefault();

            // Keep loading pages until the start message is null
            while (start != null)
            {
                // Add a slight delay between fetching pages so we don't hammer discord too hard
                await Task.Delay(150);

                // Get the next page of messages
                var page = (await channel.GetMessagesAsync(start, Direction.Before, 99).FlattenAsync()).OrderByDescending(a => a.CreatedAt).ToArray();

                // Set the start of the next page to the end of this page
                start = page.LastOrDefault();

                // yield every message in page
                foreach (var message in page)
                {
                    yield return(message);
                }
            }
        }
예제 #20
0
        /// <summary>
        /// Handles adding <see cref="SelectMenuComponent"/> to the given interaction
        /// </summary>
        /// <param name="sm">The <see cref="SelectMenuAttribute"/> that represents the select menu handler</param>
        /// <param name="method">The method that will be triggered when handling the interaction</param>
        /// <param name="type">The type of the class that will handle the interaction</param>
        /// <param name="builder">The <see cref="ComponentBuilder"/></param>
        /// <param name="channel">The channel the interaction happened in</param>
        /// <param name="user">The user that triggered the interaction</param>
        /// <param name="message">The message that triggered the interaction</param>
        /// <returns>A task representing the completion of the request</returns>
        /// <exception cref="ArgumentException">Thrown when the min or max values field on the select menu are less than 1</exception>
        public async Task HandleSelectMenu(SelectMenuAttribute sm, MethodInfo method, Type type, ComponentBuilder builder, IChannel channel, IUser user, IMessage?message)
        {
            if (sm.MinValues < 1 || sm.MaxValues < 1)
            {
                throw new ArgumentException("Min/Max values for Select Menu must be 1 or greater.", "(Max/Min)Values");
            }

            var id      = _handlers.IdFromMethod(method);
            var options = new List <SelectMenuOptionBuilder>();

            if (!string.IsNullOrEmpty(sm.OptionsMethod))
            {
                options.AddRange(await MenuOptionsFromMethod(sm.OptionsMethod, type, channel, user, message));
            }

            options.AddRange(MenuOptionsFromAttributes(method));

            builder.WithSelectMenu(id, options, sm.Placeholder, sm.MinValues, sm.MaxValues, sm.Disabled, sm.Row);
        }
예제 #21
0
        /// <summary>
        /// Gets all of the <see cref="SelectMenuOptionBuilder[]"/> for the given method
        /// </summary>
        /// <param name="method">The method to get the select menu option builders from</param>
        /// <param name="type">The type of the class that will handle the interaction</param>
        /// <param name="channel">The channel the interaction happened in</param>
        /// <param name="user">The user that triggered the interaction</param>
        /// <param name="message">The message that triggered the interaction</param>
        /// <returns>A task representing the completion of the request</returns>
        /// <exception cref="ArgumentException">Thrown if the method is invalid</exception>
        /// <exception cref="NullReferenceException">Thrown if the service type is invalid</exception>
        public async Task <SelectMenuOptionBuilder[]> MenuOptionsFromMethod(string method, Type type, IChannel channel, IUser user, IMessage?message)
        {
            var minfo = type.GetMethod(method);

            if (minfo == null)
            {
                throw new ArgumentException($"Cannot find method `{method}` in `{type.Name}` type.", nameof(method));
            }

            var instance = _provider.GetService(type);

            if (instance == null)
            {
                throw new NullReferenceException($"Cannot get instance of type `{type.Name}`");
            }

            if (minfo.ReturnType != typeof(Task <SelectMenuOptionBuilder[]>))
            {
                throw new ArgumentException($"Method `{method}` on type `{type.Name}` does not return `Task<SelectMenuOptionBuilder[]>`");
            }

            ((ComponentHandler)instance).SetContext(channel, user, _client, message);

            var pars = Parameters(minfo, user, channel, message);
            var res  = minfo.Invoke(instance, pars);

            if (res == null)
            {
                throw new ArgumentException($"Results of `{method}` on type `{type.Name}` returned null.");
            }

            var returnType = (Task <SelectMenuOptionBuilder[]>)res;

            return(await returnType);
        }
예제 #22
0
 public void Reply(IMessage?response)
 {
     MessageContext.ReplyResponse = response;
 }
예제 #23
0
        private async void StartStream(YoutubeClient youtube, AudioClient audioClient, AudioOnlyStreamInfo streamInfo, IMessage?message)
        {
            Stream ytStream = await youtube.Videos.Streams.GetAsync(streamInfo);

            // Convert yt stream
            MemoryStream memoryStream = new MemoryStream();
            await Cli.Wrap("ffmpeg")
            .WithArguments(" -hide_banner -loglevel panic -i pipe:0 -ac 2 -f s16le -ar 48000 pipe:1")
            .WithStandardInputPipe(PipeSource.FromStream(ytStream))
            .WithStandardOutputPipe(PipeTarget.ToStream(memoryStream))
            .ExecuteAsync();

            // Clear stream before beginning
            if (audioClient.CurrentStream != null)
            {
                audioClient.CurrentStream.Dispose();
                audioClient.CurrentStream = null;
            }

            AudioOutStream discord = audioClient.Client.CreatePCMStream(AudioApplication.Mixed);

            audioClient.CurrentStream = discord;

            // Delete calling command
            if (message != null)
            {
                await message.DeleteAsync();
            }

            // Start playing music
            await this.WriteStreamToVoiceChannel(audioClient, discord, memoryStream);
        }