Add byLikes tracking to blogs and posts in DataAccess

Introduce byLikes parameter to AddBlog, AddPost, and UpdatePost methods, updating SQL logic and schema to store this flag. Ensure all relevant calls and SQL statements handle the new ByLikes column, allowing tracking of whether entries were added "by likes." Also standardize hasImage boolean handling in SQL.

Add Tumblr likes fetching and DB tracking support

Implemented -likes command to fetch/process Tumblr blog likes.
Added DB columns and logic to track likes progress (LikesPulled, LikesCursor).
Integrated API call, pagination, and rate-limit handling for likes.
Extended AddBlog/AddPost/UpdatePost for likes-related fields.
Added DateModified tracking to DB operations.
Improved error handling and updated launch/app settings.
This commit is contained in:
jim
2026-04-02 10:57:13 -05:00
parent 4e87daf2b2
commit eb86bc8f66
5 changed files with 588 additions and 25 deletions
+304 -23
View File
@@ -158,8 +158,57 @@ namespace URLNotesGrabberCORE
}
}
public static void EnsureBlogsLikesColumnsExist(string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
try
{
connection.Open();
string checkSql = "PRAGMA table_info(Blogs);";
bool likesPulledExists = false;
bool likesCursorExists = false;
using (SQLiteCommand command = new SQLiteCommand(checkSql, connection))
{
using (SQLiteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
string columnName = reader.GetString(1);
if (columnName.Equals("LikesPulled", StringComparison.OrdinalIgnoreCase)) likesPulledExists = true;
if (columnName.Equals("LikesCursor", StringComparison.OrdinalIgnoreCase)) likesCursorExists = true;
}
}
}
if (!likesPulledExists)
{
string addCol = "ALTER TABLE Blogs ADD COLUMN LikesPulled INTEGER DEFAULT 0;";
using (SQLiteCommand cmd = new SQLiteCommand(addCol, connection)) cmd.ExecuteNonQuery();
Console.WriteLine("[Migration] Added LikesPulled column to Blogs table");
}
if (!likesCursorExists)
{
string addCol = "ALTER TABLE Blogs ADD COLUMN LikesCursor INTEGER DEFAULT 0;";
using (SQLiteCommand cmd = new SQLiteCommand(addCol, connection)) cmd.ExecuteNonQuery();
Console.WriteLine("[Migration] Added LikesCursor column to Blogs table");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error mapping Blogs likes columns: {ex.Message}");
}
finally
{
connection.Close();
}
}
#region Adds
public static void AddBlog(string blogName, string DBPath = @"TL.db")
public static void AddBlog(string blogName, bool byLikes = false, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -171,11 +220,13 @@ namespace URLNotesGrabberCORE
connection.Open();
// Insert BlogName and DateAdded (current UTC datetime)
string sql = "INSERT OR IGNORE INTO Blogs (BlogName, DateAdded) VALUES (@BlogName, @DateAdded)";
string sql = "INSERT OR IGNORE INTO Blogs (BlogName, DateAdded, DateModified, ByLikes) VALUES (@BlogName, @DateAdded, @DateModified, @ByLikes)";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@DateAdded", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@ByLikes", byLikes ? 1 : 0);
command.ExecuteNonQuery();
}
// Console.WriteLine("+ " + blogName);
@@ -192,11 +243,11 @@ namespace URLNotesGrabberCORE
}
}
public static void AddPost(string blogName, long postID, string reblogURL, string postDate, string postURL, string slug, string reblogKey, string reblogName, string summary, string quote, string body, string tags, string link, string photoURL, string photoCaption, string downloadedFiles, string audioCaption, string question, string answer, string title, bool hasImage, string DBPath = @"TL.db")
public static void AddPost(string blogName, long postID, string reblogURL, string postDate, string postURL, string slug, string reblogKey, string reblogName, string summary, string quote, string body, string tags, string link, string photoURL, string photoCaption, string downloadedFiles, string audioCaption, string question, string answer, string title, bool hasImage, bool byLikes = false, string DBPath = @"TL.db", string? rootBlogName = null, string? rootURL = null)
{
try { AddBlog(blogName, DBPath); } catch { }
try { AddBlog(blogName, byLikes, DBPath); } catch { }
try { UpdatePostSetDate(blogName, postID, postDate, DBPath); } catch { }
try { UpdatePost(blogName, postID, reblogURL, postDate, postURL, slug, reblogKey, reblogName, summary, quote, body, tags, link, photoURL, photoCaption, downloadedFiles, audioCaption, question, answer, title, hasImage, DBPath); } catch { }
try { UpdatePost(blogName, postID, reblogURL, postDate, postURL, slug, reblogKey, reblogName, summary, quote, body, tags, link, photoURL, photoCaption, downloadedFiles, audioCaption, question, answer, title, hasImage, byLikes, DBPath, rootBlogName, rootURL); } catch { }
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -225,9 +276,13 @@ namespace URLNotesGrabberCORE
Question,
Answer,
Title,
HasImage
DateModified,
RootBlogName,
RootURL,
HasImage,
ByLikes
) VALUES (" +
Q(blogName) + ", " + postID + ", " + Q(reblogURL) + ", " + Q(postDate) + ", " + Q(postURL) + ", " + Q(slug) + ", " + Q(reblogKey) + ", " + Q(reblogName) + ", " + Q(summary) + ", " + Q(quote) + ", " + Q(body) + ", " + Q(tags) + ", " + Q(link) + ", " + Q(photoURL) + ", " + Q(photoCaption) + ", " + Q(downloadedFiles) + ", " + Q(audioCaption) + ", " + Q(question) + ", " + Q(answer) + ", " + Q(title) + ", " + hasImage + ")";
Q(blogName) + ", " + postID + ", " + Q(reblogURL) + ", " + Q(postDate) + ", " + Q(postURL) + ", " + Q(slug) + ", " + Q(reblogKey) + ", " + Q(reblogName) + ", " + Q(summary) + ", " + Q(quote) + ", " + Q(body) + ", " + Q(tags) + ", " + Q(link) + ", " + Q(photoURL) + ", " + Q(photoCaption) + ", " + Q(downloadedFiles) + ", " + Q(audioCaption) + ", " + Q(question) + ", " + Q(answer) + ", " + Q(title) + ", " + Q(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) + ", " + Q(rootBlogName ?? ".") + ", " + Q(rootURL ?? ".") + ", " + (hasImage ? 1 : 0) + ", " + (byLikes ? 1 : 0) + ")";
SQLiteCommand command = new SQLiteCommand(sql, connection);
int rowsInserted = 0;
@@ -246,7 +301,7 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string updateSql = "UPDATE Posts SET hasImage = " + hasImage + "WHERE blogName = '" + blogName + "' AND postID = '" + postID + "'";
string updateSql = "UPDATE Posts SET hasImage = " + hasImage + ", DateModified = '" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "' WHERE blogName = '" + blogName + "' AND postID = '" + postID + "'";
SQLiteCommand updateCommand = new SQLiteCommand(updateSql, connection);
updateCommand.ExecuteNonQuery();
@@ -272,11 +327,12 @@ namespace URLNotesGrabberCORE
{
try
{
string updateBlogSql = "UPDATE Blogs SET HasBeenOutput = 0, DateAdded = @DateAdded WHERE BlogName = @BlogName";
string updateBlogSql = "UPDATE Blogs SET HasBeenOutput = 0, DateAdded = @DateAdded, DateModified = @DateModified WHERE BlogName = @BlogName";
using (var updateBlogCommand = new SQLiteCommand(updateBlogSql, connection))
{
updateBlogCommand.Parameters.AddWithValue("@BlogName", blogName);
updateBlogCommand.Parameters.AddWithValue("@DateAdded", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
updateBlogCommand.Parameters.AddWithValue("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
updateBlogCommand.ExecuteNonQuery();
}
}
@@ -289,7 +345,6 @@ namespace URLNotesGrabberCORE
}
}
public static void AddAPICount(string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -321,7 +376,7 @@ namespace URLNotesGrabberCORE
public static bool AddNote(string rootBlogName, string noteBlogName, long postID, long timestamp, string type, string DBPath = @"TL.db")
{
//try { AddPost(rootBlogName, postID, DBPath); } catch { }
try { AddBlog(noteBlogName, DBPath); } catch { }
try { AddBlog(noteBlogName, false, DBPath); } catch { }
Console.WriteLine("{2}\t{0}\t{1}", UnixTimeStampToDateTime(timestamp), noteBlogName, type);
@@ -331,7 +386,7 @@ namespace URLNotesGrabberCORE
{
connection2.Open();
string sql = "INSERT OR IGNORE INTO Notes (rootBlogName, noteBlogName, PostID, TimeStamp, Type, DatetimeCrawled) values(@rootBlogName, @noteBlogName, @PostID, @TimeStamp, @Type, @DatetimeCrawled)";
string sql = "INSERT OR IGNORE INTO Notes (rootBlogName, noteBlogName, PostID, TimeStamp, Type, DatetimeCrawled, DateModified) values(@rootBlogName, @noteBlogName, @PostID, @TimeStamp, @Type, @DatetimeCrawled, @DateModified)";
using (SQLiteCommand command = new SQLiteCommand(sql, connection2))
{
command.Parameters.AddWithValue("@rootBlogName", rootBlogName);
@@ -340,6 +395,7 @@ namespace URLNotesGrabberCORE
command.Parameters.AddWithValue("@TimeStamp", timestamp);
command.Parameters.AddWithValue("@Type", type ?? string.Empty);
command.Parameters.AddWithValue("@DatetimeCrawled", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
int rowsInserted = command.ExecuteNonQuery();
@@ -348,10 +404,11 @@ namespace URLNotesGrabberCORE
{
try
{
string updateSql = "UPDATE Blogs SET HasBeenOutput = 0 WHERE BlogName = @BlogName";
string updateSql = "UPDATE Blogs SET HasBeenOutput = 0, DateModified = @DateModified WHERE BlogName = @BlogName";
using (var updateCommand = new SQLiteCommand(updateSql, connection2))
{
updateCommand.Parameters.AddWithValue("@BlogName", noteBlogName);
updateCommand.Parameters.AddWithValue("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
updateCommand.ExecuteNonQuery();
}
}
@@ -698,6 +755,49 @@ namespace URLNotesGrabberCORE
return count;
}
public static List<Tuple<string, int, long>> GetBlogsForLikes(string specificBlog = null, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
List<Tuple<string, int, long>> blogs = new List<Tuple<string, int, long>>();
try
{
connection.Open();
string sql;
if (!string.IsNullOrEmpty(specificBlog))
sql = "SELECT BlogName, COALESCE(LikesPulled, 0), COALESCE(LikesCursor, 0) FROM Blogs WHERE BlogName = @blog";
else
sql = "SELECT BlogName, COALESCE(LikesPulled, 0), COALESCE(LikesCursor, 0) FROM Blogs WHERE COALESCE(LikesPulled, 0) = 0";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
if (!string.IsNullOrEmpty(specificBlog))
command.Parameters.AddWithValue("@blog", specificBlog);
using (SQLiteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
blogs.Add(new Tuple<string, int, long>(
reader.GetString(0),
reader.GetInt32(1),
reader.GetInt64(2)
));
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching blogs for likes: {ex.Message}");
}
finally
{
connection.Close();
}
return blogs;
}
public static List<string> GetBlogs(bool reblogsOnly, int from, int to, int top, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -799,10 +899,11 @@ namespace URLNotesGrabberCORE
connection.Open();
//string sql = "UPDATE Posts SET HasNotesGathered = 1, NotesGatheredDateTime = @notesGathered WHERE BlogName = @BlogName AND PostID = @PostID";
string sql = "UPDATE Posts SET HasNotesGathered = 1, NotesGatheredDateTime = @notesGathered WHERE PostID = @PostID";
string sql = "UPDATE Posts SET HasNotesGathered = 1, NotesGatheredDateTime = @notesGathered, DateModified = @dateModified WHERE PostID = @PostID";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@notesGathered", DateTimeOffset.UtcNow.ToUnixTimeSeconds());
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@PostID", postID);
command.ExecuteNonQuery();
@@ -831,9 +932,10 @@ namespace URLNotesGrabberCORE
connection.Open();
string sql = "UPDATE Posts SET NotFound = 1 WHERE BlogName = @BlogName AND PostID = @PostID";
string sql = "UPDATE Posts SET NotFound = 1, DateModified = @dateModified WHERE BlogName = @BlogName AND PostID = @PostID";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@PostID", postID);
command.ExecuteNonQuery();
@@ -859,10 +961,11 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string sql = "UPDATE Posts SET postDate = @postDate WHERE BlogName = @BlogName AND PostID = @PostID";
string sql = "UPDATE Posts SET postDate = @postDate, DateModified = @dateModified WHERE BlogName = @BlogName AND PostID = @PostID";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@postDate", postDate ?? string.Empty);
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@PostID", postID);
command.ExecuteNonQuery();
@@ -883,7 +986,7 @@ namespace URLNotesGrabberCORE
public static bool UpdateNote(string rootBlogName, string noteBlogName, long postID, long timestamp, string type, string DBPath = @"TL.db")
{
//try { AddPost(rootBlogName, postID, DBPath); } catch { }
try { AddBlog(noteBlogName, DBPath); } catch { }
try { AddBlog(noteBlogName, false, DBPath); } catch { }
Console.WriteLine("{2}\t{0}\t{1}", UnixTimeStampToDateTime(timestamp), noteBlogName, type);
@@ -893,10 +996,11 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string sql = "UPDATE Notes SET timestamp = @timestamp WHERE rootBlogName = @rootBlogName AND noteBlogName = @noteBlogName AND PostID = @postID";
string sql = "UPDATE Notes SET timestamp = @timestamp, DateModified = @dateModified WHERE rootBlogName = @rootBlogName AND noteBlogName = @noteBlogName AND PostID = @postID";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@timestamp", timestamp);
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@rootBlogName", rootBlogName);
command.Parameters.AddWithValue("@noteBlogName", noteBlogName);
command.Parameters.AddWithValue("@postID", postID);
@@ -920,7 +1024,7 @@ namespace URLNotesGrabberCORE
return false;
}
public static void UpdatePost(string blogName, long postID, string reblogURL, string postDate, string postURL, string slug, string reblogKey, string reblogName, string summary, string quote, string body, string tags, string link, string photoURL, string photoCaption, string downloadedFiles, string audioCaption, string question, string answer, string title, bool hasImage, string DBPath = @"TL.db")
public static void UpdatePost(string blogName, long postID, string reblogURL, string postDate, string postURL, string slug, string reblogKey, string reblogName, string summary, string quote, string body, string tags, string link, string photoURL, string photoCaption, string downloadedFiles, string audioCaption, string question, string answer, string title, bool hasImage, bool byLikes = false, string DBPath = @"TL.db", string? rootBlogName = null, string? rootURL = null)
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -947,7 +1051,11 @@ namespace URLNotesGrabberCORE
sql += "question = @question, ";
sql += "answer = @answer, ";
sql += "title = @title, ";
sql += "hasImage = @hasImage ";
sql += "DateModified = @dateModified, ";
sql += "RootBlogName = CASE WHEN @rootBlogName IS NULL OR @rootBlogName = '' OR @rootBlogName = '.' THEN RootBlogName ELSE @rootBlogName END, ";
sql += "RootURL = CASE WHEN @rootURL IS NULL OR @rootURL = '' OR @rootURL = '.' THEN RootURL ELSE @rootURL END, ";
sql += "hasImage = @hasImage, ";
sql += "ByLikes = @byLikes ";
sql += " WHERE BlogName = @BlogName AND PostID = @PostID";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
@@ -970,7 +1078,11 @@ namespace URLNotesGrabberCORE
command.Parameters.AddWithValue("@question", question ?? string.Empty);
command.Parameters.AddWithValue("@answer", answer ?? string.Empty);
command.Parameters.AddWithValue("@title", title ?? string.Empty);
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@rootBlogName", string.IsNullOrWhiteSpace(rootBlogName) ? (object)DBNull.Value : rootBlogName);
command.Parameters.AddWithValue("@rootURL", string.IsNullOrWhiteSpace(rootURL) ? (object)DBNull.Value : rootURL);
command.Parameters.AddWithValue("@hasImage", hasImage ? 1 : 0);
command.Parameters.AddWithValue("@byLikes", byLikes ? 1 : 0);
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@PostID", postID);
@@ -998,10 +1110,11 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string sql = "UPDATE Blogs SET HasBeenOutput = 1 WHERE BlogName = @BlogName";
string sql = "UPDATE Blogs SET HasBeenOutput = 1, DateModified = @DateModified WHERE BlogName = @BlogName";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@BlogName", blogName);
command.Parameters.AddWithValue("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.ExecuteNonQuery();
}
}
@@ -1017,6 +1130,32 @@ namespace URLNotesGrabberCORE
}
}
public static void UpdateBlogLikesStatus(string blogName, int likesPulled, long likesCursor, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
try
{
connection.Open();
string sql = "UPDATE Blogs SET LikesPulled = @pulled, LikesCursor = @cursor, DateModified = @modified WHERE BlogName = @name";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@pulled", likesPulled);
command.Parameters.AddWithValue("@cursor", likesCursor);
command.Parameters.AddWithValue("@modified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@name", blogName);
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
Console.WriteLine($"Error updating blog likes status: {ex.Message}");
}
finally
{
connection.Close();
}
}
public static int UpdateAPICount(string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -1059,10 +1198,11 @@ namespace URLNotesGrabberCORE
connection.Open();
//string sql = "UPDATE Notes SET replyText = @replyText WHERE rootBlogName = @rootBlogName AND PostID = @PostID AND noteBlogName = @noteBlogName AND TimeStamp = @TimeStamp AND Type = 'reply'";
string sql = "UPDATE Notes SET replyText = @replyText WHERE PostID = @PostID AND noteBlogName = @noteBlogName AND TimeStamp = @TimeStamp AND Type = 'reply'";
string sql = "UPDATE Notes SET replyText = @replyText, DateModified = @dateModified WHERE PostID = @PostID AND noteBlogName = @noteBlogName AND TimeStamp = @TimeStamp AND Type = 'reply'";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@replyText", replyText ?? ".");
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@rootBlogName", rootBlogName);
command.Parameters.AddWithValue("@PostID", postID);
command.Parameters.AddWithValue("@noteBlogName", noteBlogName);
@@ -1101,10 +1241,11 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string sql = "UPDATE Notes SET replyText = @replyText WHERE rootBlogName = @rootBlogName AND PostID = @PostID AND Type = 'reply'";
string sql = "UPDATE Notes SET replyText = @replyText, DateModified = @dateModified WHERE rootBlogName = @rootBlogName AND PostID = @PostID AND Type = 'reply'";
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
command.Parameters.AddWithValue("@replyText", replyText ?? ".");
command.Parameters.AddWithValue("@dateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
command.Parameters.AddWithValue("@rootBlogName", rootBlogName);
command.Parameters.AddWithValue("@PostID", postID);
int rowsAffected = command.ExecuteNonQuery();
@@ -1160,6 +1301,65 @@ namespace URLNotesGrabberCORE
private static string OAuthTokenSecret => Configuration["TumblrApi:OAuthTokenSecret"] ??
throw new InvalidOperationException("OAuthTokenSecret is not configured");
private static int GetRetryDelaySecondsFromHeaders(IEnumerable<HeaderParameter>? headers)
{
if (headers == null)
return 60;
int retryInSeconds = 0;
bool remainingIsZero = false;
foreach (var header in headers)
{
string? headerName = header?.Name;
string? headerValue = header?.Value?.ToString();
if (string.IsNullOrWhiteSpace(headerName) || string.IsNullOrWhiteSpace(headerValue))
continue;
if (string.Equals(headerName, "Retry-After", StringComparison.OrdinalIgnoreCase))
{
if (int.TryParse(headerValue, out int retrySecs))
{
retryInSeconds = Math.Max(retryInSeconds, retrySecs);
}
else if (DateTimeOffset.TryParse(headerValue, out var retryAt))
{
int secs = (int)Math.Max(0, (retryAt - DateTimeOffset.UtcNow).TotalSeconds);
retryInSeconds = Math.Max(retryInSeconds, secs);
}
}
if (headerName.IndexOf("X-RateLimit-Reset", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (long.TryParse(headerValue, out long epoch))
{
int secs = (int)Math.Max(0, epoch - DateTimeOffset.UtcNow.ToUnixTimeSeconds());
retryInSeconds = Math.Max(retryInSeconds, secs);
}
}
if (headerName.Contains("Remaining", StringComparison.OrdinalIgnoreCase) && headerValue == "0")
{
remainingIsZero = true;
}
if (remainingIsZero && headerName.IndexOf("Reset", StringComparison.OrdinalIgnoreCase) >= 0)
{
if (int.TryParse(headerValue, out int resetValue))
{
retryInSeconds = Math.Max(retryInSeconds, resetValue);
}
else if (long.TryParse(headerValue, out long resetEpoch))
{
int secs = (int)Math.Max(0, resetEpoch - DateTimeOffset.UtcNow.ToUnixTimeSeconds());
retryInSeconds = Math.Max(retryInSeconds, secs);
}
}
}
return retryInSeconds > 0 ? retryInSeconds : 60;
}
public static async Task<Root> GrabNotes(string blog, long ID, string? timestamp = null)
{
var URL = "https://api.tumblr.com/v2/blog/[0].tumblr.com/notes?id=[1]&mode=all";
@@ -1407,5 +1607,86 @@ namespace URLNotesGrabberCORE
return myDeserializedClass;
}
}
public static async Task<LikesRoot> GrabLikes(string blog, long beforeTimestamp = 0)
{
var URL = $"https://api.tumblr.com/v2/blog/{Uri.EscapeDataString(blog)}.tumblr.com/likes?npf=false&reblog_info=true";
if (beforeTimestamp > 0)
{
URL += $"&before={beforeTimestamp}";
}
using (var client = new RestClient(URL))
{
var oAuth1 = OAuth1Authenticator.ForAccessToken(consumerKey: ConsumerKey,
consumerSecret: ConsumerSecret,
token: OAuthToken,
tokenSecret: OAuthTokenSecret,
OAuthSignatureMethod.HmacSha1
);
client.Authenticator = oAuth1;
var request = new RestRequest(URL, Method.Get);
var response = await client.ExecuteAsync(request);
var myJsonResponse = response.Content ?? string.Empty;
Console.WriteLine($"[Likes API] {DateTime.Now}\t{DataAccess.UpdateAPICount()}");
var myDeserializedClass = new LikesRoot();
// Check if response is successful and contains JSON
if (!response.IsSuccessful || string.IsNullOrEmpty(myJsonResponse))
{
string? statusStr = null;
try { statusStr = response != null ? response.StatusCode.ToString() : null; } catch { statusStr = null; }
Console.WriteLine($"[Likes API] {statusStr}\t{response?.StatusDescription}");
if (!string.IsNullOrEmpty(statusStr))
myDeserializedClass.statusCode = statusStr;
if (response != null && response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
myDeserializedClass.statusCode = "TooManyRequests";
myDeserializedClass.retryInSeconds = GetRetryDelaySecondsFromHeaders(response.Headers);
}
return myDeserializedClass;
}
if (myJsonResponse.TrimStart().StartsWith("<"))
{
Console.WriteLine($"[Likes API] Received HTML response (likely error page): {response?.StatusCode}");
myDeserializedClass.statusCode = response?.StatusCode.ToString();
if (response != null && response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
{
myDeserializedClass.statusCode = "TooManyRequests";
myDeserializedClass.retryInSeconds = GetRetryDelaySecondsFromHeaders(response.Headers);
}
return myDeserializedClass;
}
try
{
var deserializedResult = JsonConvert.DeserializeObject<LikesRoot>(myJsonResponse);
if (deserializedResult != null)
{
myDeserializedClass = deserializedResult;
myDeserializedClass.rawJson = myJsonResponse;
bool metaIndicatesRateLimit = myDeserializedClass.meta != null && myDeserializedClass.meta.status == 429;
bool statusIndicatesRateLimit = response != null && response.StatusCode == System.Net.HttpStatusCode.TooManyRequests;
if (metaIndicatesRateLimit || statusIndicatesRateLimit)
{
myDeserializedClass.statusCode = "TooManyRequests";
myDeserializedClass.retryInSeconds = GetRetryDelaySecondsFromHeaders(response?.Headers);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[Likes API] Failed to parse JSON response: {ex.Message}");
}
return myDeserializedClass;
}
}
}
}