Wp2ghost/GhostImageManager/Services/GhostApiService.cs
2026-10-14 19:12:29 +02:00

302 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text;
using System.Text.Json;
using GhostImageManager.Models;
namespace GhostImageManager.Services;
public class GhostApiService
{
private readonly HttpClient _httpClient;
private readonly ILogger<GhostApiService> _logger;
public GhostApiService(HttpClient httpClient, ILogger<GhostApiService> logger)
{
_httpClient = httpClient;
_logger = logger;
}
/// <summary>
/// Lädt alle Einträge einer Ressource ("posts" oder "pages") – seitenweise, bis nichts mehr kommt.
/// </summary>
public async Task<List<GhostPost>> GetPostsAsync(GhostInstance instance, string filter = "", int limit = 50, string resource = "posts")
{
_logger.LogInformation("Hole {Resource} von {Url} (Filter: {Filter}, Limit: {Limit})", resource, instance.Url, filter, limit);
var posts = new List<GhostPost>();
for (var page = 1; ; page++)
{
var endpoint = $"{resource}/?limit={limit}&page={page}&filter={Uri.EscapeDataString(filter)}&formats=html,lexical" +
"&fields=id,title,slug,feature_image,og_image,twitter_image,codeinjection_head,codeinjection_foot,html,lexical,created_at,updated_at,status";
var request = CreateRequest(HttpMethod.Get, instance, endpoint);
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Ghost API Fehler beim Laden von {Resource}: {StatusCode} - {Error}", resource, response.StatusCode, errorContent);
response.EnsureSuccessStatusCode();
}
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty(resource, out var postsElement) || postsElement.GetArrayLength() == 0)
break;
foreach (var post in postsElement.EnumerateArray())
{
posts.Add(new GhostPost
{
Id = GetString(post, "id"),
Title = GetString(post, "title"),
Slug = GetString(post, "slug"),
Html = GetString(post, "html"),
Lexical = GetString(post, "lexical"),
OtherImageFields = string.Join("\n",
GetString(post, "og_image"),
GetString(post, "twitter_image"),
GetString(post, "codeinjection_head"),
GetString(post, "codeinjection_foot")),
FeatureImage = GetString(post, "feature_image"),
CreatedAt = GetDateTime(post, "created_at"),
UpdatedAt = GetDateTime(post, "updated_at"),
Status = GetString(post, "status")
});
}
var totalPages = 1;
if (doc.RootElement.TryGetProperty("meta", out var meta) &&
meta.TryGetProperty("pagination", out var pagination) &&
pagination.TryGetProperty("pages", out var pagesElement))
totalPages = pagesElement.GetInt32();
if (page >= totalPages)
break;
}
_logger.LogInformation("{Count} {Resource} von Ghost geladen", posts.Count, resource);
return posts;
}
/// <summary>
/// Bildquellen außerhalb von Posts/Seiten: Tags, Benutzer und Website-Einstellungen (Logo, Icon, Cover ...).
/// Liefert je Quelle eine Bezeichnung und das rohe JSON, in dem nach Bild-URLs gesucht wird.
/// </summary>
public async Task<List<(string Label, string Json)>> GetOtherImageSourcesAsync(GhostInstance instance)
{
var sources = new List<(string Label, string Json)>();
foreach (var (resource, prefix) in new[] { ("tags", "Tag"), ("users", "Benutzer") })
{
var doc = await GetJsonAsync(instance, $"{resource}/?limit=all");
if (doc != null && doc.RootElement.TryGetProperty(resource, out var items))
{
foreach (var item in items.EnumerateArray())
sources.Add(($"{prefix}: {GetString(item, "name")}", item.GetRawText()));
}
}
var settings = await GetJsonAsync(instance, "settings/");
if (settings != null)
sources.Add(("Website-Einstellungen", settings.RootElement.GetRawText()));
return sources;
}
private async Task<JsonDocument?> GetJsonAsync(GhostInstance instance, string endpoint)
{
var request = CreateRequest(HttpMethod.Get, instance, endpoint);
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("Ghost API: {Endpoint} nicht lesbar ({StatusCode}) – Bilder daraus werden nicht als verwendet erkannt", endpoint, response.StatusCode);
return null;
}
return JsonDocument.Parse(await response.Content.ReadAsStringAsync());
}
public async Task<GhostPost?> GetPostAsync(GhostInstance instance, string postId)
{
_logger.LogInformation("Lade Post {PostId} von {Url}", postId, instance.Url);
var request = CreateRequest(HttpMethod.Get, instance, $"posts/{postId}/?fields=id,title,slug,feature_image,html,created_at,updated_at,status");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Ghost API Fehler beim Laden von Post {PostId}: {StatusCode} - {Error}", postId, response.StatusCode, errorContent);
return null;
}
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("posts", out var posts) && posts.GetArrayLength() > 0)
{
var post = posts[0];
return new GhostPost
{
Id = GetString(post, "id"),
Title = GetString(post, "title"),
Slug = GetString(post, "slug"),
Html = GetString(post, "html"),
FeatureImage = GetString(post, "feature_image"),
CreatedAt = GetDateTime(post, "created_at"),
UpdatedAt = GetDateTime(post, "updated_at"),
Status = GetString(post, "status")
};
}
return null;
}
public async Task<bool> UpdatePostAsync(GhostInstance instance, GhostPost post)
{
_logger.LogInformation("Aktualisiere Post {PostId} ({Title}) in Ghost", post.Id, post.Title);
var request = CreateRequest(HttpMethod.Put, instance, $"posts/{post.Id}/?source=html");
var body = new
{
posts = new[]
{
new
{
id = post.Id,
title = post.Title,
html = post.Html,
status = post.Status,
feature_image = post.FeatureImage,
updated_at = post.UpdatedAt?.ToString("o")
}
}
};
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Ghost API Fehler beim Aktualisieren von Post {PostId}: {StatusCode} - {Error}", post.Id, response.StatusCode, errorContent);
return false;
}
_logger.LogInformation("Post {PostId} erfolgreich aktualisiert", post.Id);
return true;
}
public async Task<List<GhostUser>> GetUsersAsync(GhostInstance instance)
{
_logger.LogInformation("Hole Benutzer von {Url}", instance.Url);
var request = CreateRequest(HttpMethod.Get, instance, "users/?limit=100&fields=id,name,slug,email,profile_image");
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Ghost API Fehler beim Laden der Benutzer: {StatusCode} - {Error}", response.StatusCode, errorContent);
return new List<GhostUser>();
}
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var users = new List<GhostUser>();
if (doc.RootElement.TryGetProperty("users", out var usersElement))
{
foreach (var user in usersElement.EnumerateArray())
{
users.Add(new GhostUser
{
Id = GetString(user, "id"),
Name = GetString(user, "name"),
Slug = GetString(user, "slug"),
Email = GetString(user, "email"),
ProfileImage = GetString(user, "profile_image")
});
}
}
_logger.LogInformation("{Count} Benutzer von Ghost geladen", users.Count);
return users;
}
public async Task<string?> UploadImageAsync(GhostInstance instance, Stream fileStream, string fileName, string? reference = null)
{
_logger.LogInformation("Lade Bild {FileName} zu Ghost hoch", fileName);
var token = JwtTokenService.GenerateToken(instance.AdminApiKey);
var request = new HttpRequestMessage(HttpMethod.Post, $"{instance.Url.TrimEnd('/')}/ghost/api/admin/images/upload/");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Ghost", token);
request.Headers.Add("Accept-Version", "v5.0");
var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream);
var ext = Path.GetExtension(fileName).ToLowerInvariant();
var mimeType = ext switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".webp" => "image/webp",
".svg" => "image/svg+xml",
_ => "application/octet-stream"
};
streamContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(mimeType);
content.Add(streamContent, "file", fileName);
if (!string.IsNullOrEmpty(reference))
content.Add(new StringContent(reference), "ref");
request.Content = content;
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("Ghost API Fehler beim Hochladen von {FileName}: {StatusCode} - {Error}", fileName, response.StatusCode, errorContent);
return null;
}
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
if (doc.RootElement.TryGetProperty("images", out var images) && images.GetArrayLength() > 0)
{
var url = GetString(images[0], "url");
_logger.LogInformation("Bild {FileName} hochgeladen: {Url}", fileName, url);
return url;
}
_logger.LogWarning("Keine URL im Upload-Response für {FileName} gefunden", fileName);
return null;
}
private HttpRequestMessage CreateRequest(HttpMethod method, GhostInstance instance, string endpoint)
{
var token = JwtTokenService.GenerateToken(instance.AdminApiKey);
var request = new HttpRequestMessage(method, $"{instance.Url.TrimEnd('/')}/ghost/api/admin/{endpoint}");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Ghost", token);
request.Headers.Add("Accept-Version", "v5.0");
return request;
}
private static string GetString(JsonElement element, string property)
{
if (element.TryGetProperty(property, out var prop) && prop.ValueKind != JsonValueKind.Null)
return prop.GetString() ?? "";
return "";
}
private static DateTime? GetDateTime(JsonElement element, string property)
{
if (element.TryGetProperty(property, out var prop) && prop.ValueKind != JsonValueKind.Null)
if (DateTime.TryParse(prop.GetString(), out var dt))
return dt;
return null;
}
}