/// <summary>
        /// A static fractory method to get eBay SOA service client proxy instance
        /// </summary>
        /// <typeparam name="TServiceContract">Servcie contract type parameter</typeparam>
        /// <param name="config">Client configuration</param>
        /// <param name="clientType">The type of specific client</param>
        /// <param name="serviceName">The name of the service, for tracking purpose</param>
        /// <returns>ClientBase instance, need to be casted to a specific client which extends ClientBase</returns>
        public static ClientBase <TServiceContract> GetSerivceClient <TServiceContract>(ClientConfig config, Type clientType, string serviceName)
            where TServiceContract : class
        {
            // http binding setting
            BasicHttpBinding binding = new BasicHttpBinding();

            // http timeout setting
            if (config.HttpTimeout > 0)
            {
                binding.OpenTimeout    = TimeSpan.FromMilliseconds(config.HttpTimeout);
                binding.ReceiveTimeout = TimeSpan.FromMilliseconds(config.HttpTimeout);
            }
            // required by WCF to support larget response message
            binding.MaxReceivedMessageSize = MAX_RECEIVE_MESSAGE_SIZE;

            // support https protocol
            string endPointAddress = config.EndPointAddress;

            if (endPointAddress.StartsWith("https"))
            {
                binding.Security.Mode = BasicHttpSecurityMode.Transport;
            }
            // build endpont address
            EndpointAddress address = new EndpointAddress(endPointAddress);

            // Use reflection to create a specific ClientBase instance
            ClientBase <TServiceContract> client = (ClientBase <TServiceContract>)Activator.CreateInstance(clientType, new object[] { binding, address });

            // add custome behaviour to the client instance
            MessageBehavior behavior = new MessageBehavior(config, serviceName);

            client.Endpoint.Behaviors.Add(behavior);

            return(client);
        }
