private Task <DiscordWebhookClient> MakeCachedClientFor(ICacheEntry entry, IWebhook webhook)
        {
            _logger.Information("Client for {Webhook} not found in cache, creating", webhook.Id);

            // Define expiration for the client cache
            // 10 minutes *without a query* and it gets yoten
            entry.SlidingExpiration = TimeSpan.FromMinutes(10);

            // IMemoryCache won't automatically dispose of its values when the cache gets evicted
            // so we register a hook to do so here.
            entry.RegisterPostEvictionCallback((key, value, reason, state) => (value as IDisposable)?.Dispose());

            // DiscordWebhookClient has a sync network call in its constructor (!!!)
            // and we want to punt that onto a task queue, so we do that.
            return(Task.Run(async() =>
            {
                try
                {
                    return new DiscordWebhookClient(webhook);
                }
                catch (InvalidOperationException)
                {
                    // TODO: does this leak stuff inside the DiscordWebhookClient created above?

                    // Webhook itself was found in cache, but has been deleted on the channel
                    // We request a new webhook instead
                    return new DiscordWebhookClient(await _webhookCache.InvalidateAndRefreshWebhook(webhook));
                }
            }));
        }
Ejemplo n.º 2
0
        public DefaultProvider(IWebhook webhook) : base(webhook)
        {
            _httpClient = new HttpClient();

            _httpClient.DefaultRequestHeaders.Add("User-Agent", $"DSharp4Webhook ({WebhookProvider.LibraryUrl}, {WebhookProvider.LibraryVersion})");
            _httpClient.DefaultRequestHeaders.Add("X-RateLimit-Precision", "millisecond");
        }
        private async Task <ulong> ExecuteWebhookInner(IWebhook webhook, string name, string avatarUrl, string content,
                                                       IAttachment attachment, bool hasRetried = false)
        {
            var client = await GetClientFor(webhook);

            try
            {
                // If we have an attachment, use the special SendFileAsync method
                if (attachment != null)
                {
                    using (var attachmentStream = await _client.GetStreamAsync(attachment.Url))
                        using (_metrics.Measure.Timer.Time(BotMetrics.WebhookResponseTime))
                            return(await client.SendFileAsync(attachmentStream, attachment.Filename, content,
                                                              username : FixClyde(name),
                                                              avatarUrl : avatarUrl));
                }

                // Otherwise, send normally
                return(await client.SendMessageAsync(content, username : FixClyde(name), avatarUrl : avatarUrl));
            }
            catch (HttpException e)
            {
                // If we hit an error, just retry (if we haven't already)
                if (e.DiscordCode == 10015 && !hasRetried) // Error 10015 = "Unknown Webhook"
                {
                    _logger.Warning(e, "Error invoking webhook {Webhook} in channel {Channel}", webhook.Id, webhook.ChannelId);
                    return(await ExecuteWebhookInner(await _webhookCache.InvalidateAndRefreshWebhook(webhook), name, avatarUrl, content, attachment, hasRetried : true));
                }

                throw;
            }
        }
Ejemplo n.º 4
0
 /// <summary>
 /// Initializes a new instance of the <see cref="Mapping"/> class.
 /// </summary>
 /// <param name="guid">The unique identifier.</param>
 /// <param name="title">The unique title (can be null).</param>
 /// <param name="path">The full file path from this mapping title (can be null).</param>
 /// <param name="settings">The WireMockServerSettings.</param>
 /// <param name="requestMatcher">The request matcher.</param>
 /// <param name="provider">The provider.</param>
 /// <param name="priority">The priority for this mapping.</param>
 /// <param name="scenario">The scenario. [Optional]</param>
 /// <param name="executionConditionState">State in which the current mapping can occur. [Optional]</param>
 /// <param name="nextState">The next state which will occur after the current mapping execution. [Optional]</param>
 /// <param name="stateTimes">Only when the current state is executed this number, the next state which will occur. [Optional]</param>
 /// <param name="webhook">The Webhook. [Optional]</param>
 public Mapping(
     Guid guid,
     [CanBeNull] string title,
     [CanBeNull] string path,
     [NotNull] IWireMockServerSettings settings,
     [NotNull] IRequestMatcher requestMatcher,
     [NotNull] IResponseProvider provider,
     int priority,
     [CanBeNull] string scenario,
     [CanBeNull] string executionConditionState,
     [CanBeNull] string nextState,
     [CanBeNull] int?stateTimes,
     [CanBeNull] IWebhook webhook)
 {
     Guid                    = guid;
     Title                   = title;
     Path                    = path;
     Settings                = settings;
     RequestMatcher          = requestMatcher;
     Provider                = provider;
     Priority                = priority;
     Scenario                = scenario;
     ExecutionConditionState = executionConditionState;
     NextState               = nextState;
     StateTimes              = stateTimes;
     Webhook                 = webhook;
 }
