Skip to content

Commit

Permalink
Added Twitter API limit warning
Browse files Browse the repository at this point in the history
  • Loading branch information
Meisterlala committed Jan 12, 2023
1 parent f4db914 commit 48ca227
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 3 deletions.
17 changes: 17 additions & 0 deletions Neko/Gui/ImageSourcesWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public ImageSourceConfig(string name, string description, string help, Type type

private readonly HeaderImage.Individual Header = new();

private static DateTime TwitterTimeout = DateTime.MinValue;

public void Draw()
{
// ------------ Header --------------
Expand Down Expand Up @@ -105,6 +107,21 @@ public void Draw()
DrawTheCatAPI();
// ------------ Twitter --------------
SourceCheckbox(SourceList[9], ref Plugin.Config.Sources.Twitter.enabled);
if (Twitter.IsRateLimited)
{
ImGui.SameLine();
ImGui.TextColored(new Vector4(1f, 0, 0f, 1f), "API limit reached");
ImGui.SameLine();
Common.HelpMarker("The free Twitter API is limited to 2 million tweets per Month. This limit is shared between all users of this plugin and will usually be reset on the 26st of every month.");
// Make the Twitter config unable to open
if (Plugin.Config.Sources.Twitter.enabled)
{
Plugin.Config.Sources.Twitter.enabled = false;
Plugin.Config.Save();
Plugin.UpdateImageSource();
}
}

if (Plugin.Config.Sources.Twitter.enabled)
DrawTwitter();

Expand Down
11 changes: 11 additions & 0 deletions Neko/Gui/MainWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ public MainWindow()
imageGrayed = false;
}

~MainWindow()
{
Dispose();
}

public void Dispose()
{
Slideshow.Stop();
Queue.Dispose();
}

public void Draw()
{
if (!Visible) return;
Expand Down
22 changes: 22 additions & 0 deletions Neko/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using Dalamud.Logging;
using TextCopy;
Expand Down Expand Up @@ -142,4 +143,25 @@ public static string EndWithEllipsis(string text, int maxLength)
? text
: text[..(maxLength - 3)] + "...";
}

public static HttpRequestMessage RequestClone(HttpRequestMessage req)
{
var clone = new HttpRequestMessage(req.Method, req.RequestUri)
{
Content = req.Content,
Version = req.Version
};

foreach (var prop in req.Options)
{
clone.Options.TryAdd(prop.Key, prop.Value);
}

foreach (var header in req.Headers)
{
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
}

return clone;
}
}
7 changes: 7 additions & 0 deletions Neko/NekoQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public NekoQueue()
tokenSource.Cancel();
}

public void Dispose()
{
tokenSource.Cancel();
tokenSource = new();
StopQueue = true;
}

public override string ToString()
{
var res = $"Queue length: {TargetDownloadCount} preloaded: {TargetPreloadCount}{(StopQueue ? " Queue Stopped" : "")}";
Expand Down
2 changes: 1 addition & 1 deletion Neko/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void Dispose()
CommandManager.RemoveHandler(CommandMain);

// Stop loading images
GuiMain?.Slideshow?.Stop();
GuiMain?.Dispose();
}

public static void UpdateImageSource() => ImageSource.UpdateFrom(Config.LoadSources());
Expand Down
25 changes: 25 additions & 0 deletions Neko/Sources/APIS/Twitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,25 @@ private static HttpRequestMessage AuthorizedRequest(string url) =>

public override bool SameAs(ImageSource other) => other is Twitter t && t.ConfigQuery == ConfigQuery;

/// <summary>
/// Checks the header to see if the response is from Twitter rate limiting
/// </summary>
/// <param name="response">response Message</param>
/// <returns>True if the header is a response from Twitter</returns>
public static bool Is429Response(HttpResponseMessage response) =>
response.RequestMessage?.RequestUri?.Host == "api.twitter.com" &&
response.Headers.TryGetValues("x-rate-limit-remaining", out var remaining) &&
remaining != null &&
response.Headers.TryGetValues("x-rate-limit-reset", out var reset) &&
reset != null &&
response.Headers.TryGetValues("x-rate-limit-limit", out var limit) &&
limit != null;

/// <summary>
/// Checks if the API is rate limited
/// </summary>
public static bool IsRateLimited;

public class Config : IImageConfig
{
public bool enabled;
Expand Down Expand Up @@ -207,6 +226,9 @@ public override NekoImage Next(CancellationToken ct = default)
{
return new NekoImage(async (img) =>
{
if (IsRateLimited)
throw new Exception("Twitter API rate limit exceeded");

var searchResult = await URLs.GetURL(ct).ConfigureAwait(false);
var response = await Download.DownloadImage(searchResult.Media.Url, typeof(Search), ct).ConfigureAwait(false);
img.Description = searchResult.TweetDescription();
Expand Down Expand Up @@ -438,6 +460,9 @@ public override NekoImage Next(CancellationToken ct = default)
{
return new NekoImage(async (img) =>
{
if (IsRateLimited)
throw new Exception("Twitter API rate limit exceeded");

lock (userIDTaskLock)
{
userIDTask ??= GetUserID(ct);
Expand Down
16 changes: 14 additions & 2 deletions Neko/Sources/Download.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ public static async Task<T> ParseJson<T>(HttpRequestMessage request, Cancellatio
// Handle 429 (Too Many Requests) by waiting and retrying
if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
DebugHelper.LogNetwork(() => "API retuned 429 (Too Many Requests)\n" + response.Headers.ToString());

var retryAfter = 2000; // in ms
// Respect timeout header for WAIFU.IM
if (response.Headers.TryGetValues("Retry-After", out var values) && values.Any())
Expand All @@ -109,10 +111,20 @@ public static async Task<T> ParseJson<T>(HttpRequestMessage request, Cancellatio
retryAfter = (int)(seconds * 1000);
}

// Twitter API limit reached
if (APIS.Twitter.Is429Response(response))
{
APIS.Twitter.IsRateLimited = true;
throw new Exception("Twitter API limit reached. Wait a few days until the limit gets reset", ex);
}

PluginLog.LogInformation($"API retuned 429 (Too Many Requests). Waiting {retryAfter / 1000.0} seconds before trying again.");
// Wait 2 seconds and retry
PluginLog.LogVerbose($"API retuned 429 (Too Many Requests). Waiting {retryAfter / 1000.0} seconds before trying again.");
await Task.Delay(retryAfter, ct).ConfigureAwait(false);
return await ParseJson<T>(request, ct).ConfigureAwait(false);
ct.ThrowIfCancellationRequested();
// Clone request, because you cant send the same one twice
var newRequest = Helper.RequestClone(request);
return await ParseJson<T>(newRequest, ct).ConfigureAwait(false);
}

DebugHelper.LogNetwork(() => $"Error Downloading Json from {request.RequestUri}:\n{JsonSerializer.Serialize(response.Content.ReadAsStringAsync(ct).Result, new JsonSerializerOptions() { WriteIndented = true })}");
Expand Down

0 comments on commit 48ca227

Please sign in to comment.