Example #2
0
        /// <summary>
        /// Waits any text answer
        /// </summary>
        /// <param name="channel">A <see cref="DiscordChannel"/> where message should be</param>
        /// <param name="messageBody">A body of this message</param>
        /// <param name="validateAnswer">returns true if an answer is acceptable, or false to throw error message</param>
        /// <param name="onValidatedAnswer">Do any demanding task here to process this answer</param>
        /// <param name="UserId">In case one user should answer this question</param>
        /// <param name="behavior">What to do after timeout</param>
        /// <param name="timeout">This message will be terminated after timeout</param>
        /// <param name="timeoutMessage"></param>
        /// <param name="showTimeoutMessage"></param>
        /// <param name="timeoutBeforeDelete"></param>
        /// <param name="wrongAnswer"></param>
        /// <param name="existingMessage">Existing <see cref="DiscordMessage"/> to modify and to subscribe to</param>
        /// <param name="deleteAnswer"></param>
        /// <param name="deleteAnswerTimeout"></param>
        /// <param name="waitForMultipleAnswers">If <see langword="false"/> it will unsubscribe after getting first answer</param>
        public static async Task CreateQuestion(DiscordChannel channel, string messageBody,
                                                Func <string, bool> validateAnswer, Func <Answer.AnswerArgs, Task> onValidatedAnswer,
                                                ulong?UserId                   = null, MessageBehavior behavior = MessageBehavior.Volatile,
                                                TimeSpan?timeout               = null, string timeoutMessage    = "Timeout", bool showTimeoutMessage = true,
                                                TimeSpan?timeoutBeforeDelete   = null, string wrongAnswer       = "This isn't what I'm looking for.",
                                                DiscordMessage existingMessage = null, bool deleteAnswer        = false, TimeSpan?deleteAnswerTimeout = null, bool waitForMultipleAnswers = false)
        {
            SetDefaultValuesForTimeouts(ref timeout, ref timeoutBeforeDelete, ref deleteAnswerTimeout);
            DiscordMessage Question = await GetOrCreateMessage(channel, messageBody, existingMessage).ConfigureAwait(false);

            bool answered     = false;
            bool unsubscribed = false;

            PausableTimer timeoutTimer = new PausableTimer(timeout.Value.TotalMilliseconds);

            timeoutTimer.AutoReset = false;

            timeoutTimer.Elapsed += async(s, e) => {
                //Do we need to dispose it? Or just leave it?
                //In case of permanent behavior it will just skip and won't unsubscribe, just what we need
                timeoutTimer.Stop();
                if (behavior != MessageBehavior.Permanent && !answered)
                {
                    _ = UnsubscribeAndDismiss();
                    //Timeout message
                    if ((!answered || waitForMultipleAnswers) && showTimeoutMessage)
                    {
                        await QuickVolatileMessage(channel, timeoutMessage, TimeSpan.FromSeconds(3)).ConfigureAwait(false);
                    }
                }
            };

            Bot.Instance.Client.MessageCreated += MessageAdded;
            Bot.Instance.Client.MessageDeleted += OnDeleted;
            //Do I need to even start it in case of Permanent behavior?
            timeoutTimer.Start();

            //This will hold this method from returning until it will receive right answer or get deleted
            //This will give an opportunity to nest dialogs one after another
            while (!unsubscribed)
            {
                await Task.Delay(100);
            }

            Task MessageAdded(MessageCreateEventArgs e)
            {
                //Ignore other channels and other users activity
                if (e.Channel.Id != Question.ChannelId || e.Author.IsCurrent || UserId.HasValue ? e.Author.Id != UserId.Value : false)
                {
                    return(Task.CompletedTask);
                }
                timeoutTimer.Pause();
                //save answered state for further use, but keep local copy for this event, it can be changed outside
                var a = answered = validateAnswer(e.Message.Content);

                if (a && !waitForMultipleAnswers)
                {
                    timeoutTimer.Stop();
                    _ = UnsubscribeAndDismiss();
                }
                if (a)
                {
                    _ = onValidatedAnswer(new Answer.AnswerArgs(e.Author, true, e.Message, Question));
                }
                else
                {
                    //wrong answer
                    _ = QuickVolatileMessage(channel, wrongAnswer, timeoutBeforeDelete);
                }

                if (deleteAnswer)
                {
                    _ = DeleteMessageAfter(e.Message, deleteAnswerTimeout.Value.Milliseconds).ConfigureAwait(false);
                }
                if (waitForMultipleAnswers || !answered)
                {
                    timeoutTimer.Release();
                }
                return(Task.CompletedTask);               //Nice hack, thank's VS
            }

            async Task UnsubscribeAndDismiss()
            {
                if (unsubscribed)
                {
                    return;
                }
                unsubscribed = true;
                if (behavior != MessageBehavior.Permanent)
                {
                    Bot.Instance.Client.MessageCreated -= MessageAdded;
                }
                if (behavior == MessageBehavior.Volatile)
                {
                    //Should pause here
                    await Task.Delay((int)timeoutBeforeDelete.Value.TotalMilliseconds).ConfigureAwait(false);

                    await Question.DeleteAsync().ConfigureAwait(false);
                }
            }

            Task OnDeleted(MessageDeleteEventArgs e)
            {
                if (e.Message.Id != Question.Id)
                {
                    return(Task.CompletedTask);
                }
                unsubscribed = true;
                Bot.Instance.Client.MessageDeleted -= OnDeleted;
                Bot.Instance.Client.MessageCreated -= MessageAdded;
                return(Task.CompletedTask);
            }
        }