Ejemplo n.º 5
0
        /// <summary>
        ///     Retrieves the image from the url.
        /// </summary>
        /// <param name="webhook">
        ///     Webhook that provides BaseRestProvider.
        /// </param>
        /// <param name="url">
        ///     Endpoint to the image.
        /// </param>
        /// <returns>
        ///     Image data.
        /// </returns>
        public static IAvatarAction GetImageByUrl(this IWebhook webhook, string url)
        {
            Contract.AssertNotNull(webhook, nameof(webhook));
            Contract.AssertArgumentNotTrue(string.IsNullOrEmpty(url), nameof(url));

            return(new AvatarAction(webhook, webhook.RestSettings, null));
        }
        public bool TryExecuteWebhook(IWebhook webhook)
        {
            // If we have nothing saved, just allow it (we'll save something once the response returns)
            if (!_info.TryGetValue(webhook.Id, out var info))
            {
                return(true);
            }

            // If we're past the reset time, allow the request and update the bucket limit
            if (SystemClock.Instance.GetCurrentInstant() > info.resetTime)
            {
                if (!info.hasResetTimeExpired)
                {
                    info.remaining = info.maxLimit;
                }
                info.hasResetTimeExpired = true;
                return(true);
            }

            // If we don't have any more requests left, deny the request
            if (info.remaining == 0)
            {
                _logger.Debug("Rate limit bucket for {Webhook} out of requests, denying request", webhook.Id);
                return(false);
            }

            // Otherwise, decrement the request count and allow the request
            info.remaining--;
            return(true);
        }
Ejemplo n.º 7
0
        public async Task <IWebhook> InvalidateAndRefreshWebhook(IWebhook webhook)
        {
            _logger.Information("Refreshing webhook for channel {Channel}", webhook.ChannelId);

            _webhooks.TryRemove(webhook.ChannelId, out _);
            return(await GetWebhook(webhook.Channel));
        }
 private WebhookCreateAuditLogData(IWebhook webhook, WebhookType type, string name, ulong channelId)
 {
     Webhook   = webhook;
     Name      = name;
     Type      = type;
     ChannelId = channelId;
 }
Ejemplo n.º 9
0
 /// <summary> Creates a new Webhook Discord client. </summary>
 public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config)
     : this(config)
 {
     _webhookId = webhookId;
     ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult();
     Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult();
 }
Ejemplo n.º 10
0
 /// <summary>
 ///     Creates a new Webhook Discord client.
 /// </summary>
 /// <param name="webhookUrl">The url of the webhook.</param>
 /// <param name="config">The configuration options to use for this client.</param>
 /// <exception cref="ArgumentException">Thrown if the <paramref name="webhookUrl"/> is an invalid format.</exception>
 /// <exception cref="ArgumentNullException">Thrown if the <paramref name="webhookUrl"/> is null or whitespace.</exception>
 public DiscordWebhookClient(string webhookUrl, DiscordRestConfig config) : this(config)
 {
     string token;
     ParseWebhookUrl(webhookUrl, out _webhookId, out token);
     ApiClient.LoginAsync(TokenType.Webhook, token).GetAwaiter().GetResult();
     Webhook = WebhookClientHelper.GetWebhookAsync(this, _webhookId).GetAwaiter().GetResult();
 }
Ejemplo n.º 11
0
 public static void AssertWebhookIsNotBroken(IWebhook webhook)
 {
     if (webhook.Status == WebhookStatus.NOT_EXIST)
     {
         throw new InvalidOperationException("Attempt to interact with a nonexistent webhook");
     }
 }
        public static Task DeleteWithTokenAsync(this IWebhook webhook,
                                                IRestRequestOptions options = null, CancellationToken cancellationToken = default)
        {
            var client = webhook.GetRestClient();

            return(client.DeleteWebhookAsync(webhook.Id, webhook.Token, options, cancellationToken));
        }
