feat: merge ThreeTxtFileHelper into URLNotesGrabberCORE
Folds the standalone ThreeTxtFileHelper tool into URLNotesGrabberCORE so text-file ingest/output/correct lives alongside the API scraper. Adds new flags -ingest, -output, -correct (with -apply), -updatepaths, and a one-time -importposts <posts.db> migration. Schema: Blogs.TTFolderPath and Posts.PostType are added by an idempotent migration. On (BlogName, PostID) collisions, content columns are overwritten while engagement columns (ByLikes, RootBlogName, RootURL, HasNotesGathered, NotFound, NotesGatheredDateTime, Likes*) are preserved. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
@@ -1786,6 +1786,519 @@ namespace URLNotesGrabberCORE
|
||||
}
|
||||
}
|
||||
#endregion Updates
|
||||
|
||||
#region TTFileHelper
|
||||
|
||||
// Idempotent migration: adds Blogs.TTFolderPath and Posts.PostType if missing.
|
||||
// Mirrors the EnsureBlogsLikesColumnsExist pattern (PRAGMA table_info + ALTER TABLE ADD COLUMN).
|
||||
public static void EnsureTTFileHelperColumnsExist(string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
SQLiteConnection connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
|
||||
try
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
bool ttFolderPathExists = false;
|
||||
using (SQLiteCommand command = new SQLiteCommand("PRAGMA table_info(Blogs);", connection))
|
||||
using (SQLiteDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
string columnName = reader.GetString(1);
|
||||
if (columnName.Equals("TTFolderPath", StringComparison.OrdinalIgnoreCase)) ttFolderPathExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ttFolderPathExists)
|
||||
{
|
||||
using (SQLiteCommand cmd = new SQLiteCommand("ALTER TABLE Blogs ADD COLUMN TTFolderPath TEXT;", connection))
|
||||
cmd.ExecuteNonQuery();
|
||||
Console.WriteLine("[Migration] Added TTFolderPath column to Blogs table");
|
||||
}
|
||||
|
||||
bool postTypeExists = false;
|
||||
using (SQLiteCommand command = new SQLiteCommand("PRAGMA table_info(Posts);", connection))
|
||||
using (SQLiteDataReader reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
string columnName = reader.GetString(1);
|
||||
if (columnName.Equals("PostType", StringComparison.OrdinalIgnoreCase)) postTypeExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!postTypeExists)
|
||||
{
|
||||
using (SQLiteCommand cmd = new SQLiteCommand("ALTER TABLE Posts ADD COLUMN PostType TEXT;", connection))
|
||||
cmd.ExecuteNonQuery();
|
||||
Console.WriteLine("[Migration] Added PostType column to Posts table");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error ensuring TTFileHelper columns: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// INSERT-or-UPDATE for a post arriving from a Tumblr text-file export.
|
||||
// On collision, only content columns + PostType + DateModified are updated;
|
||||
// engagement columns (ByLikes, RootBlogName, RootURL, HasNotesGathered, NotFound,
|
||||
// NotesGatheredDateTime) are preserved.
|
||||
public static void UpsertPostFromTextFile(
|
||||
string blogName,
|
||||
string 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,
|
||||
string? postType,
|
||||
bool hasImage,
|
||||
string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
try { AddBlog(blogName, false, DBPath); } catch { }
|
||||
|
||||
SQLiteConnection connection;
|
||||
bool ownsConnection;
|
||||
lock (_importSessionLock)
|
||||
{
|
||||
if (_importConnection != null) { connection = _importConnection; ownsConnection = false; }
|
||||
else { connection = new SQLiteConnection("Data Source=" + DBPath); ownsConnection = true; }
|
||||
}
|
||||
|
||||
string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
try
|
||||
{
|
||||
if (ownsConnection) connection.Open();
|
||||
|
||||
string insertSql = @"INSERT INTO Posts (
|
||||
BlogName, PostID, reblogURL, PostDate, PostURL, Slug,
|
||||
ReblogKey, ReblogName, Summary, Quote, Body, Tags, Link,
|
||||
PhotoURL, PhotoCaption, DownloadedFiles, AudioCaption,
|
||||
Question, Answer, Title, PostType,
|
||||
DateModified, DateCreated, RootBlogName, RootURL,
|
||||
HasImage, ByLikes
|
||||
) VALUES (
|
||||
@BlogName, @PostID, @reblogURL, @PostDate, @PostURL, @Slug,
|
||||
@ReblogKey, @ReblogName, @Summary, @Quote, @Body, @Tags, @Link,
|
||||
@PhotoURL, @PhotoCaption, @DownloadedFiles, @AudioCaption,
|
||||
@Question, @Answer, @Title, @PostType,
|
||||
@DateModified, @DateCreated, '.', '.', @HasImage, 0)";
|
||||
|
||||
int rowsInserted = 0;
|
||||
try
|
||||
{
|
||||
using (var cmd = new SQLiteCommand(insertSql, connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@BlogName", blogName);
|
||||
cmd.Parameters.AddWithValue("@PostID", postID);
|
||||
cmd.Parameters.AddWithValue("@reblogURL", (object?)reblogURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostDate", (object?)postDate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostURL", (object?)postURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Slug", (object?)slug ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ReblogKey", (object?)reblogKey ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ReblogName", (object?)reblogName ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Summary", (object?)summary ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Quote", (object?)quote ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Body", (object?)body ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Tags", (object?)tags ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Link", (object?)link ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PhotoURL", (object?)photoURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PhotoCaption", (object?)photoCaption ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@DownloadedFiles", (object?)downloadedFiles ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@AudioCaption", (object?)audioCaption ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Question", (object?)question ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Answer", (object?)answer ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Title", (object?)title ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostType", (object?)postType ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@DateModified", now);
|
||||
cmd.Parameters.AddWithValue("@DateCreated", now);
|
||||
cmd.Parameters.AddWithValue("@HasImage", hasImage ? 1 : 0);
|
||||
rowsInserted = cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
catch (Exception insertEx)
|
||||
{
|
||||
string msg = insertEx.Message ?? string.Empty;
|
||||
bool isUniqueViolation = msg.Contains("UNIQUE constraint failed");
|
||||
if (!isUniqueViolation)
|
||||
{
|
||||
Console.WriteLine($"[UpsertPostFromTextFile] INSERT failed for {blogName}/{postID}: {msg}");
|
||||
}
|
||||
}
|
||||
|
||||
if (rowsInserted == 0)
|
||||
{
|
||||
string updateSql = @"UPDATE Posts SET
|
||||
reblogURL = @reblogURL,
|
||||
PostDate = @PostDate,
|
||||
PostURL = @PostURL,
|
||||
Slug = @Slug,
|
||||
ReblogKey = @ReblogKey,
|
||||
ReblogName = @ReblogName,
|
||||
Summary = @Summary,
|
||||
Quote = @Quote,
|
||||
Body = @Body,
|
||||
Tags = @Tags,
|
||||
Link = @Link,
|
||||
PhotoURL = @PhotoURL,
|
||||
PhotoCaption = @PhotoCaption,
|
||||
DownloadedFiles = @DownloadedFiles,
|
||||
AudioCaption = @AudioCaption,
|
||||
Question = @Question,
|
||||
Answer = @Answer,
|
||||
Title = @Title,
|
||||
PostType = @PostType,
|
||||
HasImage = @HasImage,
|
||||
DateModified = @DateModified
|
||||
WHERE BlogName = @BlogName AND PostID = @PostID";
|
||||
|
||||
using (var cmd = new SQLiteCommand(updateSql, connection))
|
||||
{
|
||||
cmd.Parameters.AddWithValue("@BlogName", blogName);
|
||||
cmd.Parameters.AddWithValue("@PostID", postID);
|
||||
cmd.Parameters.AddWithValue("@reblogURL", (object?)reblogURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostDate", (object?)postDate ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostURL", (object?)postURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Slug", (object?)slug ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ReblogKey", (object?)reblogKey ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@ReblogName", (object?)reblogName ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Summary", (object?)summary ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Quote", (object?)quote ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Body", (object?)body ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Tags", (object?)tags ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Link", (object?)link ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PhotoURL", (object?)photoURL ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PhotoCaption", (object?)photoCaption ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@DownloadedFiles", (object?)downloadedFiles ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@AudioCaption", (object?)audioCaption ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Question", (object?)question ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Answer", (object?)answer ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@Title", (object?)title ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@PostType", (object?)postType ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@HasImage", hasImage ? 1 : 0);
|
||||
cmd.Parameters.AddWithValue("@DateModified", now);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (ownsConnection) connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<TTPostRecord> GetAllPostsForBlog(string blogName, string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
var results = new List<TTPostRecord>();
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
|
||||
string sql = @"SELECT BlogName, PostID, reblogURL, PostDate, PostURL, Slug,
|
||||
ReblogKey, ReblogName, Summary, Quote, Body, Tags, Link,
|
||||
PhotoURL, PhotoCaption, DownloadedFiles, AudioCaption,
|
||||
Question, Answer, Title, PostType,
|
||||
HasImage, DateCreated, DateModified
|
||||
FROM Posts WHERE BlogName = @BlogName";
|
||||
using var cmd = new SQLiteCommand(sql, connection);
|
||||
cmd.Parameters.AddWithValue("@BlogName", blogName);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
results.Add(new TTPostRecord
|
||||
{
|
||||
BlogName = SafeStr(reader, 0),
|
||||
PostId = SafeStr(reader, 1),
|
||||
ReblogUrl = SafeNullableStr(reader, 2),
|
||||
Date = SafeNullableStr(reader, 3),
|
||||
PostUrl = SafeNullableStr(reader, 4),
|
||||
Slug = SafeNullableStr(reader, 5),
|
||||
ReblogKey = SafeNullableStr(reader, 6),
|
||||
ReblogName = SafeNullableStr(reader, 7),
|
||||
Summary = SafeNullableStr(reader, 8),
|
||||
Quote = SafeNullableStr(reader, 9),
|
||||
Body = SafeNullableStr(reader, 10),
|
||||
Tags = SafeNullableStr(reader, 11),
|
||||
Link = SafeNullableStr(reader, 12),
|
||||
PhotoUrl = SafeNullableStr(reader, 13),
|
||||
PhotoCaption = SafeNullableStr(reader, 14),
|
||||
DownloadedFiles = SafeNullableStr(reader, 15),
|
||||
AudioCaption = SafeNullableStr(reader, 16),
|
||||
Question = SafeNullableStr(reader, 17),
|
||||
Answer = SafeNullableStr(reader, 18),
|
||||
Title = SafeNullableStr(reader, 19),
|
||||
PostType = SafeNullableStr(reader, 20),
|
||||
HasImage = SafeNullableStr(reader, 21),
|
||||
CreatedDate = SafeNullableStr(reader, 22),
|
||||
ModifiedDate = SafeNullableStr(reader, 23)
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public static TTPostRecord? GetPost(string blogName, string postId, string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
|
||||
string sql = @"SELECT BlogName, PostID, reblogURL, PostDate, PostURL, Slug,
|
||||
ReblogKey, ReblogName, Summary, Quote, Body, Tags, Link,
|
||||
PhotoURL, PhotoCaption, DownloadedFiles, AudioCaption,
|
||||
Question, Answer, Title, PostType,
|
||||
HasImage, DateCreated, DateModified
|
||||
FROM Posts WHERE BlogName = @BlogName AND PostID = @PostID";
|
||||
using var cmd = new SQLiteCommand(sql, connection);
|
||||
cmd.Parameters.AddWithValue("@BlogName", blogName);
|
||||
cmd.Parameters.AddWithValue("@PostID", postId);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
if (!reader.Read()) return null;
|
||||
|
||||
return new TTPostRecord
|
||||
{
|
||||
BlogName = SafeStr(reader, 0),
|
||||
PostId = SafeStr(reader, 1),
|
||||
ReblogUrl = SafeNullableStr(reader, 2),
|
||||
Date = SafeNullableStr(reader, 3),
|
||||
PostUrl = SafeNullableStr(reader, 4),
|
||||
Slug = SafeNullableStr(reader, 5),
|
||||
ReblogKey = SafeNullableStr(reader, 6),
|
||||
ReblogName = SafeNullableStr(reader, 7),
|
||||
Summary = SafeNullableStr(reader, 8),
|
||||
Quote = SafeNullableStr(reader, 9),
|
||||
Body = SafeNullableStr(reader, 10),
|
||||
Tags = SafeNullableStr(reader, 11),
|
||||
Link = SafeNullableStr(reader, 12),
|
||||
PhotoUrl = SafeNullableStr(reader, 13),
|
||||
PhotoCaption = SafeNullableStr(reader, 14),
|
||||
DownloadedFiles = SafeNullableStr(reader, 15),
|
||||
AudioCaption = SafeNullableStr(reader, 16),
|
||||
Question = SafeNullableStr(reader, 17),
|
||||
Answer = SafeNullableStr(reader, 18),
|
||||
Title = SafeNullableStr(reader, 19),
|
||||
PostType = SafeNullableStr(reader, 20),
|
||||
HasImage = SafeNullableStr(reader, 21),
|
||||
CreatedDate = SafeNullableStr(reader, 22),
|
||||
ModifiedDate = SafeNullableStr(reader, 23)
|
||||
};
|
||||
}
|
||||
|
||||
// Find a post by PostID alone (matches ThreeTxtFileHelper's correction-mode
|
||||
// lookup, which does not scope by blog name). Returns the first match if any.
|
||||
public static TTPostRecord? GetPostByIdAnyBlog(string postId, string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
|
||||
string sql = @"SELECT BlogName, PostID, reblogURL, PostDate, PostURL, Slug,
|
||||
ReblogKey, ReblogName, Summary, Quote, Body, Tags, Link,
|
||||
PhotoURL, PhotoCaption, DownloadedFiles, AudioCaption,
|
||||
Question, Answer, Title, PostType,
|
||||
HasImage, DateCreated, DateModified
|
||||
FROM Posts WHERE PostID = @PostID LIMIT 1";
|
||||
using var cmd = new SQLiteCommand(sql, connection);
|
||||
cmd.Parameters.AddWithValue("@PostID", postId);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
if (!reader.Read()) return null;
|
||||
|
||||
return new TTPostRecord
|
||||
{
|
||||
BlogName = SafeStr(reader, 0),
|
||||
PostId = SafeStr(reader, 1),
|
||||
ReblogUrl = SafeNullableStr(reader, 2),
|
||||
Date = SafeNullableStr(reader, 3),
|
||||
PostUrl = SafeNullableStr(reader, 4),
|
||||
Slug = SafeNullableStr(reader, 5),
|
||||
ReblogKey = SafeNullableStr(reader, 6),
|
||||
ReblogName = SafeNullableStr(reader, 7),
|
||||
Summary = SafeNullableStr(reader, 8),
|
||||
Quote = SafeNullableStr(reader, 9),
|
||||
Body = SafeNullableStr(reader, 10),
|
||||
Tags = SafeNullableStr(reader, 11),
|
||||
Link = SafeNullableStr(reader, 12),
|
||||
PhotoUrl = SafeNullableStr(reader, 13),
|
||||
PhotoCaption = SafeNullableStr(reader, 14),
|
||||
DownloadedFiles = SafeNullableStr(reader, 15),
|
||||
AudioCaption = SafeNullableStr(reader, 16),
|
||||
Question = SafeNullableStr(reader, 17),
|
||||
Answer = SafeNullableStr(reader, 18),
|
||||
Title = SafeNullableStr(reader, 19),
|
||||
PostType = SafeNullableStr(reader, 20),
|
||||
HasImage = SafeNullableStr(reader, 21),
|
||||
CreatedDate = SafeNullableStr(reader, 22),
|
||||
ModifiedDate = SafeNullableStr(reader, 23)
|
||||
};
|
||||
}
|
||||
|
||||
public static void SetBlogTTFolderPath(string blogName, string? path, string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
try { AddBlog(blogName, false, DBPath); } catch { }
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
using var cmd = new SQLiteCommand(
|
||||
"UPDATE Blogs SET TTFolderPath = @path, DateModified = @modified WHERE BlogName = @name",
|
||||
connection);
|
||||
cmd.Parameters.AddWithValue("@path", (object?)path ?? DBNull.Value);
|
||||
cmd.Parameters.AddWithValue("@modified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
cmd.Parameters.AddWithValue("@name", blogName);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Partial UPDATE used by the correct-apply path. fieldsToUpdate maps
|
||||
// ThreeTxtFileHelper prefix names ("Reblog URL", "Body", etc.) to non-empty
|
||||
// values pulled from a BAK file. Only those columns + DateModified are written;
|
||||
// other content columns and all engagement columns are left intact.
|
||||
// Returns true if a row was matched (and therefore updated).
|
||||
public static bool UpdatePostContentFields(string blogName, string postId, IDictionary<string, string> fieldsToUpdate, string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
|
||||
var setClauses = new List<string>();
|
||||
var parameters = new List<(string Name, object Value)>();
|
||||
|
||||
foreach (var kvp in fieldsToUpdate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kvp.Value)) continue;
|
||||
string? column = MapPrefixToColumn(kvp.Key);
|
||||
if (column == null) continue;
|
||||
string paramName = "@p" + parameters.Count;
|
||||
setClauses.Add($"{column} = {paramName}");
|
||||
parameters.Add((paramName, kvp.Value));
|
||||
}
|
||||
|
||||
if (setClauses.Count == 0) return false;
|
||||
|
||||
setClauses.Add("DateModified = @DateModified");
|
||||
parameters.Add(("@DateModified", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
|
||||
string sql = $"UPDATE Posts SET {string.Join(", ", setClauses)} WHERE BlogName = @BlogName AND PostID = @PostID";
|
||||
using var cmd = new SQLiteCommand(sql, connection);
|
||||
foreach (var (name, value) in parameters)
|
||||
cmd.Parameters.AddWithValue(name, value);
|
||||
cmd.Parameters.AddWithValue("@BlogName", blogName);
|
||||
cmd.Parameters.AddWithValue("@PostID", postId);
|
||||
|
||||
return cmd.ExecuteNonQuery() > 0;
|
||||
}
|
||||
|
||||
private static string? MapPrefixToColumn(string prefix)
|
||||
{
|
||||
return prefix.ToLowerInvariant() switch
|
||||
{
|
||||
"reblog url" => "reblogURL",
|
||||
"date" => "PostDate",
|
||||
"has image" => "HasImage",
|
||||
"post url" => "PostURL",
|
||||
"slug" => "Slug",
|
||||
"reblog key" => "ReblogKey",
|
||||
"reblog name" => "ReblogName",
|
||||
"summary" => "Summary",
|
||||
"quote" => "Quote",
|
||||
"body" => "Body",
|
||||
"tags" => "Tags",
|
||||
"link" => "Link",
|
||||
"photo url" => "PhotoURL",
|
||||
"photo caption" => "PhotoCaption",
|
||||
"downloaded files" => "DownloadedFiles",
|
||||
"audio caption" => "AudioCaption",
|
||||
"question" => "Question",
|
||||
"answer" => "Answer",
|
||||
"title" => "Title",
|
||||
"post id" => null,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static List<(string BlogName, string? TTFolderPath)> GetAllBlogsWithTTFolderPath(string? DBPath = null)
|
||||
{
|
||||
DBPath ??= GetDefaultDbPath();
|
||||
var results = new List<(string, string?)>();
|
||||
|
||||
using var connection = new SQLiteConnection("Data Source=" + DBPath);
|
||||
connection.Open();
|
||||
using var cmd = new SQLiteCommand("SELECT BlogName, TTFolderPath FROM Blogs", connection);
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
string name = reader.GetString(0);
|
||||
string? path = reader.IsDBNull(1) ? null : reader.GetString(1);
|
||||
results.Add((name, path));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string SafeStr(SQLiteDataReader reader, int ordinal)
|
||||
{
|
||||
return reader.IsDBNull(ordinal) ? string.Empty : reader.GetValue(ordinal)?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
private static string? SafeNullableStr(SQLiteDataReader reader, int ordinal)
|
||||
{
|
||||
return reader.IsDBNull(ordinal) ? null : reader.GetValue(ordinal)?.ToString();
|
||||
}
|
||||
|
||||
#endregion TTFileHelper
|
||||
}
|
||||
|
||||
// Mirrors ThreeTxtFileHelper.PostData so the ported ingest/output/correct logic
|
||||
// can read/write rows without an ORM. All content fields are nullable except keys.
|
||||
public class TTPostRecord
|
||||
{
|
||||
public string BlogName { get; set; } = "";
|
||||
public string PostId { get; set; } = "";
|
||||
public string? ReblogUrl { get; set; }
|
||||
public string? Date { get; set; }
|
||||
public string? HasImage { get; set; }
|
||||
public string? PostUrl { get; set; }
|
||||
public string? Slug { get; set; }
|
||||
public string? ReblogKey { get; set; }
|
||||
public string? ReblogName { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public string? Quote { get; set; }
|
||||
public string? Body { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
public string? Link { get; set; }
|
||||
public string? PhotoUrl { get; set; }
|
||||
public string? PhotoCaption { get; set; }
|
||||
public string? DownloadedFiles { get; set; }
|
||||
public string? AudioCaption { get; set; }
|
||||
public string? Question { get; set; }
|
||||
public string? Answer { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? PostType { get; set; }
|
||||
public string? CreatedDate { get; set; }
|
||||
public string? ModifiedDate { get; set; }
|
||||
}
|
||||
|
||||
public class ApiKeyConfig
|
||||
|
||||
Reference in New Issue
Block a user