Example #3
0
        public static async Task <DiscordMessage> CreateQuestion(DiscordChannel channel, string messageBody, IEnumerable <Answer> answers,
                                                                 ulong?UserId                   = null, MessageBehavior behavior = MessageBehavior.Volatile,
                                                                 TimeSpan?timeout               = null, string timeoutMessage    = "Timeout", bool showTimeoutMessage = true,
                                                                 TimeSpan?timeoutBeforeDelete   = null, string wrongAnswer       = "This isn't what I'm looking for.",
                                                                 DiscordMessage existingMessage = null, bool deleteAnswer        = false, TimeSpan?deleteAnswerTimeout = null, bool waitForMultipleAnswers = false)
        {
            SetDefaultValuesForTimeouts(ref timeout, ref timeoutBeforeDelete, ref deleteAnswerTimeout);
            DiscordMessage Question = await GetOrCreateMessage(channel, messageBody, existingMessage).ConfigureAwait(false);

            //Some optimizations
            var answersFromTokens   = answers.SelectMany(a => a.StringTokens.Select(t => (Answer: a, Token: t))).ToArray();
            var answersFromEmojiIds = answers.ToDictionary(a => a.Emoji);

            _ = SetReactions(Question, answers.Where(a => a.Emoji != null), true);

            bool answered     = false;
            bool unsubscribed = false;

            PausableTimer timeoutTimer = new PausableTimer(timeout.Value.TotalMilliseconds);

            timeoutTimer.AutoReset = false;

            timeoutTimer.Elapsed += async(s, e) => {
                //Do we need to dispose it? Or just leave it?
                //In case of permanent behavior it will just skip and won't unsubscribe, just what we need
                timeoutTimer.Stop();
                if (behavior != MessageBehavior.Permanent && !answered)
                {
                    _ = UnsubscribeAndDismiss();
                    //Timeout message
                    if ((!answered || waitForMultipleAnswers) && showTimeoutMessage)
                    {
                        await QuickVolatileMessage(channel, timeoutMessage, TimeSpan.FromSeconds(3)).ConfigureAwait(false);
                    }
                }
            };

            if (answersFromEmojiIds.Count > 0)
            {
                Bot.Instance.Client.MessageReactionAdded += ReactionAdded;
            }
            if (answersFromTokens.Count() > 0)
            {
                Bot.Instance.Client.MessageCreated += MessageAdded;
            }
            Bot.Instance.Client.MessageDeleted += OnDeleted;
            //Do I need to even start it in case of Permanent behavior?
            timeoutTimer.Start();

            //This will hold this method from returning until it will receive right answer or get deleted
            //This will give an opportunity to nest dialogs one after another
            while (!unsubscribed && behavior != MessageBehavior.Permanent)
            {
                await Task.Delay(100);
            }
            //Permanent fall through and return it's message object
            return(Question);


            async Task ReactionAdded(MessageReactionAddEventArgs e)
            {
                //Ignore own reactions and other messages
                if (e.User.IsCurrent || e.Message.Id != Question.Id)
                {
                    return;
                }

                if (deleteAnswer)
                {
                    await((Func <Task>)(async() => {
                        await Task.Delay((int)deleteAnswerTimeout.Value.TotalMilliseconds);
                        await e.Message.DeleteReactionAsync(e.Emoji, e.User);
                    }))().ConfigureAwait(false);
                }

                if (answersFromEmojiIds.ContainsKey(e.Emoji))
                {
                    var answer = answersFromEmojiIds[e.Emoji];

                    timeoutTimer.Pause();

                    answered = true;
                    _        = answer.InvokeFromEmoji(e.User, Question);
                }

                //There is a case when you need to keep it working, like a reaction role thing
                if (!waitForMultipleAnswers && answered && behavior != MessageBehavior.Permanent)
                {
                    timeoutTimer.Stop();
                    timeoutTimer.Dispose();
                    await UnsubscribeAndDismiss().ConfigureAwait(false);

                    return;
                }
                else
                {
                    timeoutTimer.Release();
                }
            }

            async Task MessageAdded(MessageCreateEventArgs e)
            {
                //Ignore other channels and other users activity
                if (e.Channel.Id != Question.ChannelId || e.Author.IsCurrent || UserId.HasValue ? e.Author.Id != UserId.Value : false)
                {
                    return;
                }

                //Get list of all tokens ans Answer objects
                foreach (var pair in answersFromTokens)
                {
                    if (e.Message.Content.ToLower().Contains(pair.Token))
                    {
                        timeoutTimer.Pause();

                        //If there is a blank token, we will need a validation
                        answered = pair.Answer.ValidateAnswer(e.Message.Content);
                        _        = pair.Answer.InvokeFromMessage(e.Message, Question);
                        if (deleteAnswer)
                        {
                            //Doing it afterwards is simpler
                            await e.Message.DeleteAsync().ConfigureAwait(false);
                        }

                        //what to do if answer wasn't accepted? should be handled internally? Quick response

                        if (!waitForMultipleAnswers && answered)
                        {
                            timeoutTimer.Stop();
                            timeoutTimer.Dispose();
                            await UnsubscribeAndDismiss().ConfigureAwait(false);

                            return;
                        }
                        else
                        {
                            timeoutTimer.Release();
                        }
                    }
                }
                //Seems like answer wasn't recognised. Should it have an event to expire? Or make a new method for that?

                if (!answered)
                {
                    await QuickVolatileMessage(channel, string.Format(wrongAnswer,
                                                                      string.Join(", ", answers.Where(a => a.StringTokens.Length > 0).SelectMany(a => a.StringTokens))),
                                               TimeSpan.FromSeconds(10)).ConfigureAwait(false);
                }
            }

            async Task UnsubscribeAndDismiss()
            {
                if (unsubscribed)
                {
                    return;
                }
                unsubscribed = true;
                if (behavior != MessageBehavior.Permanent)
                {
                    //Can I unsubscribe if not even subscribed?
                    Bot.Instance.Client.MessageReactionAdded -= ReactionAdded;
                    Bot.Instance.Client.MessageCreated       -= MessageAdded;
                }
                if (behavior == MessageBehavior.Volatile)
                {
                    //Should pause here
                    await Task.Delay((int)timeoutBeforeDelete.Value.TotalMilliseconds).ConfigureAwait(false);

                    await Question.DeleteAsync().ConfigureAwait(false);
                }
            }

            Task OnDeleted(MessageDeleteEventArgs e)
            {
                if (e.Message.Id != Question.Id)
                {
                    return(Task.CompletedTask);
                }
                unsubscribed = true;
                Bot.Instance.Client.MessageDeleted -= OnDeleted;
                Bot.Instance.Client.MessageCreated -= MessageAdded;
                return(Task.CompletedTask);
            }
        }