Ejemplo n.º 13
0
        public static async Task <Model> ModifyAsync(IWebhook webhook, BaseDiscordClient client,
                                                     Action <WebhookProperties> func, RequestOptions options)
        {
            WebhookProperties args = new WebhookProperties();

            func(args);
            ModifyWebhookParams apiArgs = new ModifyWebhookParams
            {
                Avatar = args.Image.IsSpecified ? args.Image.Value?.ToModel() : Optional.Create <ImageModel?>(),
                Name   = args.Name
            };

            if (!apiArgs.Avatar.IsSpecified && webhook.AvatarId != null)
            {
                apiArgs.Avatar = new ImageModel(webhook.AvatarId);
            }

            if (args.Channel.IsSpecified)
            {
                apiArgs.ChannelId = args.Channel.Value.Id;
            }
            else if (args.ChannelId.IsSpecified)
            {
                apiArgs.ChannelId = args.ChannelId.Value;
            }

            return(await client.ApiClient.ModifyWebhookAsync(webhook.Id, apiArgs, options).ConfigureAwait(false));
        }
        public static Task <IWebhook> ModifyAsync(this IWebhook webhook,
                                                  Action <ModifyWebhookActionProperties> action,
                                                  IRestRequestOptions options = null, CancellationToken cancellationToken = default)
        {
            var client = webhook.GetRestClient();

            return(client.ModifyWebhookAsync(webhook.Id, action, null, options, cancellationToken));
        }
        public static Task <IUserMessage> ExecuteAsync(this IWebhook webhook,
                                                       LocalWebhookMessage message,
                                                       Snowflake?threadId          = null, bool wait = false,
                                                       IRestRequestOptions options = null, CancellationToken cancellationToken = default)
        {
            var client = webhook.GetRestClient();

            return(client.ExecuteWebhookAsync(webhook.Id, webhook.Token, message, threadId, wait, options, cancellationToken));
        }
        public static Task DeleteMessageAsync(this IWebhook webhook,
                                              Snowflake messageId,
                                              Snowflake?threadId          = null,
                                              IRestRequestOptions options = null, CancellationToken cancellationToken = default)
        {
            var client = webhook.GetRestClient();

            return(client.DeleteWebhookMessageAsync(webhook.Id, webhook.Token, messageId, threadId, options, cancellationToken));
        }
        public static Task <IUserMessage> ModifyMessageAsync(this IWebhook webhook,
                                                             Snowflake messageId, Action <ModifyWebhookMessageActionProperties> action,
                                                             Snowflake?threadId          = null,
                                                             IRestRequestOptions options = null, CancellationToken cancellationToken = default)
        {
            var client = webhook.GetRestClient();

            return(client.ModifyWebhookMessageAsync(webhook.Id, webhook.Token, messageId, action, threadId, options, cancellationToken));
        }
Ejemplo n.º 18
0
        public override void Enable()
        {
            Instance = this;

            CensusCore.CensusCore.InjectEvents();

            Webhook = WebhookProvider.CreateStaticWebhook(Config.WebhookURL);
            Log.Info($"Config Value : " +
                     $"\nURL : {Instance.Config.WebhookURL}");
        }
Ejemplo n.º 19
0
        /// <summary>
        ///     Tries to add a webhook to the collection.
        /// </summary>
        /// <param name="webhook">
        ///     Webhook instance.
        /// </param>
        /// <returns>
        ///     true if the webhook was added to the collection; otherwise, false
        /// </returns>
        public bool TryAddWebhook(IWebhook webhook)
        {
            bool allow = !(webhook is null) && !_webhooks.ContainsKey(webhook.Id);

            if (allow)
            {
                _webhooks.Add(webhook !.Id, webhook);
            }

            return(allow);
        }
Ejemplo n.º 20
0
        /// <summary>
        ///     Creates a provider.
        /// </summary>
        /// <exception cref="ArgumentNullException">
        ///     If at least one of the parameters is null.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     If no suitable implementation is found.
        /// </exception>
        public static BaseRestProvider CreateProvider(IWebhook webhook)
        {
            Contract.AssertNotNull(webhook, nameof(webhook));

            var providerType = GetProviderType();

#pragma warning disable CS8603 // Possible null reference return.
            return(Activator.CreateInstance(providerType, webhook) as BaseRestProvider);

#pragma warning restore CS8603 // Possible null reference return.
        }
Ejemplo n.º 21
0
        /// <summary>
        ///     Adds the specified webhook to the collection,
        ///     useful when implementing a custom webhook.
        /// </summary>
        /// <param name="webhook">
        ///     Webhook instance.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     If the webhook is null.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        ///     If the webhook is already in the collection.
        /// </exception>
        public void AddWebhook(IWebhook webhook)
        {
            if (webhook is null)
            {
                throw new ArgumentNullException(nameof(webhook), "The webhook can't be null");
            }
            if (_webhooks.ContainsKey(webhook.Id))
            {
                throw new InvalidOperationException("The webhook is already contained in the collection");
            }

            _webhooks.Add(webhook.Id, webhook);
        }
