Initial commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
using ListmonkIntegration.Models;
|
||||
using TemplateInfo = ListmonkIntegration.Models.TemplateInfo;
|
||||
|
||||
namespace ListmonkIntegration.Services
|
||||
{
|
||||
public interface IListmonkService
|
||||
{
|
||||
Task<bool> TestConnectionAsync();
|
||||
Task<int> CreateListAsync(string listName);
|
||||
Task<int> CreateTemplateAsync(string name, string subject, string body);
|
||||
Task<int> CreateCampaignAsync(string name, int listId, int templateId, string subject);
|
||||
Task<bool> StartCampaignAsync(int campaignId);
|
||||
Task<List<ListInfo>> GetListsAsync();
|
||||
Task<List<string>> GetListAttributesAsync(int listId);
|
||||
Task<List<TemplateInfo>> GetTemplatesAsync();
|
||||
Task<string> GetTemplateBodyAsync(int templateId);
|
||||
Task<Subscriber?> GetSubscriberByEmailAsync(string email);
|
||||
Task<bool> UpdateSubscriberAsync(int subscriberId, string email, string name, Dictionary<string, string> attributes, List<int> listIds);
|
||||
Task<bool> ImportSubscribersAsync(List<int> listIds, List<Subscriber> subscribers);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,811 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ListmonkIntegration.Models;
|
||||
using TemplateInfo = ListmonkIntegration.Models.TemplateInfo;
|
||||
|
||||
namespace ListmonkIntegration.Services
|
||||
{
|
||||
public class ListmonkService : IListmonkService
|
||||
{
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ListmonkSettings _settings;
|
||||
private readonly ILogger<ListmonkService> _logger;
|
||||
|
||||
public ListmonkService(
|
||||
HttpClient httpClient,
|
||||
ListmonkSettings settings,
|
||||
ILogger<ListmonkService> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
|
||||
// Настройка базового адреса
|
||||
_httpClient.BaseAddress = new Uri(_settings.BaseUrl);
|
||||
|
||||
// Listmonk использует Basic Auth с API credentials
|
||||
// Username: логин пользователя (например, "admin")
|
||||
// Password: API токен (d1qPRY5jl1LsD1LskI4zWu4ukYWTyNLxjot05CjgRin4nQy3)
|
||||
var username = _settings.Username;
|
||||
var password = !string.IsNullOrEmpty(_settings.ApiToken)
|
||||
? _settings.ApiToken
|
||||
: _settings.Password;
|
||||
|
||||
var authToken = Convert.ToBase64String(
|
||||
Encoding.ASCII.GetBytes($"{username}:{password}"));
|
||||
|
||||
_httpClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Basic", authToken);
|
||||
|
||||
_logger.LogInformation($"Using Basic Auth with username: {username}");
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"Testing connection to Listmonk...");
|
||||
_logger.LogInformation($"Base URL: {_settings.BaseUrl}");
|
||||
_logger.LogInformation($"Username: {_settings.Username}");
|
||||
|
||||
var response = await _httpClient.GetAsync("/api/config");
|
||||
|
||||
_logger.LogInformation($"Response status: {response.StatusCode}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Success! Connected to Listmonk");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogWarning($"Failed! Status: {response.StatusCode}, Content: {errorContent}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Exception during connection test: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CreateListAsync(string listName)
|
||||
{
|
||||
_logger.LogInformation($"Creating list: {listName}");
|
||||
|
||||
// Сначала проверим, нет ли уже списка с таким именем
|
||||
var existingListId = await FindListIdByNameAsync(listName);
|
||||
if (existingListId.HasValue)
|
||||
{
|
||||
_logger.LogInformation($"List '{listName}' already exists with ID {existingListId.Value}");
|
||||
return existingListId.Value;
|
||||
}
|
||||
|
||||
var content = new StringContent(
|
||||
JsonSerializer.Serialize(new
|
||||
{
|
||||
name = listName,
|
||||
type = "private",
|
||||
optin = "single"
|
||||
}),
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/lists", content);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
_logger.LogInformation($"Create list response: {responseBody}");
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
// Listmonk возвращает {"data": {"id": 1, ...}}
|
||||
var listId = json.RootElement
|
||||
.GetProperty("data")
|
||||
.GetProperty("id")
|
||||
.GetInt32();
|
||||
|
||||
_logger.LogInformation($"List created with ID: {listId}");
|
||||
return listId;
|
||||
}
|
||||
|
||||
private async Task<int?> FindListIdByNameAsync(string listName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync("/api/lists");
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Lists response: {responseBody.Substring(0, Math.Min(200, responseBody.Length))}...");
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
// Структура: {"data": {"results": [...]}}
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
JsonElement results;
|
||||
if (data.ValueKind == JsonValueKind.Object &&
|
||||
data.TryGetProperty("results", out results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var list in results.EnumerateArray())
|
||||
{
|
||||
var name = list.GetProperty("name").GetString();
|
||||
if (name == listName)
|
||||
{
|
||||
var id = list.GetProperty("id").GetInt32();
|
||||
_logger.LogInformation($"Found existing list '{listName}' with ID {id}");
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var list in data.EnumerateArray())
|
||||
{
|
||||
var name = list.GetProperty("name").GetString();
|
||||
if (name == listName)
|
||||
{
|
||||
var id = list.GetProperty("id").GetInt32();
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"Error searching for list '{listName}'");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<bool> ImportSubscribersAsync(List<int> listIds, List<Subscriber> subscribers)
|
||||
{
|
||||
_logger.LogInformation($"Importing {subscribers.Count} subscribers to lists: {string.Join(", ", listIds)}");
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
int updatedCount = 0;
|
||||
int createdCount = 0;
|
||||
|
||||
foreach (var sub in subscribers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Проверяем, существует ли подписчик
|
||||
var existingSubscriber = await GetSubscriberByEmailAsync(sub.Email);
|
||||
|
||||
if (existingSubscriber != null)
|
||||
{
|
||||
// Подписчик существует - обновляем его
|
||||
_logger.LogInformation($"Subscriber {sub.Email} exists, updating...");
|
||||
|
||||
// Получаем ID существующего подписчика
|
||||
var subscriberId = await GetSubscriberIdByEmailAsync(sub.Email);
|
||||
if (subscriberId.HasValue)
|
||||
{
|
||||
// Получаем текущие списки подписчика
|
||||
var currentListIds = await GetSubscriberListIdsAsync(subscriberId.Value);
|
||||
|
||||
// Добавляем новые списки к существующим
|
||||
var allListIds = new List<int>(currentListIds);
|
||||
foreach (var listId in listIds)
|
||||
{
|
||||
if (!allListIds.Contains(listId))
|
||||
{
|
||||
allListIds.Add(listId);
|
||||
}
|
||||
}
|
||||
|
||||
// Объединяем атрибуты
|
||||
var mergedAttributes = new Dictionary<string, string>(existingSubscriber.Attributes);
|
||||
foreach (var attr in sub.Attributes)
|
||||
{
|
||||
mergedAttributes[attr.Key] = attr.Value;
|
||||
}
|
||||
|
||||
// Обновляем подписчика
|
||||
var updated = await UpdateSubscriberAsync(
|
||||
subscriberId.Value,
|
||||
sub.Email,
|
||||
sub.FullName,
|
||||
mergedAttributes,
|
||||
allListIds
|
||||
);
|
||||
|
||||
if (updated)
|
||||
{
|
||||
updatedCount++;
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Создаём нового подписчика
|
||||
await AddSubscriberToList(listIds, sub);
|
||||
createdCount++;
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failCount++;
|
||||
_logger.LogWarning(ex, $"Failed to process subscriber {sub.Email}");
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Import completed: {successCount} success ({createdCount} created, {updatedCount} updated), {failCount} failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<List<int>> GetSubscriberListIdsAsync(int subscriberId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"/api/subscribers/{subscriberId}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return new List<int>();
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
var listIds = new List<int>();
|
||||
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
if (data.TryGetProperty("lists", out var lists))
|
||||
{
|
||||
foreach (var list in lists.EnumerateArray())
|
||||
{
|
||||
if (list.TryGetProperty("id", out var id))
|
||||
{
|
||||
listIds.Add(id.GetInt32());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listIds;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error getting list IDs for subscriber {subscriberId}");
|
||||
return new List<int>();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AddSubscriberToList(List<int> listIds, Subscriber subscriber)
|
||||
{
|
||||
var subscriberData = new
|
||||
{
|
||||
email = subscriber.Email,
|
||||
name = subscriber.FullName,
|
||||
lists = listIds, // Массив ID списков
|
||||
attribs = subscriber.Attributes,
|
||||
status = "enabled",
|
||||
mode = "subscribe"
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(subscriberData);
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/subscribers", content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogWarning($"Failed to add subscriber {subscriber.Email}: {response.StatusCode} - {errorContent}");
|
||||
throw new Exception($"Failed to add subscriber: {errorContent}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CreateCampaignAsync(string name, int listId, int templateId)
|
||||
{
|
||||
var content = new StringContent(
|
||||
JsonSerializer.Serialize(new
|
||||
{
|
||||
name = name,
|
||||
subject = "Тема письма",
|
||||
lists = new[] { listId },
|
||||
template_id = templateId,
|
||||
type = "regular",
|
||||
content_type = "html"
|
||||
}),
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/campaigns", content);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public async Task<List<ListInfo>> GetListsAsync()
|
||||
{
|
||||
_logger.LogInformation("Fetching lists from Listmonk");
|
||||
|
||||
var response = await _httpClient.GetAsync("/api/lists");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogError($"Failed to fetch lists: {response.StatusCode} - {errorContent}");
|
||||
throw new Exception($"Failed to fetch lists: {response.StatusCode}");
|
||||
}
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Raw response: {responseBody.Substring(0, Math.Min(500, responseBody.Length))}");
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
var lists = new List<ListInfo>();
|
||||
|
||||
// Структура: {"data": {"results": [...]}}
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
if (data.ValueKind == JsonValueKind.Object &&
|
||||
data.TryGetProperty("results", out var results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var list in results.EnumerateArray())
|
||||
{
|
||||
var listInfo = new ListInfo
|
||||
{
|
||||
Id = list.GetProperty("id").GetInt32(),
|
||||
Name = list.GetProperty("name").GetString() ?? string.Empty,
|
||||
SubscriberCount = list.TryGetProperty("subscriber_count", out var countProp)
|
||||
? countProp.GetInt32()
|
||||
: 0
|
||||
};
|
||||
lists.Add(listInfo);
|
||||
_logger.LogInformation($"Parsed list: ID={listInfo.Id}, Name={listInfo.Name}, Subscribers={listInfo.SubscriberCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
// На случай, если Listmonk вернёт массив напрямую
|
||||
foreach (var list in data.EnumerateArray())
|
||||
{
|
||||
var listInfo = new ListInfo
|
||||
{
|
||||
Id = list.GetProperty("id").GetInt32(),
|
||||
Name = list.GetProperty("name").GetString() ?? string.Empty,
|
||||
SubscriberCount = list.TryGetProperty("subscriber_count", out var countProp)
|
||||
? countProp.GetInt32()
|
||||
: 0
|
||||
};
|
||||
lists.Add(listInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Total lists found: {lists.Count}");
|
||||
return lists;
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetListAttributesAsync(int listId)
|
||||
{
|
||||
_logger.LogInformation($"Fetching attributes for list {listId}");
|
||||
|
||||
// Получаем ТОЛЬКО подписчиков из конкретного списка
|
||||
var response = await _httpClient.GetAsync($"/api/subscribers?list_id={listId}&per_page=1000");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning($"Failed to fetch subscribers for list {listId}: {response.StatusCode}");
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Subscribers response for list {listId}: {responseBody.Substring(0, Math.Min(500, responseBody.Length))}");
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
var attributes = new HashSet<string>();
|
||||
|
||||
// Структура: {"data": {"results": [...]}}
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
JsonElement results;
|
||||
if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("results", out results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var subscriber in results.EnumerateArray())
|
||||
{
|
||||
if (subscriber.TryGetProperty("attribs", out var attribs))
|
||||
{
|
||||
foreach (var prop in attribs.EnumerateObject())
|
||||
{
|
||||
attributes.Add(prop.Name);
|
||||
_logger.LogDebug($"Found attribute '{prop.Name}' for list {listId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var subscriber in data.EnumerateArray())
|
||||
{
|
||||
if (subscriber.TryGetProperty("attribs", out var attribs))
|
||||
{
|
||||
foreach (var prop in attribs.EnumerateObject())
|
||||
{
|
||||
attributes.Add(prop.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = attributes.ToList();
|
||||
_logger.LogInformation($"Found {result.Count} unique attributes for list {listId}: {string.Join(", ", result)}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> CreateTemplateAsync(string name, string subject, string body)
|
||||
{
|
||||
_logger.LogInformation($"Creating template: {name}");
|
||||
|
||||
// Listmonk требует плейсхолдер {{ template "content" . }} в теле шаблона
|
||||
// Если его нет — добавляем автоматически
|
||||
if (!body.Contains("{{ template \"content\" . }}"))
|
||||
{
|
||||
body = body.TrimEnd() + "\n\n{{ template \"content\" . }}";
|
||||
_logger.LogInformation("Added required placeholder to template body");
|
||||
}
|
||||
|
||||
var templateData = new
|
||||
{
|
||||
name = name,
|
||||
subject = subject,
|
||||
body = body,
|
||||
type = "campaign"
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(templateData);
|
||||
_logger.LogInformation($"Request body: {jsonContent}");
|
||||
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/templates", content);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
_logger.LogInformation($"Response status: {response.StatusCode}");
|
||||
_logger.LogInformation($"Response body: {responseBody}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError($"Failed to create template: {response.StatusCode} - {responseBody}");
|
||||
throw new Exception($"Failed to create template: {responseBody}");
|
||||
}
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
int templateId;
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
templateId = data.GetProperty("id").GetInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
templateId = json.RootElement.GetProperty("id").GetInt32();
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Template created with ID: {templateId}");
|
||||
return templateId;
|
||||
}
|
||||
public async Task<List<TemplateInfo>> GetTemplatesAsync()
|
||||
{
|
||||
_logger.LogInformation("Fetching templates from Listmonk");
|
||||
|
||||
var response = await _httpClient.GetAsync("/api/templates");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogError($"Failed to fetch templates: {response.StatusCode} - {error}");
|
||||
return new List<TemplateInfo>();
|
||||
}
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Templates response: {responseBody.Substring(0, Math.Min(500, responseBody.Length))}");
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
var templates = new List<TemplateInfo>();
|
||||
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
JsonElement results;
|
||||
if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("results", out results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var template in results.EnumerateArray())
|
||||
{
|
||||
templates.Add(new TemplateInfo
|
||||
{
|
||||
Id = template.GetProperty("id").GetInt32(),
|
||||
Name = template.GetProperty("name").GetString() ?? string.Empty,
|
||||
Subject = template.TryGetProperty("subject", out var subj)
|
||||
? subj.GetString() ?? string.Empty
|
||||
: string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (data.ValueKind == JsonValueKind.Array)
|
||||
{
|
||||
foreach (var template in data.EnumerateArray())
|
||||
{
|
||||
templates.Add(new TemplateInfo
|
||||
{
|
||||
Id = template.GetProperty("id").GetInt32(),
|
||||
Name = template.GetProperty("name").GetString() ?? string.Empty,
|
||||
Subject = template.TryGetProperty("subject", out var subj)
|
||||
? subj.GetString() ?? string.Empty
|
||||
: string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Found {templates.Count} templates");
|
||||
return templates;
|
||||
}
|
||||
|
||||
public async Task<string> GetTemplateBodyAsync(int templateId)
|
||||
{
|
||||
_logger.LogInformation($"Fetching template {templateId}");
|
||||
|
||||
var response = await _httpClient.GetAsync($"/api/templates/{templateId}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
if (data.TryGetProperty("body", out var body))
|
||||
{
|
||||
return body.GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public async Task<int> CreateCampaignAsync(string name, int listId, int templateId, string subject)
|
||||
{
|
||||
_logger.LogInformation($"Creating campaign: {name}");
|
||||
_logger.LogInformation($"List ID: {listId}, Template ID: {templateId}, Subject: {subject}");
|
||||
|
||||
// Убираем send_at - кампания создастся как черновик, потом запустим отдельно
|
||||
var campaignData = new
|
||||
{
|
||||
name = name,
|
||||
subject = subject,
|
||||
lists = new[] { listId },
|
||||
template_id = templateId,
|
||||
type = "regular",
|
||||
content_type = "html",
|
||||
messenger = "email"
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(campaignData);
|
||||
_logger.LogInformation($"Request body: {jsonContent}");
|
||||
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync("/api/campaigns", content);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
_logger.LogInformation($"Response status: {response.StatusCode}");
|
||||
_logger.LogInformation($"Response body: {responseBody}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError($"Failed to create campaign: {response.StatusCode} - {responseBody}");
|
||||
throw new Exception($"Failed to create campaign: {responseBody}");
|
||||
}
|
||||
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
int campaignId;
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
campaignId = data.GetProperty("id").GetInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
campaignId = json.RootElement.GetProperty("id").GetInt32();
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Campaign created with ID: {campaignId}");
|
||||
return campaignId;
|
||||
}
|
||||
|
||||
public async Task<bool> StartCampaignAsync(int campaignId)
|
||||
{
|
||||
_logger.LogInformation($"Starting campaign {campaignId}");
|
||||
|
||||
var statusData = new
|
||||
{
|
||||
status = "running"
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(statusData);
|
||||
_logger.LogInformation($"Request body: {jsonContent}");
|
||||
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
|
||||
// ВАЖНО: PUT, не POST!
|
||||
var request = new HttpRequestMessage(HttpMethod.Put, $"/api/campaigns/{campaignId}/status")
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
_logger.LogInformation($"Response status: {response.StatusCode}");
|
||||
_logger.LogInformation($"Response body: {responseBody}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogError($"Failed to start campaign: {response.StatusCode} - {responseBody}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Campaign {campaignId} started successfully");
|
||||
return true;
|
||||
}
|
||||
public async Task<Subscriber?> GetSubscriberByEmailAsync(string email)
|
||||
{
|
||||
_logger.LogInformation($"Searching for subscriber by email: {email}");
|
||||
|
||||
try
|
||||
{
|
||||
// Ищем подписчика по email
|
||||
var response = await _httpClient.GetAsync($"/api/subscribers?query=LOWER(email)=LOWER('{email}')&per_page=1");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning($"Failed to search subscriber: {response.StatusCode}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
JsonElement results;
|
||||
if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("results", out results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
|
||||
{
|
||||
var subscriberData = results[0];
|
||||
|
||||
// Парсим атрибуты
|
||||
var attributes = new Dictionary<string, string>();
|
||||
if (subscriberData.TryGetProperty("attribs", out var attribs))
|
||||
{
|
||||
foreach (var prop in attribs.EnumerateObject())
|
||||
{
|
||||
attributes[prop.Name] = prop.Value.GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем списки
|
||||
var listIds = new List<int>();
|
||||
if (subscriberData.TryGetProperty("lists", out var lists))
|
||||
{
|
||||
foreach (var list in lists.EnumerateArray())
|
||||
{
|
||||
if (list.TryGetProperty("id", out var id))
|
||||
{
|
||||
listIds.Add(id.GetInt32());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Subscriber
|
||||
{
|
||||
Email = subscriberData.GetProperty("email").GetString() ?? string.Empty,
|
||||
FullName = subscriberData.GetProperty("name").GetString() ?? string.Empty,
|
||||
Attributes = attributes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error searching subscriber {email}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateSubscriberAsync(int subscriberId, string email, string name, Dictionary<string, string> attributes, List<int> listIds)
|
||||
{
|
||||
_logger.LogInformation($"Updating subscriber {subscriberId} ({email})");
|
||||
|
||||
var updateData = new
|
||||
{
|
||||
email = email,
|
||||
name = name,
|
||||
attribs = attributes,
|
||||
lists = listIds,
|
||||
status = "enabled"
|
||||
};
|
||||
|
||||
var jsonContent = JsonSerializer.Serialize(updateData);
|
||||
_logger.LogInformation($"Update data: {jsonContent}");
|
||||
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
var response = await _httpClient.PutAsync($"/api/subscribers/{subscriberId}", content);
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogInformation($"Update response: {response.StatusCode} - {responseBody}");
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
private async Task<int?> GetSubscriberIdByEmailAsync(string email)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"/api/subscribers?query=LOWER(email)=LOWER('{email}')&per_page=1");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var responseBody = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonDocument.Parse(responseBody);
|
||||
|
||||
if (json.RootElement.TryGetProperty("data", out var data))
|
||||
{
|
||||
JsonElement results;
|
||||
if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("results", out results))
|
||||
{
|
||||
if (results.ValueKind == JsonValueKind.Array && results.GetArrayLength() > 0)
|
||||
{
|
||||
return results[0].GetProperty("id").GetInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error getting subscriber ID for {email}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user