Files
URLNotesGrabberCore/URLNotesGrabberCORE/ResponseNotes.cs
T
jimandClaude Opus 4.8 003a504d5e fix: retry transient CDN failures instead of failing the post
A non-JSON response body (CDN 403/5xx HTML, empty body, transport error)
never reached the Tumblr API, so it says nothing about the post being
fetched. These were recorded as FAILURE, which consumed the post's single
attempt for the pass and cleared the API key's rate-limit flag on the way
through.

Classify them as Root.transientFailure and retry in place (1s/4s/10s)
before skipping. Skipped posts stay unmarked in the DB so a later launch
retries them. Ten consecutive transient failures now aborts the pass
rather than skipping post-by-post against an edge refusing all traffic.

Also:
- MarkAvailable() only on a response that reached the API, and it is now
  a no-op when the key was not flagged (was writing to the DB and logging
  on every single call)
- Only a real 429 counts as a rate limit; stop inferring one from
  X-RateLimit-* headers, which Tumblr sends on every response
- Limiters pace with AcquireAsync instead of AttemptAcquire, which did
  not wait and aborted the run once a window was saturated
- Throttle --collect and --likes from 300/min to 60/min
- Log one line per transient failure instead of the HTML body and stack
  trace; keep full detail only for a 2xx that fails to parse
- --collect returns exit 3 when a pass ends incomplete

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-22 12:10:49 -05:00

145 lines
3.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace URLNotesGrabberCORE
{
internal class ResponseNotes
{
}
// Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
public class AvatarUrl
{
[JsonProperty("64")]
public string _64 { get; set; }
[JsonProperty("128")]
public string _128 { get; set; }
}
public class Links
{
public Next next { get; set; }
}
public class Meta
{
public int status { get; set; }
public string msg { get; set; }
}
public class Next
{
public string href { get; set; }
public string method { get; set; }
public QueryParams query_params { get; set; }
}
public class Note
{
public string type { get; set; }
public long timestamp { get; set; }
public string blog_name { get; set; }
public string blog_uuid { get; set; }
public string blog_url { get; set; }
public bool followed { get; set; }
public string avatar_shape { get; set; }
public AvatarUrl avatar_url { get; set; }
public string post_id { get; set; }
public string reblog_parent_blog_name { get; set; }
public string reply_text { get; set; }
}
public class QueryParams
{
public string mode { get; set; }
public string id { get; set; }
public string before_timestamp { get; set; }
public string before { get; set; }
public string after { get; set; }
}
public class Response
{
public List<Note> notes { get; set; }
public int total_notes { get; set; }
public bool is_subscribed { get; set; }
public bool can_subscribe { get; set; }
public bool can_hide_or_delete_notes { get; set; }
public bool conversational_notifications_enabled { get; set; }
public Links _links { get; set; }
}
public class Root
{
public Meta meta { get; set; }
[JsonConverter(typeof(EmptyArrayOrObjectConverter<Response>))]
public Response response { get; set; }
public string statusCode { get; set; }
public int retryInSeconds { get; set; }
public string rawJson { get; set; }
// The request never reached the Tumblr API (transport error, or an edge/CDN response with a
// non-JSON body). Says nothing about the post, so the caller should retry rather than fail it.
public bool transientFailure { get; set; }
}
// Classes for Posts API endpoint (for reply_text)
public class PostsResponse
{
public List<Post> posts { get; set; }
}
public class Post
{
public long id { get; set; }
public List<NoteDetail> notes { get; set; }
}
public class NoteDetail
{
public long timestamp { get; set; }
public string type { get; set; }
public string blog_name { get; set; }
public string reply_text { get; set; }
}
public class PostsRoot
{
public Meta meta { get; set; }
[JsonConverter(typeof(EmptyArrayOrObjectConverter<PostsResponse>))]
public PostsResponse response { get; set; }
public string statusCode { get; set; }
public int retryInSeconds { get; set; }
public string rawJson { get; set; }
}
public class LikesResponse
{
public List<dynamic> liked_posts { get; set; }
public int liked_count { get; set; }
public Links _links { get; set; }
}
public class LikesRoot
{
public Meta meta { get; set; }
[JsonConverter(typeof(EmptyArrayOrObjectConverter<LikesResponse>))]
public LikesResponse response { get; set; }
public string statusCode { get; set; }
public int retryInSeconds { get; set; }
public string rawJson { get; set; }
}
}