diff --git a/HellionChat/EmoteCache.cs b/HellionChat/EmoteCache.cs index 0af31b7..260da90 100644 --- a/HellionChat/EmoteCache.cs +++ b/HellionChat/EmoteCache.cs @@ -125,6 +125,15 @@ public static class EmoteCache try { var global = await Client.GetAsync(GlobalEmotes, ct); + if (!global.IsSuccessStatusCode) + { + // Nothing usable at all -- let the catch below reset to Unloaded + // so a later trigger can retry. + throw new HttpRequestException( + $"BetterTTV global emotes returned {(int)global.StatusCode}." + ); + } + var globalList = await global.Content.ReadAsStringAsync(ct); foreach (var emote in JsonSerializer.Deserialize(globalList)!) @@ -135,9 +144,27 @@ public static class EmoteCache for (var i = 0; i < 15; i++) { var top = await Client.GetAsync(Top100Emotes.Format(BetterTTV, lastId), ct); + + // The shared-emote endpoint went behind authentication and now + // answers 403 with a JSON object. Deserializing that as a list + // threw on every single start, and took the global emotes -- which + // still work -- down with it. The global set is the useful half + // anyway, so a failure here stops paging instead of the load. + if (!top.IsSuccessStatusCode) + { + Plugin.LogProxy.Warning( + $"BetterTTV shared emotes unavailable ({(int)top.StatusCode}); " + + "continuing with global emotes only." + ); + break; + } + var topList = await top.Content.ReadAsStringAsync(ct); - var jsonList = JsonSerializer.Deserialize>(topList)!; + var jsonList = JsonSerializer.Deserialize>(topList); + if (jsonList is not { Count: > 0 }) + break; + // BetterTTV occasionally returns entries with a null Code; // skip them so a single bad row doesn't break the whole cache. foreach (var emote in jsonList) @@ -147,7 +174,7 @@ public static class EmoteCache ) Cache.TryAdd(emote.Emote.Code, emote.Emote); - lastId = jsonList.Last().Id; + lastId = jsonList[^1].Id; } SortedCodeArray = Cache.Keys.Order().ToArray();