Ejemplo n.º 22
0
        private void CacheWebhook(object key, IWebhook webhook)
        {
            CacheInstance(key, webhook);

            if (!webhook.User.HasValue)
            {
                return;
            }

            var user    = webhook.User.Value;
            var userKey = KeyHelpers.CreateUserCacheKey(user.ID);

            Cache(userKey, user);
        }
        public void ToMappingModel_With_SingleWebHook()
        {
            // Assign
            var request  = Request.Create();
            var response = Response.Create();
            var webhooks = new IWebhook[]
            {
                new Webhook
                {
                    Request = new WebhookRequest
                    {
                        Url     = "https://test.com",
                        Headers = new Dictionary <string, WireMockList <string> >
                        {
                            { "Single", new WireMockList <string>("x") },
                            { "Multi", new WireMockList <string>("a", "b") }
                        },
                        Method   = "post",
                        BodyData = new BodyData
                        {
                            BodyAsString     = "b",
                            DetectedBodyType = BodyType.String,
                            DetectedBodyTypeFromContentType = BodyType.String
                        }
                    }
                }
            };
            var mapping = new Mapping(Guid.NewGuid(), "", null, _settings, request, response, 0, null, null, null, null, webhooks);

            // Act
            var model = _sut.ToMappingModel(mapping);

            // Assert
            model.Should().NotBeNull();
            model.Priority.Should().BeNull();

            model.Response.BodyAsJsonIndented.Should().BeNull();
            model.Response.UseTransformer.Should().BeNull();
            model.Response.Headers.Should().BeNull();

            model.Webhooks.Should().BeNull();

            model.Webhook.Request.Method.Should().Be("post");
            model.Webhook.Request.Url.Should().Be("https://test.com");
            model.Webhook.Request.Headers.Should().HaveCount(2);
            model.Webhook.Request.Body.Should().Be("b");
            model.Webhook.Request.BodyAsJson.Should().BeNull();
        }
Ejemplo n.º 24
0
        private async Task <DiscordWebhookClient> CreateWebhookAsync(string name, ITextChannel chan)
        {
            try
            {
                if (!CanUseWebhooks(chan))
                {
                    return(null);
                }

                IWebhook webhook = await chan.CreateWebhookAsync(name);

                return(new DiscordWebhookClient(webhook));
            }
            catch (Exception ex)
            {
                this.Logger.Nice("Webhook", ConsoleColor.Red, $"Could not create webhook: {ex.Message}");
                return(null);
            }
        }
Ejemplo n.º 25
0
        public async Task SendEmojiWithName(string name)
        {
            if (!Emojis.TryGetValue(name, out string emoji))
            {
                await ReplyAsync($"Das Emoji {name} wurde nicht gefunden!");

                return;
            }

            var cxt = base.Context;

            IWebhook webhook = await GetWebhook((ITextChannel)cxt.Channel, (IGuildUser)cxt.User);

            var webhookClient = new DiscordWebhookClient(webhook);

            await cxt.Channel.DeleteMessageAsync(base.Context.Message);

            await webhookClient.SendMessageAsync(emoji, avatarUrl : cxt.User.GetAvatarUrl());
        }
        public void UpdateRateLimitInfo(IWebhook webhook, HttpResponseMessage response)
        {
            var info = _info.GetOrAdd(webhook.Id, _ => new WebhookRateLimitInfo());

            if (int.TryParse(GetHeader(response, "X-RateLimit-Limit"), out var limit))
            {
                info.maxLimit = limit;
            }

            // Max "safe" is way above UNIX timestamp values, and we get fractional seconds, hence the double
            // but need culture/format specifiers to get around Some Locales (cough, my local PC) having different settings for decimal point...
            // We also use Reset-After to avoid issues with clock desync between us and Discord's server, this way it's all relative (plus latency errors work in our favor)
            if (double.TryParse(GetHeader(response, "X-RateLimit-Reset-After"), NumberStyles.Float, CultureInfo.InvariantCulture, out var resetTimestampDelta))
            {
                var resetTime = SystemClock.Instance.GetCurrentInstant() + Duration.FromSeconds(resetTimestampDelta);
                if (resetTime > info.resetTime)
                {
                    // Set to the *latest* reset value we have (for safety), since we rely on relative times this can jitter a bit
                    info.resetTime           = resetTime;
                    info.hasResetTimeExpired = false;
                }
            }

            if (int.TryParse(GetHeader(response, "X-RateLimit-Remaining"), out var remainingRequests))
            {
                // Overwrite a negative "we don't know" value with whatever we just got
                // Otherwise, *lower* remaining requests takes precedence
                if (info.remaining < 0 || remainingRequests < info.remaining)
                {
                    info.remaining = remainingRequests;
                }
            }

            _logger.Debug("Updated rate limit information for {Webhook}, bucket has {RequestsRemaining} requests remaining, reset in {ResetTime}", webhook.Id, info.remaining, info.resetTime - SystemClock.Instance.GetCurrentInstant());

            if (response.StatusCode == HttpStatusCode.TooManyRequests)
            {
                // 429, we're *definitely* out of requests
                info.remaining = 0;
                _logger.Warning("Got 429 Too Many Requests when invoking webhook {Webhook}, next bucket reset in {ResetTime}", webhook.Id, info.resetTime - SystemClock.Instance.GetCurrentInstant());
            }
        }
