636 lines
24 KiB
C#
636 lines
24 KiB
C#
|
|
using System.Text.Json;
|
||
|
|
using System.Text.Json.Serialization;
|
||
|
|
using wp2ghost;
|
||
|
|
|
||
|
|
class Program
|
||
|
|
{
|
||
|
|
private static string WpBaseUrl = "https://deine-domain.com";
|
||
|
|
internal static string OutputZipPath = "ghost_import.zip";
|
||
|
|
private static int MaxPosts = -1;
|
||
|
|
private static string ConfigPath = "";
|
||
|
|
private static string BaseDir = "";
|
||
|
|
private static string GhostUrl = "";
|
||
|
|
private static string GhostAdminApiKey = "";
|
||
|
|
private static string GhostContentApiKey = "";
|
||
|
|
|
||
|
|
static async Task Main(string[] args)
|
||
|
|
{
|
||
|
|
BaseDir = Directory.GetCurrentDirectory();
|
||
|
|
ConfigPath = Path.Combine(BaseDir, "config.txt");
|
||
|
|
|
||
|
|
LoadConfig();
|
||
|
|
ImageManager.Init(BaseDir);
|
||
|
|
PostFetcher.Init(BaseDir);
|
||
|
|
GhostExporter.Init(BaseDir);
|
||
|
|
GhostApi.Init(GhostUrl, GhostAdminApiKey, GhostContentApiKey, ImageManager.ImageContainerPath);
|
||
|
|
|
||
|
|
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||
|
|
Console.Title = "WP2Ghost Converter";
|
||
|
|
|
||
|
|
|
||
|
|
bool running = true;
|
||
|
|
while (running)
|
||
|
|
{
|
||
|
|
ShowMenu();
|
||
|
|
string? choice = Console.ReadLine()?.Trim();
|
||
|
|
|
||
|
|
switch (choice)
|
||
|
|
{
|
||
|
|
case "1":
|
||
|
|
await PostFetcher.FetchFromWordPressAsync(WpBaseUrl, MaxPosts);
|
||
|
|
break;
|
||
|
|
case "2":
|
||
|
|
await GhostExporter.CreateZipAsync();
|
||
|
|
break;
|
||
|
|
case "3":
|
||
|
|
ConfigureSettings();
|
||
|
|
break;
|
||
|
|
case "4":
|
||
|
|
ShowCacheInfo();
|
||
|
|
break;
|
||
|
|
case "5":
|
||
|
|
PostFetcher.ClearProcessedPosts();
|
||
|
|
break;
|
||
|
|
case "6":
|
||
|
|
ImageManager.ClearAll();
|
||
|
|
break;
|
||
|
|
case "7":
|
||
|
|
await GhostMenuAsync();
|
||
|
|
break;
|
||
|
|
case "0":
|
||
|
|
ImageManager.SaveCache();
|
||
|
|
running = false;
|
||
|
|
Console.WriteLine("Programm beendet. Auf Wiedersehen!");
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
Console.WriteLine("Ungültige Auswahl. Bitte erneut eingeben.");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ShowMenu()
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("=========================================");
|
||
|
|
Console.WriteLine(" WP2Ghost - WordPress to Ghost");
|
||
|
|
Console.WriteLine("=========================================");
|
||
|
|
Console.WriteLine($"WordPress-URL : {WpBaseUrl}");
|
||
|
|
Console.WriteLine($"Ausgabe-Datei : {OutputZipPath}");
|
||
|
|
Console.WriteLine($"Max. Beiträge/Seiten: {(MaxPosts == -1 ? "Alle" : MaxPosts.ToString())}");
|
||
|
|
Console.WriteLine($"Abgefragte Objekte : {PostFetcher.ProcessedCount}");
|
||
|
|
Console.WriteLine($"Ghost-URL : {(string.IsNullOrEmpty(GhostUrl) ? "(nicht konfiguriert)" : GhostUrl)}");
|
||
|
|
Console.WriteLine($"Ghost-Status : {(GhostApi.IsConfigured() ? "verbunden" : "nicht konfiguriert")}");
|
||
|
|
Console.WriteLine("-----------------------------------------");
|
||
|
|
Console.WriteLine(" 1 - Von WordPress abrufen (Posts & Pages)");
|
||
|
|
Console.WriteLine(" 2 - Ghost ZIP erstellen");
|
||
|
|
Console.WriteLine(" 3 - Einstellungen konfigurieren");
|
||
|
|
Console.WriteLine(" 4 - Cache-Informationen anzeigen");
|
||
|
|
Console.WriteLine(" 5 - Abgefragte Objekte zurücksetzen");
|
||
|
|
Console.WriteLine(" 6 - Bilder lokal löschen");
|
||
|
|
Console.WriteLine(" 7 - Beiträge direkt an Ghost senden");
|
||
|
|
Console.WriteLine(" 0 - Beenden");
|
||
|
|
Console.WriteLine("-----------------------------------------");
|
||
|
|
Console.Write("Auswahl: ");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void LoadConfig()
|
||
|
|
{
|
||
|
|
if (!File.Exists(ConfigPath))
|
||
|
|
{
|
||
|
|
SaveConfig();
|
||
|
|
Console.WriteLine($"Neue Konfigurationsdatei erstellt: {ConfigPath}");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var lines = File.ReadAllLines(ConfigPath);
|
||
|
|
foreach (var line in lines)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
|
||
|
|
continue;
|
||
|
|
|
||
|
|
var parts = line.Split('=', 2);
|
||
|
|
if (parts.Length == 2)
|
||
|
|
{
|
||
|
|
string key = parts[0].Trim();
|
||
|
|
string value = parts[1].Trim();
|
||
|
|
|
||
|
|
switch (key.ToLower())
|
||
|
|
{
|
||
|
|
case "wpbaseurl":
|
||
|
|
WpBaseUrl = value;
|
||
|
|
break;
|
||
|
|
case "outputzippath":
|
||
|
|
OutputZipPath = value;
|
||
|
|
break;
|
||
|
|
case "maxposts":
|
||
|
|
if (int.TryParse(value, out int max))
|
||
|
|
MaxPosts = max;
|
||
|
|
break;
|
||
|
|
case "ghosturl":
|
||
|
|
GhostUrl = value;
|
||
|
|
break;
|
||
|
|
case "ghostadminapikey":
|
||
|
|
GhostAdminApiKey = value;
|
||
|
|
break;
|
||
|
|
case "ghostcontentapikey":
|
||
|
|
GhostContentApiKey = value;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
Console.WriteLine($"Fehler beim Laden der Konfiguration: {ex.Message}");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void SaveConfig()
|
||
|
|
{
|
||
|
|
var lines = new List<string>
|
||
|
|
{
|
||
|
|
"# WP2Ghost Konfiguration",
|
||
|
|
"# Ändere die Werte und starte das Programm neu",
|
||
|
|
"",
|
||
|
|
$"wpBaseUrl={WpBaseUrl}",
|
||
|
|
$"outputZipPath={OutputZipPath}",
|
||
|
|
$"maxPosts={MaxPosts}",
|
||
|
|
"",
|
||
|
|
"# Ghost API (optional - für direkten Push)",
|
||
|
|
$"ghostUrl={GhostUrl}",
|
||
|
|
$"ghostAdminApiKey={GhostAdminApiKey}",
|
||
|
|
};
|
||
|
|
File.WriteAllLines(ConfigPath, lines);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ConfigureSettings()
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("--- Einstellungen konfigurieren ---");
|
||
|
|
Console.Write($"WordPress-URL [{WpBaseUrl}]: ");
|
||
|
|
string? input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input))
|
||
|
|
WpBaseUrl = input;
|
||
|
|
|
||
|
|
Console.Write($"Ausgabe-Datei [{OutputZipPath}]: ");
|
||
|
|
input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input))
|
||
|
|
OutputZipPath = input;
|
||
|
|
|
||
|
|
Console.Write($"Maximale Anzahl Posts (leer = alle) [{(MaxPosts == -1 ? "alle" : MaxPosts.ToString())}]: ");
|
||
|
|
input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input) && int.TryParse(input, out int max))
|
||
|
|
MaxPosts = max;
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("--- Ghost API (optional) ---");
|
||
|
|
Console.Write($"Ghost-URL [{GhostUrl}]: ");
|
||
|
|
input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input))
|
||
|
|
GhostUrl = input;
|
||
|
|
|
||
|
|
Console.Write($"Ghost Admin API Key [{(string.IsNullOrEmpty(GhostAdminApiKey) ? "(nicht gesetzt)" : "****")}] : ");
|
||
|
|
input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input))
|
||
|
|
GhostAdminApiKey = input;
|
||
|
|
|
||
|
|
Console.Write($"Ghost Content API Key [{(string.IsNullOrEmpty(GhostContentApiKey) ? "(nicht gesetzt)" : "****")}] : ");
|
||
|
|
input = Console.ReadLine()?.Trim();
|
||
|
|
if (!string.IsNullOrEmpty(input))
|
||
|
|
GhostContentApiKey = input;
|
||
|
|
|
||
|
|
GhostApi.Init(GhostUrl, GhostAdminApiKey, GhostContentApiKey, ImageManager.ImageContainerPath);
|
||
|
|
|
||
|
|
SaveConfig();
|
||
|
|
Console.WriteLine("Einstellungen gespeichert.");
|
||
|
|
}
|
||
|
|
|
||
|
|
static void ShowCacheInfo()
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("--- Cache-Informationen ---");
|
||
|
|
Console.WriteLine($"Abgefragte Objekte: {PostFetcher.ProcessedCount}");
|
||
|
|
Console.WriteLine($"Ghost-Beiträge geladen: {GhostExporter.PostCount}");
|
||
|
|
|
||
|
|
ImageManager.ShowInfo();
|
||
|
|
PostFetcher.ShowInfo();
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostMenuAsync()
|
||
|
|
{
|
||
|
|
if (!GhostApi.IsConfigured())
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("Ghost API nicht konfiguriert. Bitte zuerst Option 3 (Einstellungen) nutzen.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool inGhostMenu = true;
|
||
|
|
while (inGhostMenu)
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("=========================================");
|
||
|
|
Console.WriteLine(" Ghost Admin API");
|
||
|
|
Console.WriteLine("=========================================");
|
||
|
|
Console.WriteLine($" Ghost-URL : {GhostUrl}");
|
||
|
|
Console.WriteLine("-----------------------------------------");
|
||
|
|
Console.WriteLine(" 1 - Posts anzeigen (Ghost)");
|
||
|
|
Console.WriteLine(" 2 - Post lesen (nach ID)");
|
||
|
|
Console.WriteLine(" 3 - Post erstellen");
|
||
|
|
Console.WriteLine(" 4 - Post bearbeiten");
|
||
|
|
Console.WriteLine(" 5 - Post löschen");
|
||
|
|
Console.WriteLine(" 6 - Alle lokalen Posts an Ghost senden");
|
||
|
|
Console.WriteLine(" 7 - Bilder hochladen");
|
||
|
|
Console.WriteLine(" 8 - Post aus JSON erstellen/aktualisieren");
|
||
|
|
Console.WriteLine(" 0 - Zurück zum Hauptmenü");
|
||
|
|
Console.WriteLine("-----------------------------------------");
|
||
|
|
Console.Write("Auswahl: ");
|
||
|
|
|
||
|
|
string? choice = Console.ReadLine()?.Trim();
|
||
|
|
switch (choice)
|
||
|
|
{
|
||
|
|
case "1":
|
||
|
|
await GhostBrowsePostsAsync();
|
||
|
|
break;
|
||
|
|
case "2":
|
||
|
|
await GhostReadPostAsync();
|
||
|
|
break;
|
||
|
|
case "3":
|
||
|
|
await GhostCreatePostAsync();
|
||
|
|
break;
|
||
|
|
case "4":
|
||
|
|
await GhostEditPostAsync();
|
||
|
|
break;
|
||
|
|
case "5":
|
||
|
|
await GhostDeletePostAsync();
|
||
|
|
break;
|
||
|
|
case "6":
|
||
|
|
await GhostApi.PushAllPostsAsync(html => HtmlGhostConverter.Convert(html));
|
||
|
|
break;
|
||
|
|
case "7":
|
||
|
|
await GhostUploadImagesAsync();
|
||
|
|
break;
|
||
|
|
case "8":
|
||
|
|
await GhostCreateOrUpdateFromJsonAsync();
|
||
|
|
break;
|
||
|
|
case "0":
|
||
|
|
inGhostMenu = false;
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
Console.WriteLine("Ungültige Auswahl.");
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostBrowsePostsAsync()
|
||
|
|
{
|
||
|
|
Console.Write("Filter (leer = alle, z.B. status:published): ");
|
||
|
|
string? filter = Console.ReadLine()?.Trim();
|
||
|
|
Console.Write("Anzahl (leer = 50): ");
|
||
|
|
string? limitStr = Console.ReadLine()?.Trim();
|
||
|
|
int limit = int.TryParse(limitStr, out int l) ? l : 50;
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine("Lade Posts von Ghost...");
|
||
|
|
var posts = await GhostApi.BrowsePostsAsync(filter ?? "", limit);
|
||
|
|
|
||
|
|
if (posts.Count == 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Keine Posts gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine($" {"ID",-24} {"Status",-12} {"Titel"}");
|
||
|
|
Console.WriteLine($" {new string('-', 23)} {new string('-', 11)} {new string('-', 30)}");
|
||
|
|
foreach (var post in posts)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" {post.Id,-24} {post.Status,-12} {post.Title}");
|
||
|
|
}
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine($" Gesamt: {posts.Count} Posts");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostReadPostAsync()
|
||
|
|
{
|
||
|
|
Console.Write("Post-ID: ");
|
||
|
|
string? id = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(id)) return;
|
||
|
|
|
||
|
|
var post = await GhostApi.GetPostAsync(id);
|
||
|
|
if (post == null)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Post nicht gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine($" ID : {post.Id}");
|
||
|
|
Console.WriteLine($" Titel : {post.Title}");
|
||
|
|
Console.WriteLine($" Slug : {post.Slug}");
|
||
|
|
Console.WriteLine($" Status : {post.Status}");
|
||
|
|
Console.WriteLine($" Erstellt : {post.CreatedAt}");
|
||
|
|
Console.WriteLine($" Aktualisiert: {post.UpdatedAt}");
|
||
|
|
Console.WriteLine($" Feature Img : {post.FeatureImage ?? "(keine)"}");
|
||
|
|
Console.WriteLine($" HTML ({(post.Html?.Length ?? 0)} Zeichen):");
|
||
|
|
Console.WriteLine($" {new string('-', 50)}");
|
||
|
|
string preview = post.Html ?? "";
|
||
|
|
if (preview.Length > 500) preview = preview[..500] + "...";
|
||
|
|
Console.WriteLine($" {preview}");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostCreatePostAsync()
|
||
|
|
{
|
||
|
|
Console.Write("Titel: ");
|
||
|
|
string? title = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(title)) return;
|
||
|
|
|
||
|
|
Console.Write("HTML-Inhalt: ");
|
||
|
|
string? html = Console.ReadLine()?.Trim();
|
||
|
|
|
||
|
|
Console.Write("Status (published/draft) [published]: ");
|
||
|
|
string? status = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(status)) status = "published";
|
||
|
|
|
||
|
|
Console.Write("Slug (leer = automatisch): ");
|
||
|
|
string? slug = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(slug)) slug = null;
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.Write("Erstelle Post...");
|
||
|
|
var post = await GhostApi.CreatePostAsync(title, html ?? "", status, slug);
|
||
|
|
if (post != null)
|
||
|
|
Console.WriteLine($" OK (ID: {post.Id})");
|
||
|
|
else
|
||
|
|
Console.WriteLine(" FEHLER");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostEditPostAsync()
|
||
|
|
{
|
||
|
|
Console.Write("Post-ID: ");
|
||
|
|
string? id = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(id)) return;
|
||
|
|
|
||
|
|
var existing = await GhostApi.GetPostAsync(id);
|
||
|
|
if (existing == null)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Post nicht gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($" Aktueller Titel: {existing.Title}");
|
||
|
|
Console.Write("Neuer Titel (Enter = unverändert): ");
|
||
|
|
string? title = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(title)) title = existing.Title;
|
||
|
|
|
||
|
|
Console.Write("Neuer HTML-Inhalt (Enter = unverändert): ");
|
||
|
|
string? html = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(html)) html = existing.Html;
|
||
|
|
|
||
|
|
Console.Write($"Neuer Status ({existing.Status}): ");
|
||
|
|
string? status = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(status)) status = existing.Status;
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.Write("Aktualisiere Post...");
|
||
|
|
var updated = await GhostApi.UpdatePostAsync(id, title ?? "", html ?? "", existing.UpdatedAt ?? "", status ?? "published");
|
||
|
|
if (updated != null)
|
||
|
|
Console.WriteLine(" OK");
|
||
|
|
else
|
||
|
|
Console.WriteLine(" FEHLER");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostDeletePostAsync()
|
||
|
|
{
|
||
|
|
Console.Write("Post-ID: ");
|
||
|
|
string? id = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(id)) return;
|
||
|
|
|
||
|
|
var post = await GhostApi.GetPostAsync(id);
|
||
|
|
if (post == null)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Post nicht gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($" Post: {post.Title}");
|
||
|
|
Console.Write("Wirklich löschen? (j/n): ");
|
||
|
|
string? confirm = Console.ReadLine()?.Trim();
|
||
|
|
if (confirm?.ToLower() != "j") return;
|
||
|
|
|
||
|
|
Console.Write("Lösche Post...");
|
||
|
|
bool success = await GhostApi.DeletePostAsync(id);
|
||
|
|
Console.WriteLine(success ? " OK" : " FEHLER");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostUploadImagesAsync()
|
||
|
|
{
|
||
|
|
var cachedFiles = ImageManager.CachedFiles;
|
||
|
|
if (cachedFiles.Count == 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Keine Bilder im Cache. Bitte zuerst Option 1 (Abruf) nutzen.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($" {cachedFiles.Count} Bilder zum Hochladen vorhanden.");
|
||
|
|
Console.Write("Alle Bilder hochladen? (j/n): ");
|
||
|
|
string? confirm = Console.ReadLine()?.Trim();
|
||
|
|
if (confirm?.ToLower() != "j") return;
|
||
|
|
|
||
|
|
int uploaded = 0;
|
||
|
|
foreach (var file in cachedFiles)
|
||
|
|
{
|
||
|
|
Console.Write($" [Upload] {Path.GetFileName(file.LocalPath)}...");
|
||
|
|
string? url = await GhostApi.UploadImageAsync(file.LocalPath);
|
||
|
|
if (url != null)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" -> {url}");
|
||
|
|
uploaded++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
Console.WriteLine(" FEHLER");
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.WriteLine($" Hochgeladen: {uploaded}/{cachedFiles.Count}");
|
||
|
|
}
|
||
|
|
|
||
|
|
static async Task GhostCreateOrUpdateFromJsonAsync()
|
||
|
|
{
|
||
|
|
string ghostPostsDir = Path.Combine(BaseDir, "ghostPosts");
|
||
|
|
if (!Directory.Exists(ghostPostsDir))
|
||
|
|
{
|
||
|
|
Console.WriteLine("Verzeichnis 'ghostPosts' nicht gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var jsonFiles = Directory.GetFiles(ghostPostsDir, "*.json");
|
||
|
|
if (jsonFiles.Length == 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine("Keine JSON-Dateien in ghostPosts/ vorhanden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($" {jsonFiles.Length} verfügbare Posts:");
|
||
|
|
foreach (var f in jsonFiles)
|
||
|
|
{
|
||
|
|
string id = Path.GetFileNameWithoutExtension(f);
|
||
|
|
Console.Write($" {id} - ");
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var preview = JsonSerializer.Deserialize<GhostPostFile>(File.ReadAllText(f));
|
||
|
|
Console.WriteLine(preview?.Title ?? "(kein Titel)");
|
||
|
|
}
|
||
|
|
catch { Console.WriteLine("(Fehler beim Lesen)"); }
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.Write("Ghost-ID des Posts: ");
|
||
|
|
string? ghostId = Console.ReadLine()?.Trim();
|
||
|
|
if (string.IsNullOrEmpty(ghostId)) return;
|
||
|
|
|
||
|
|
string jsonPath = Path.Combine(ghostPostsDir, $"{ghostId}.json");
|
||
|
|
if (!File.Exists(jsonPath))
|
||
|
|
{
|
||
|
|
Console.WriteLine($"Datei {jsonPath} nicht gefunden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var postFile = JsonSerializer.Deserialize<GhostPostFile>(File.ReadAllText(jsonPath));
|
||
|
|
if (postFile == null)
|
||
|
|
{
|
||
|
|
Console.WriteLine("JSON konnte nicht gelesen werden.");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Console.WriteLine($" Post: {postFile.Title}");
|
||
|
|
Console.WriteLine($" Slug: {postFile.Slug}");
|
||
|
|
Console.WriteLine($" Kategorien: {postFile.Categories.Count}");
|
||
|
|
Console.WriteLine($" Kommentare: {postFile.Comments.Count}");
|
||
|
|
Console.WriteLine($" Bilder: {postFile.ImageFiles.Count}");
|
||
|
|
|
||
|
|
// 1. ALLE Bilder hochladen + Ghost-URL-Mapping erstellen
|
||
|
|
var ghostUrlMap = new Dictionary<string, string>();
|
||
|
|
string html = postFile.Html ?? "";
|
||
|
|
|
||
|
|
if (postFile.ImageFiles.Count > 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" [Bilder] Lade {postFile.ImageFiles.Count} Bilder hoch...");
|
||
|
|
foreach (string relPath in postFile.ImageFiles)
|
||
|
|
{
|
||
|
|
string localPath = Path.Combine(BaseDir, "images", relPath);
|
||
|
|
if (!File.Exists(localPath))
|
||
|
|
{
|
||
|
|
Console.WriteLine($" [Übersprungen] {relPath} (nicht lokal)");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
Console.Write($" [Upload] {Path.GetFileName(localPath)}...");
|
||
|
|
string? url = await GhostApi.UploadImageIfNewAsync(localPath);
|
||
|
|
if (url != null)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" -> {url}");
|
||
|
|
// Backslash-normalisiert für alte JSONs
|
||
|
|
string normalized = relPath.Replace("\\", "/");
|
||
|
|
string placeholder1 = $"__GHOST_URL__/content/images/{normalized}";
|
||
|
|
string placeholder2 = $"__GHOST_URL__/content/images/{relPath}";
|
||
|
|
ghostUrlMap[placeholder1] = url;
|
||
|
|
if (placeholder1 != placeholder2)
|
||
|
|
ghostUrlMap[placeholder2] = url;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
Console.WriteLine(" FEHLER");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Featured Image hochladen (separat, da nicht in ImageFiles)
|
||
|
|
string? featureImageUrl = null;
|
||
|
|
if (!string.IsNullOrEmpty(postFile.FeatureImage))
|
||
|
|
{
|
||
|
|
string localFeatPath = postFile.FeatureImage.Replace("__GHOST_URL__/content/images/", "");
|
||
|
|
localFeatPath = Path.Combine(BaseDir, "images", localFeatPath);
|
||
|
|
if (File.Exists(localFeatPath))
|
||
|
|
{
|
||
|
|
Console.Write(" [Featured Image]...");
|
||
|
|
featureImageUrl = await GhostApi.UploadImageIfNewAsync(localFeatPath);
|
||
|
|
Console.WriteLine(featureImageUrl != null ? $" OK" : " FEHLER");
|
||
|
|
if (featureImageUrl != null)
|
||
|
|
ghostUrlMap[postFile.FeatureImage] = featureImageUrl;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3. __GHOST_URL__ im HTML durch echte URLs ersetzen
|
||
|
|
foreach (var kvp in ghostUrlMap)
|
||
|
|
html = html.Replace(kvp.Key, kvp.Value);
|
||
|
|
|
||
|
|
// Falls noch Rest Platzhalter → Warnung
|
||
|
|
if (html.Contains("__GHOST_URL__"))
|
||
|
|
{
|
||
|
|
int remaining = System.Text.RegularExpressions.Regex.Matches(html, "__GHOST_URL__").Count;
|
||
|
|
Console.WriteLine($" [Warnung] {remaining} unrestrierte __GHOST_URL__ Platzhalter im HTML");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. Tags erstellen/zuordnen
|
||
|
|
var tags = new List<GhostAdminTag>();
|
||
|
|
if (postFile.Categories.Count > 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine(" [Tags] Verarbeite Kategorien...");
|
||
|
|
tags = await GhostApi.EnsureTagsAsync(postFile.Categories);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. Prüfe ob Post mit gleichem Slug bereits in Ghost existiert
|
||
|
|
Console.Write(" Prüfe Ghost...");
|
||
|
|
var existingPosts = await GhostApi.BrowsePostsAsync($"slug:{postFile.Slug}", 1);
|
||
|
|
var existing = existingPosts.FirstOrDefault();
|
||
|
|
|
||
|
|
GhostAdminPost? result = null;
|
||
|
|
if (existing != null)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" vorhanden (ID: {existing.Id})");
|
||
|
|
Console.Write(" Aktualisiere...");
|
||
|
|
result = await GhostApi.UpdatePostExtendedAsync(
|
||
|
|
existing.Id!, postFile.Title ?? "", html,
|
||
|
|
existing.UpdatedAt ?? "", postFile.Status ?? "published",
|
||
|
|
featureImageUrl, tags, postFile.Keywords);
|
||
|
|
Console.WriteLine(result != null ? " OK" : " FEHLER");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Console.WriteLine(" nicht vorhanden");
|
||
|
|
Console.Write(" Erstelle neuen Post...");
|
||
|
|
result = await GhostApi.CreatePostExtendedAsync(
|
||
|
|
postFile.Title ?? "", html,
|
||
|
|
postFile.Status ?? "published", postFile.Slug,
|
||
|
|
featureImageUrl, tags, postFile.Keywords);
|
||
|
|
Console.WriteLine(result != null ? $" OK (ID: {result.Id})" : " FEHLER");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 4. Kommentare als Members verarbeiten
|
||
|
|
if (postFile.Comments.Count > 0)
|
||
|
|
{
|
||
|
|
Console.WriteLine();
|
||
|
|
Console.Write($" {postFile.Comments.Count} Kommentare verarbeiten? (j/n): ");
|
||
|
|
string? commentConfirm = Console.ReadLine()?.Trim();
|
||
|
|
if (commentConfirm?.ToLower() == "j")
|
||
|
|
{
|
||
|
|
foreach (var comment in postFile.Comments)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(comment.AuthorEmail)) continue;
|
||
|
|
var member = await GhostApi.EnsureMemberAsync(comment.AuthorName ?? "Anonymous", comment.AuthorEmail);
|
||
|
|
if (member != null)
|
||
|
|
{
|
||
|
|
Console.WriteLine($" [Kommentar] {comment.AuthorName}: {comment.Content?.Substring(0, Math.Min(50, comment.Content?.Length ?? 0))}...");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|