feat: Initialize URLNotesGrabberCORE project with core application structure, data access, and configuration.

This commit is contained in:
jim
2025-11-19 15:51:52 -06:00
parent 3c44cbe671
commit 64ef0e9079
16 changed files with 846 additions and 135 deletions
+319 -83
View File
@@ -9,32 +9,70 @@ using RestSharp.Authenticators;
using RestSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Microsoft.Extensions.Diagnostics.Latency;
using System.Collections;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace URLNotesGrabberCORE
{
class ReblogRecord
{
public string postID;
public string reblogURL;
public string reblogName;
public string downloadedFiles;
public string reblogKey;
public string answer;
public string audioCaption;
public string blogName;
public string body;
public string date;
public string downloadedFiles;
public string link;
public string photoCaption;
public string photoURL;
public string postID;
public string postURL;
public string question;
public string quote;
public string reblogKey;
public string reblogName;
public string reblogURL;
public string slug;
public string summary;
public string tags;
public string title;
public ReblogRecord()
{
postID = ".";
reblogURL = ".";
reblogName = ".";
downloadedFiles = ".";
reblogKey = ".";
answer = ".";
audioCaption = ".";
blogName = ".";
body = ".";
date = ".";
downloadedFiles = ".";
link = ".";
photoCaption = ".";
photoURL = ".";
postID = ".";
postURL = ".";
question = ".";
quote = ".";
reblogKey = ".";
reblogName = ".";
reblogURL = ".";
slug = ".";
summary = ".";
tags = ".";
title = ".";
}
}
internal class DataAccess
{
public static string Q(string input)
{
return "'" + input.Replace("'", "''") + "'";
}
public static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
{
// Unix timestamp is seconds past epoch
@@ -71,12 +109,11 @@ namespace URLNotesGrabberCORE
connection.Close();
}
}
public static void AddPost(string blogName, long postID, string reblogURL, string postDate, 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, string DBPath = @"TL.db")
{
try { AddBlog(blogName, 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 { }
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -84,7 +121,30 @@ namespace URLNotesGrabberCORE
{
connection.Open();
string sql = "INSERT INTO Posts (BlogName, PostID, reblogURL, postDate) values('" + blogName + "', " + postID + ", '" + reblogURL + "', '" + postDate + "')";
string sql = @"INSERT INTO Posts (
BlogName,
PostID,
reblogURL,
PostDate,
PostURL,
Slug,
ReblogKey,
ReblogName,
Summary,
Quote,
Body,
Tags,
Link,
PhotoURL,
PhotoCaption,
DownloadedFiles,
AudioCaption,
Question,
Answer,
Title,
HasImage
) 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 + ")";
SQLiteCommand command = new SQLiteCommand(sql, connection);
command.ExecuteNonQuery();
@@ -94,6 +154,28 @@ namespace URLNotesGrabberCORE
if (ex.Message != "constraint failed\r\nUNIQUE constraint failed: Posts.BlogName, Posts.PostID")
{
Console.WriteLine(ex.Message);
///
try
{
connection.Open();
string sql = "UPDATE Posts SET hasImage = " + hasImage + "WHERE blogName = '" + blogName + "' AND postID = '" + postID + "'";
SQLiteCommand command = new SQLiteCommand(sql, connection);
command.ExecuteNonQuery();
}
catch (Exception ex2)
{
if (ex2.Message != "constraint failed\r\nUNIQUE constraint failed: Posts.BlogName, Posts.PostID")
{
Console.WriteLine(ex2.Message);
}
}
finally
{
connection.Close();
}
///
}
}
finally
@@ -132,7 +214,7 @@ namespace URLNotesGrabberCORE
//try { AddPost(rootBlogName, postID, DBPath); } catch { }
try { AddBlog(noteBlogName, DBPath); } catch { }
Console.WriteLine("{0}\t{1}", UnixTimeStampToDateTime(timestamp), noteBlogName);
Console.WriteLine("{2}\t{0}\t{1}", UnixTimeStampToDateTime(timestamp), noteBlogName, type);
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -150,9 +232,10 @@ namespace URLNotesGrabberCORE
if (ex.Message != "constraint failed\r\nUNIQUE constraint failed: Notes.RootBlogName, Notes.PostID, Notes.TimeStamp, Notes.Type, Notes.NoteBlogName")
{
Console.WriteLine(ex.Message);
Console.WriteLine("^^^^^ - SHORTCUT");
return true;
Console.WriteLine("^^^^^ - SHORTCUT");
}
else
return UpdateNote(rootBlogName, noteBlogName, postID, timestamp, type, DBPath);
}
finally
{
@@ -163,31 +246,48 @@ namespace URLNotesGrabberCORE
#endregion Adds
#region Gets
public static List<Tuple<string, long, string>> GetPosts(bool withoutNotesOnly = false, string DBPath = @"TL.db")
/// <summary>
///
/// </summary>
/// <param name="withoutNotesOnly"></param>
/// <param name="DBPath"></param>
/// <returns>blogName, postID, lastNoteTimestamp, notesGatheredTimestamp</returns>
public static List<Tuple<string, long, long, long>> GetPosts(bool withoutNotesOnly = false, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
List<Tuple<string, long, string>> posts = new List<Tuple<string, long, string>>();
List<Tuple<string, long, long, long>> posts = new List<Tuple<string, long, long, long>>();
try
{
connection.Open();
string sql = "SELECT " +
" Posts.BlogName, " +
" Posts.PostID, " +
" Max(Notes.timestamp) as LatestNoteTimestamp " +
"FROM " +
" Posts INNER JOIN " +
" Notes ON Notes.RootBlogName = Posts.BlogName AND Notes.PostID = Posts.PostID " +
"WHERE NotFound = 0 ";
string sql = "SELECT " +
" Posts.BlogName, " + Environment.NewLine +
" Posts.PostID, " + Environment.NewLine +
" Max(IFNULL(Notes.timestamp, 1925013599)) as LatestNoteTimestamp, " + Environment.NewLine +
" Posts.NotesGatheredDatetime, " + Environment.NewLine +
" CNT.CNT " + Environment.NewLine +
"FROM " + Environment.NewLine +
" Posts " + Environment.NewLine +
" LEFT OUTER JOIN " + Environment.NewLine +
" Notes ON Notes.RootBlogName = Posts.BlogName AND Notes.PostID = Posts.PostID " + Environment.NewLine +
" LEFT OUTER JOIN " + Environment.NewLine +
" ( select BlogName, count(PostID) as CNT from Posts group by BlogName) CNT on CNT.blogName = Posts.BlogName " +
"WHERE NotFound = 0 " + Environment.NewLine;
if (withoutNotesOnly)
sql += " and HasNotesGathered = 0 ";
sql += " and HasNotesGathered = 0 " + Environment.NewLine ;
sql += "GROUP BY " +
" Posts.BlogName, Posts.PostID " +
"ORDER BY " +
" Posts.NotesGatheredDatetime, Max(Notes.timestamp), BlogName, Posts.PostID";
sql += "GROUP BY " + Environment.NewLine +
" Posts.BlogName, Posts.PostID " + Environment.NewLine +
"ORDER BY " + Environment.NewLine +
" notesgathereddatetime, Posts.PostDate DESC, Posts.BlogName, Posts.PostID" + Environment.NewLine;
Console.WriteLine(withoutNotesOnly);
Console.WriteLine(sql);
Console.Write(">"); //Console.ReadKey();
Thread.Sleep(1000);
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
@@ -195,15 +295,18 @@ namespace URLNotesGrabberCORE
{
while (reader.Read())
{
Tuple<string, long, string> post = default;
string blog = null;
long id = 0;
long timestamp;
timestamp = reader.GetInt64(2); // Assuming Id is the first column
id = reader.GetInt64(1); // Assuming Id is the first column
blog = reader.GetString(0); // Assuming Title is the second column
Tuple<string, long, long, long> post = default;
string blog;
long postID = 0;
long lastNoteTimestamp;
long notesGatheredTimestamp;
post = new Tuple<string, long, string>(blog, id, timestamp.ToString());
blog = reader.GetString(0); // Assuming Title is the second column
postID = reader.GetInt64(1); // Assuming Id is the first column
lastNoteTimestamp = reader.GetInt64(2); // Assuming Id is the first column
notesGatheredTimestamp = reader.GetInt64(3); // Assuming Id is the first column
post = new Tuple<string, long, long, long>(blog, postID, lastNoteTimestamp, notesGatheredTimestamp);
posts.Add(post);
}
}
@@ -343,6 +446,46 @@ namespace URLNotesGrabberCORE
}
return blogs;
}
public static List<string> GetBlogsAll(bool reblogsOnly, int from, int to, int top, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
List<string> blogs = new List<string>();
try
{
connection.Open();
string sql = "";
if (reblogsOnly)
sql = "SELECT NoteBlogName as blogName, count(*) from notes inner join blogs on blogs.BlogName = notes.NoteBlogName where type IN ('reblog', 'reply', 'posted') and HasBeenOutput = 0 group by NoteBlogName ORDER BY count(*) desc, BlogName LIMIT " + top;
else
sql = "SELECT NoteBlogName as blogName, count(*) from notes inner join blogs on blogs.BlogName = notes.NoteBlogName where HasBeenOutput = 0 group by NoteBlogName ORDER BY count(*) desc, BlogName LIMIT " + top;
using (SQLiteCommand command = new SQLiteCommand(sql, connection))
{
using (SQLiteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
string blog = null;
blog = reader.GetString(0); // Assuming Title is the second column
blogs.Add(blog);
}
}
}
}
catch (Exception ex)
{
if (ex.Message != "constraint failed\r\nUNIQUE constraint failed: Posts.BlogName, Posts.PostID")
Console.WriteLine(ex.Message);
}
finally
{
connection.Close();
}
return blogs;
}
#endregion Gets
#region Updates
@@ -423,6 +566,87 @@ 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 { }
Console.WriteLine("{2}\t{0}\t{1}", UnixTimeStampToDateTime(timestamp), noteBlogName, type);
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
try
{
connection.Open();
string sql = string.Format("UPDATE Notes SET timestamp = '{0}' WHERE rootBlogName = '{1}' AND noteBlogName = '{2}' AND PostID = '{3}'", timestamp, rootBlogName, noteBlogName, postID);
SQLiteCommand command = new SQLiteCommand(sql, connection);
command.ExecuteNonQuery();
}
catch (Exception ex)
{
if (ex.Message != "constraint failed\r\nUNIQUE constraint failed: Notes.RootBlogName, Notes.PostID, Notes.TimeStamp, Notes.Type, Notes.NoteBlogName")
{
Console.WriteLine(ex.Message);
Console.WriteLine("^^^^^ - SHORTCUT");
return true;
}
}
finally
{
connection.Close();
}
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")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
try
{
connection.Open();
string sql = "UPDATE Posts ";
sql += "SET postDate = " + Q(postDate) + ", ";
sql += "reblogURL = " + Q(reblogURL) + ", ";
sql += " postURL = " + Q(postURL) + ", ";
sql += " slug = " + Q(slug) + ", ";
sql += " reblogKey = " + Q(reblogKey) + ", ";
sql += " reblogName = " + Q(reblogName) + ", ";
sql += " summary = " + Q(summary) + ", ";
sql += " quote = " + Q(quote) + ", ";
sql += " body = " + Q(body) + ", ";
sql += " tags = " + Q(tags) + ", ";
sql += " link = " + Q(link) + ", ";
sql += " photoURL = " + Q(photoURL) + ", ";
sql += " photoCaption = " + Q(photoCaption) + ", ";
sql += " downloadedFiles = " + Q(downloadedFiles) + ", ";
sql += " audioCaption = " + Q(audioCaption) + ", ";
sql += " question = " + Q(question) + ", ";
sql += " answer = " + Q(answer) + ", ";
sql += " title = " + Q(title) + ", ";
sql += " hasImage = " + hasImage;
sql += " WHERE BlogName = " + Q(blogName) + " AND PostID = " + postID;
SQLiteCommand command = new SQLiteCommand(sql, connection);
command.ExecuteNonQuery();
}
catch (Exception ex)
{
if (ex.Message != "constraint failed\r\nUNIQUE constraint failed: Posts.BlogName, Posts.PostID")
Console.WriteLine(ex.Message);
}
finally
{
connection.Close();
}
}
public static void UpdateBlogOutput(string blogName, string DBPath = @"TL.db")
{
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
@@ -483,82 +707,94 @@ namespace URLNotesGrabberCORE
internal class APIAccess
{
public static string Blog { get; set; }
public static string Blog { get; set; } = string.Empty;
private static IConfiguration Configuration { get; set; } = null!;
const string CONSUMER_KEY = "PtsBCGumcsgihyynxUh8b47Jfmi7uXhEIOU46bmhdsUSJ5mEP3";
const string CONSUMER_SECRET = "sA8BwNVTVKqBRRJmHbAD6NuyKPJ3bb9cei2bYhMgqT8cLX8tSG";
const string OAUTH_TOKEN = "HPJI6IijHoKN6WzBumG7KjS7g01iCu07jQsulpueKbWN1ZJ35J";
const string OAUTH_TOKEN_SECRET = "ajkN0Z1kKrsJaIXZEDI8zLsjRSQxFkfgO1i5k78FuQYCQjSY7w";
static APIAccess()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
public static async Task<Root> GrabNotes(string blog, long ID, string timestamp = null)
if (Configuration["TumblrApi:ConsumerKey"] == null)
throw new InvalidOperationException("TumblrApi configuration is missing in appsettings.json");
}
private static string ConsumerKey => Configuration["TumblrApi:ConsumerKey"] ??
throw new InvalidOperationException("ConsumerKey is not configured");
private static string ConsumerSecret => Configuration["TumblrApi:ConsumerSecret"] ??
throw new InvalidOperationException("ConsumerSecret is not configured");
private static string OAuthToken => Configuration["TumblrApi:OAuthToken"] ??
throw new InvalidOperationException("OAuthToken is not configured");
private static string OAuthTokenSecret => Configuration["TumblrApi:OAuthTokenSecret"] ??
throw new InvalidOperationException("OAuthTokenSecret is not configured");
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]";
URL = URL.Replace("[0]", blog).Replace("[1]", ID.ToString());
if (timestamp != null)
if (!string.IsNullOrEmpty(timestamp))
{
URL += "&before_timestamp=" + timestamp;
Thread.Sleep(1000);
await Task.Delay(1000);
}
var client = new RestClient(URL);
var oAuth1 = OAuth1Authenticator.ForAccessToken(consumerKey: CONSUMER_KEY,
consumerSecret: CONSUMER_SECRET,
token: OAUTH_TOKEN,
tokenSecret: OAUTH_TOKEN_SECRET,
var oAuth1 = OAuth1Authenticator.ForAccessToken(consumerKey: ConsumerKey,
consumerSecret: ConsumerSecret,
token: OAuthToken,
tokenSecret: OAuthTokenSecret,
OAuthSignatureMethod.HmacSha1
);
//oAuth1.Realm =
client.Authenticator = oAuth1;
var request = new RestRequest(URL, Method.Get);
var response = await client.ExecuteAsync(request);
var response = client.Execute(request);
var myJsonResponse = response.Content;
Console.WriteLine(timestamp.ToString() + '\t' + DateTime.Now + '\t' + DataAccess.UpdateAPICount());
Root myDeserializedClass = new Root();
var myJsonResponse = response.Content ?? string.Empty;
Console.WriteLine($"{timestamp}\t{DateTime.Now}\t{DataAccess.UpdateAPICount()}");
var myDeserializedClass = new Root();
try
{
myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
//if (myDeserializedClass.response.total_notes != 0)
//{
//DataAccess.AddBlog(blog);
//DataAccess.AddPost(blog, ID);
//}
var deserializedResult = JsonConvert.DeserializeObject<Root>(myJsonResponse);
if (deserializedResult != null)
{
myDeserializedClass = deserializedResult;
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed JSON: {myJsonResponse}");
Console.WriteLine(ex.ToString());
if (!response.IsSuccessful)
{
Console.WriteLine(response.StatusCode + '\t' + response.StatusDescription);
Console.WriteLine($"{response.StatusCode}\t{response.StatusDescription}");
myDeserializedClass.statusCode = response.StatusCode.ToString();
bool checkReset = false;
if (myDeserializedClass.statusCode != "NotFound")
if (myDeserializedClass.statusCode != "NotFound" && response.Headers != null)
{
foreach (var header in response.Headers)
{
Console.WriteLine("{0} - {1}", header.Name, header.Value);
if (checkReset && header.Name.Contains("Reset"))
if (header.Name != null && header.Value != null)
{
if (myDeserializedClass.retryInSeconds < int.Parse(header.Value.ToString()))
myDeserializedClass.retryInSeconds = int.Parse(header.Value.ToString());
Console.WriteLine($"{header.Name} - {header.Value}");
if (checkReset && header.Name.Contains("Reset"))
{
var headerValue = header.Value.ToString();
if (headerValue != null && int.TryParse(headerValue, out int resetValue))
{
if (myDeserializedClass.retryInSeconds < resetValue)
myDeserializedClass.retryInSeconds = resetValue;
}
}
if (header.Name.Contains("Remaining") && header.Value.ToString() == "0")
checkReset = true;
else
checkReset = false;
}
if (header.Name.Contains("Remaining") && header.Value.ToString() == "0")
checkReset = true;
else
checkReset = false;
//if(header.ToString().Contains("X-Ratelimit-Perday-Reset, Value = "))
//{
// string temp = header.ToString().Substring(59);
// int indexOf = temp.IndexOf(',');
// myDeserializedClass.retryInSeconds = int.Parse(temp.Substring(0, indexOf));
//}
}
}
}