Example #4
0
        /// <summary>
        /// Will create a message on a given channel, or will find an existing in case of restoring a previous session,
        /// apply answer emojis and subscribe to emojis and text answers
        /// </summary>
        /// <param name="channel">A <see cref="DiscordChannel"/> where message should be</param>
        /// <param name="messageBody">A body of this message</param>
        /// <param name="answers">List of structs containing answers in form of emojis to click or written answers with callbacks</param>
        /// <param name="UserId">In case one user should answer this question</param>
        /// <param name="timeout">This message will be terminated after timeout</param>
        /// <param name="behaviour">What to do after timeout</param>
        /// <param name="existingMessageId">If there is a message already to subscribe to</param>
        /// <param name="deleteAnswer">Delete reaction rightaway for anonymosity or to use it as a button</param>
        /// <param name="waitForMultipleAnswers">If <see langword="false"/> it will unsubscribe after getting first answer</param>
        /// <returns></returns>
        public static async Task CreateMessage(DiscordChannel channel, string messageBody,
                                               IEnumerable <Answer> answers, ulong?UserId = null, MessageBehavior behaviour = MessageBehavior.Volatile,
                                               TimeSpan?timeout               = null, string timeoutMessage = "Timed out", bool showTimeoutMessage = true,
                                               TimeSpan?timeoutBeforeDelete   = null, string wrongAnswer    = "This isn't what I'm looking for. Acceptable keywords are: {0}",
                                               DiscordMessage existingMessage = null, bool deleteAnswer     = false, TimeSpan?deleteAnswerTimeout = null, bool waitForMultipleAnswers = false)
        {
            SetDefaultValuesForTimeouts(ref timeout, ref timeoutBeforeDelete, ref deleteAnswerTimeout);

            //Find or create a message even if missing
            DiscordMessage DMessage = await GetOrCreateMessage(channel, messageBody, existingMessage).ConfigureAwait(false);

            await SetReactions(DMessage, answers.Where(a => a.Emoji != null), true).ConfigureAwait(false);

            bool answered     = false;
            bool unsubscribed = false;
            //Should this do? It's ether suppress CS1998 or do this. Unless this will have some unpredictable result. Anyway, this Task should be .ConfigureAwait(false)
            Func <Task>   unsubscribeAndDismiss = async() => { await Task.Yield(); };
            PausableTimer timeoutTimer          = new PausableTimer(timeout.Value.TotalMilliseconds);

            timeoutTimer.AutoReset = false;

            //Some optimizations
            var answersFromTokens   = answers.SelectMany(a => a.StringTokens.Select(t => (Answer: a, Token: t))).ToArray();
            var answersFromEmojiIds = answers.ToDictionary(a => a.Emoji);

            if (answers.Count() > 0)
            {
                var reactionAdded = new DSharpPlus.AsyncEventHandler <DSharpPlus.EventArgs.MessageReactionAddEventArgs>(async(e) => {
                    //Ignore own reactions and other messages
                    if (e.User.IsCurrent || e.Message.Id != DMessage.Id)
                    {
                        return;
                    }
                    //TODO Findout what if it didn't find? Or use foreach, like in messages
                    if (deleteAnswer)
                    {
                        await((Func <Task>)(async() => {
                            await Task.Delay((int)deleteAnswerTimeout.Value.TotalMilliseconds);
                            await e.Message.DeleteReactionAsync(e.Emoji, e.User);
                        }))().ConfigureAwait(false);
                    }

                    if (answersFromEmojiIds.ContainsKey(e.Emoji))
                    {
                        var answer = answersFromEmojiIds[e.Emoji];

                        timeoutTimer.Pause();

                        //Do we need to check if there is another answer set already, just in case of hickup
                        //TODO It's a reaction answer. Shouldn't it be true by default?
                        answered = true;
                        _        = answer.InvokeFromEmoji(e.User, DMessage);
                    }

                    if (!waitForMultipleAnswers && answered)
                    {
                        timeoutTimer.Stop();
                        timeoutTimer.Dispose();
                        await unsubscribeAndDismiss().ConfigureAwait(false);
                        return;
                    }
                    else
                    {
                        timeoutTimer.Release();
                    }
                });
                var messageAdded = new DSharpPlus.AsyncEventHandler <DSharpPlus.EventArgs.MessageCreateEventArgs>(async e => {
                    //Ignore other channels and other users activity
                    if (e.Channel.Id != DMessage.ChannelId || e.Author.IsCurrent || UserId.HasValue ? e.Author.Id != UserId.Value : false)
                    {
                        return;
                    }

                    //Get list of all tokens ans Answer objects
                    foreach (var pair in answersFromTokens)
                    {
                        if (e.Message.Content.ToLower().Contains(pair.Token))
                        {
                            timeoutTimer.Pause();
                            //???Do we really need to get answer value if we already found a token?
                            answered = pair.Answer.ValidateAnswer(e.Message.Content);
                            _        = pair.Answer.InvokeFromMessage(e.Message, DMessage);
                            if (deleteAnswer)
                            {
                                //Doing it afterwards is simpler
                                await e.Message.DeleteAsync().ConfigureAwait(false);
                            }

                            //what to do if answer wasn't accepted? should be handled internally? Quick response

                            if (!waitForMultipleAnswers && answered)
                            {
                                timeoutTimer.Stop();
                                timeoutTimer.Dispose();
                                await unsubscribeAndDismiss().ConfigureAwait(false);
                                return;
                            }
                            else
                            {
                                timeoutTimer.Release();
                            }
                        }
                    }
                    //Seems like answer wasn't recognised. Should it have an event to expire? Or make a new method for that?

                    await QuickVolatileMessage(channel, string.Format(wrongAnswer,
                                                                      string.Join(", ", answers.Where(a => a.StringTokens.Length > 0).SelectMany(a => a.StringTokens))),
                                               TimeSpan.FromSeconds(10)).ConfigureAwait(false);
                });
                unsubscribeAndDismiss = async() => {
                    //Should we also empty this action? This one will be ready to be collected...
                    if (unsubscribed)
                    {
                        return;
                    }
                    if (behaviour == MessageBehavior.Volatile || behaviour == MessageBehavior.KeepMessageAfterTimeout)
                    {
                        //unsubscribe later//TODO Check whether it actually works
                        if (answers.Any(a => a.Emoji != null))
                        {
                            Bot.Instance.Client.MessageReactionAdded -= reactionAdded;
                        }
                        if (answers.Any(answers => answers.StringTokens.Length > 0))
                        {
                            Bot.Instance.Client.MessageCreated -= messageAdded;
                        }
                    }
                    if (behaviour == MessageBehavior.Volatile)
                    {
                        //Should pause here
                        await Task.Delay((int)timeoutBeforeDelete.Value.TotalMilliseconds);

                        await DMessage.DeleteAsync().ConfigureAwait(false);
                    }
                    else if (behaviour == MessageBehavior.KeepMessageAfterTimeout)
                    {
                        //TODO should we delete only bots reactions?
                        await DMessage.DeleteAllReactionsAsync();
                    }
                };
                //Subscribe to reactions and answers

                if (answers.Any(a => a.Emoji != null))
                {
                    Bot.Instance.Client.MessageReactionAdded += reactionAdded;
                }
                if (answers.Any(answers => answers.StringTokens.Length > 0))
                {
                    Bot.Instance.Client.MessageCreated += messageAdded;
                }
            }
            timeoutTimer.Start();

            timeoutTimer.Elapsed += async(s, e) => {
                //Do we need to dispose it? Or just leave it?
                //In case of permanent behavior it will just skip and won't unsubscribe, just what we need
                timeoutTimer.Stop();
                if (behaviour != MessageBehavior.Permanent && !answered)
                {
                    await unsubscribeAndDismiss().ConfigureAwait(false);

                    //Timeout message
                    if (!answered && showTimeoutMessage)
                    {
                        await QuickVolatileMessage(channel, timeoutMessage, TimeSpan.FromSeconds(3)).ConfigureAwait(false);
                    }
                }
            };
        }
