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)); } })); }
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; } }
/// <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; }
/// <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); }
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; }
/// <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(); }
/// <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(); }
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)); }
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)); }
public override void Enable() { Instance = this; CensusCore.CensusCore.InjectEvents(); Webhook = WebhookProvider.CreateStaticWebhook(Config.WebhookURL); Log.Info($"Config Value : " + $"\nURL : {Instance.Config.WebhookURL}"); }
/// <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); }
/// <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. }
/// <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); }
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(); }
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); } }
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()); } }
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); } }
/// <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(); }
/// <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); }
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); }