94 lines
3.0 KiB
C#
94 lines
3.0 KiB
C#
|
|
using System.Net.Http.Headers;
|
||
|
|
using System.Text.Json;
|
||
|
|
|
||
|
|
namespace BuyMeCofee.Maui.Services;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Checks the Buy Me a Coffee API to determine whether a given email
|
||
|
|
/// belongs to a supporter (one-time or member). Results are cached.
|
||
|
|
/// </summary>
|
||
|
|
public class BmcSupporterService
|
||
|
|
{
|
||
|
|
private const string ApiBase = "https://developers.buymeacoffee.com/api/v1/";
|
||
|
|
|
||
|
|
private readonly HttpClient _http;
|
||
|
|
private readonly TimeSpan _cacheDuration;
|
||
|
|
private HashSet<string>? _cachedEmails;
|
||
|
|
private DateTime _cacheExpiry;
|
||
|
|
|
||
|
|
public BmcSupporterService(string accessToken, TimeSpan cacheDuration)
|
||
|
|
{
|
||
|
|
_cacheDuration = cacheDuration;
|
||
|
|
_http = new HttpClient { BaseAddress = new Uri(ApiBase) };
|
||
|
|
_http.DefaultRequestHeaders.Authorization =
|
||
|
|
new AuthenticationHeaderValue("Bearer", accessToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Returns true if the given email matches any one-time supporter
|
||
|
|
/// or active/inactive member on the creator's BMC account.
|
||
|
|
/// </summary>
|
||
|
|
public async Task<bool> IsSupporterAsync(string email)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrWhiteSpace(email)) return false;
|
||
|
|
|
||
|
|
var emails = await GetAllSupporterEmailsAsync();
|
||
|
|
return emails.Contains(email.Trim());
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task<HashSet<string>> GetAllSupporterEmailsAsync()
|
||
|
|
{
|
||
|
|
if (_cachedEmails != null && DateTime.UtcNow < _cacheExpiry)
|
||
|
|
return _cachedEmails;
|
||
|
|
|
||
|
|
var emails = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||
|
|
|
||
|
|
await FetchEmailsFromEndpoint("supporters", emails);
|
||
|
|
await FetchEmailsFromEndpoint("subscriptions", emails);
|
||
|
|
|
||
|
|
_cachedEmails = emails;
|
||
|
|
_cacheExpiry = DateTime.UtcNow + _cacheDuration;
|
||
|
|
return emails;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async Task FetchEmailsFromEndpoint(string endpoint, HashSet<string> emails)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var page = 1;
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
var response = await _http.GetAsync($"{endpoint}?page={page}");
|
||
|
|
if (!response.IsSuccessStatusCode) break;
|
||
|
|
|
||
|
|
var json = await response.Content.ReadAsStringAsync();
|
||
|
|
using var doc = JsonDocument.Parse(json);
|
||
|
|
var root = doc.RootElement;
|
||
|
|
|
||
|
|
if (root.TryGetProperty("data", out var data))
|
||
|
|
{
|
||
|
|
foreach (var item in data.EnumerateArray())
|
||
|
|
{
|
||
|
|
if (item.TryGetProperty("payer_email", out var emailProp))
|
||
|
|
{
|
||
|
|
var email = emailProp.GetString();
|
||
|
|
if (!string.IsNullOrWhiteSpace(email))
|
||
|
|
emails.Add(email);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!root.TryGetProperty("next_page_url", out var nextPage) ||
|
||
|
|
nextPage.ValueKind == JsonValueKind.Null)
|
||
|
|
break;
|
||
|
|
|
||
|
|
page++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
// Silently fail — don't block the app if BMC API is unreachable
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|