feat(ci): add supporter verification and auto-hide functionality
Implement BmcSupporterService to verify supporters via BMC API with configurable caching. Add SupporterEmail and HideIfSupporter properties to all controls to automatically hide donation prompts for existing supporters. Replace PNG logo with SVG for crisp rendering at all scales. Add BmcOptions and BmcConfiguration for library-wide settings. Bump version to 1.1.0.
This commit is contained in:
93
BuyMeCofee.Maui/Services/BmcSupporterService.cs
Normal file
93
BuyMeCofee.Maui/Services/BmcSupporterService.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user