Example #5
0
        /// <summary>
        /// Will create a message on a given channel, or will find an existing in case of restoring a previous session,
        /// and subscribe to any text answers that will follow until timeout.
        /// </summary>
        /// <param name="channel">A <see cref="DiscordChannel"/> where message should be</param>
        /// <param name="messageBody">A body of this message</param>
        /// <param name="onAnyAnswer">callback fired on any text answer</param>
        /// <param name="UserId">In case one user should answer this question</param>
        /// <param name="timeout">This message will be terminated after timeout</param>
        /// <param name="behaviour">What to do after timeout</param>
        /// <param name="existingMessageId">If there is a message already to subscribe to</param>
        /// <param name="deleteAnswer">Delete reaction rightaway for anonymosity or to use it as a button</param>
        /// <param name="waitForMultipleAnswers">If <see langword="false"/> it will unsubscribe after getting first answer</param>
        /// <returns></returns>
        public static async Task CreateMessage(DiscordChannel channel, string messageBody,
                                               Func <string, bool> validateAnswer, Func <Answer.AnswerArgs, Task> onAnyAnswer, ulong?UserId = null, MessageBehavior behaviour = MessageBehavior.Volatile,
                                               TimeSpan?timeout               = null, string timeoutMessage = "Timed out", bool showTimeoutMessage = true,
                                               TimeSpan?timeoutBeforeDelete   = null, string wrongAnswer    = "This isn't what I'm looking for. Acceptable keywords are: {0}",
                                               DiscordMessage existingMessage = null, bool deleteAnswer     = false, TimeSpan?deleteAnswerTimeout = null, bool waitForMultipleAnswers = false) =>

        await CreateMessage(channel, messageBody, new Answer[] {
            new Answer(null, new string[] { "" }, validateAnswer, onAnyAnswer)
        },
                            UserId, behaviour, timeout, timeoutMessage, showTimeoutMessage, timeoutBeforeDelete,
                            wrongAnswer, existingMessage, deleteAnswer, deleteAnswerTimeout, waitForMultipleAnswers);