Ejemplo n.º 27
0
        private async Task <DiscordWebhookClient> GetOrCreateWebhookAsync(string name, ITextChannel chan)
        {
            try
            {
                if (!CanUseWebhooks(chan))
                {
                    return(null);
                }

                IReadOnlyCollection <IWebhook> webhooks = await chan.GetWebhooksAsync();

                IWebhook webhook = webhooks.FirstOrDefault(x => x.Name == name);
                return(webhook == null ? await this.CreateWebhookAsync(name, chan) : new DiscordWebhookClient(webhook));
            }
            catch (Exception ex)
            {
                this.Logger.Nice("Webhook", ConsoleColor.Red, $"Could not get a list of webhooks: {ex.Message}");
                return(null);
            }
        }
Ejemplo n.º 28
0
        /// <summary>
        /// Caches a value. Certain instance types may have specializations which cache more than one value from the
        /// instance.
        /// </summary>
        /// <param name="key">The cache key.</param>
        /// <param name="instance">The instance.</param>
        /// <typeparam name="TInstance">The instance type.</typeparam>
        public void Cache <TInstance>(object key, TInstance instance)
            where TInstance : class
        {
            Action cacheAction = instance switch
            {
                IWebhook webhook => () => CacheWebhook(key, webhook),
                ITemplate template => () => CacheTemplate(key, template),
                IIntegration integration => () => CacheIntegration(key, integration),
                IBan ban => () => CacheBan(key, ban),
                IGuildMember member => () => CacheGuildMember(key, member),
                IGuildPreview preview => () => CacheGuildPreview(key, preview),
                IGuild guild => () => CacheGuild(key, guild),
                IEmoji emoji => () => CacheEmoji(key, emoji),
                IInvite invite => () => CacheInvite(key, invite),
                IMessage message => () => CacheMessage(key, message),
                IChannel channel => () => CacheChannel(key, channel),
                _ => () => CacheInstance(key, instance)
            };

            cacheAction();
        }
Ejemplo n.º 29
0
        /// <see cref="IRespondWithAProvider.WithWebhook(string, string, IDictionary{string, WireMockList{string}}, object, bool, TransformerType)"/>
        public IRespondWithAProvider WithWebhook(
            [NotNull] string url,
            [CanBeNull] string method = "post",
            [CanBeNull] IDictionary <string, WireMockList <string> > headers = null,
            [CanBeNull] object body         = null,
            bool useTransformer             = true,
            TransformerType transformerType = TransformerType.Handlebars)
        {
            Webhook = InitWebhook(url, method, headers, useTransformer, transformerType);

            if (body != null)
            {
                Webhook.Request.BodyData = new BodyData
                {
                    BodyAsJson       = body,
                    DetectedBodyType = BodyType.Json,
                    DetectedBodyTypeFromContentType = BodyType.Json
                };
            }

            return(this);
        }
Ejemplo n.º 30
0
        public static WebhookModel Map(IWebhook webhook)
        {
            if (webhook?.Request == null)
            {
                return(null);
            }

            var model = new WebhookModel
            {
                Request = new WebhookRequestModel
                {
                    Url             = webhook.Request.Url,
                    Method          = webhook.Request.Method,
                    Headers         = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
                    UseTransformer  = webhook.Request.UseTransformer,
                    TransformerType = webhook.Request.UseTransformer == true?webhook.Request.TransformerType.ToString() : null
                }
            };

            if (webhook.Request.BodyData != null)
            {
                switch (webhook.Request.BodyData.DetectedBodyType)
                {
                case BodyType.String:
                    model.Request.Body = webhook.Request.BodyData.BodyAsString;
                    break;

                case BodyType.Json:
                    model.Request.BodyAsJson = webhook.Request.BodyData.BodyAsJson;
                    break;

                default:
                    break;
                }
            }

            return(model);
        }