Merge pull request #6 from StevanFreeborn/stevanfreeborn/feat/trigger-speakerbot-when-donation-occurs

feat: implement triggering text to speech on successful payment
This commit is contained in:
Stevan Freeborn
2026-01-05 21:58:38 -06:00
committed by GitHub
10 changed files with 123 additions and 11 deletions
+4
View File
@@ -60,6 +60,10 @@ jobs:
--restart always \ --restart always \
-d \ -d \
-p 4343:8080 \ -p 4343:8080 \
-e StreamerBotOptions__Host=${{ secrets.STREAMER_BOT_HOST }} \
-e StreamerBotOptions__Post=${{ secrets.STREAMER_BOT_PORT }} \
-e StreamerBotOptions__ApiKey=${{ secrets.STREAMER_BOT_API_KEY }} \
-e StreamerBotOptions__TextToSpeechActionId=${{ secrets.STREAMER_BOT_ACTION_ID }} \
-e StripeOptions__ApiKey=${{ secrets.STRIPE_API_KEY }} \ -e StripeOptions__ApiKey=${{ secrets.STRIPE_API_KEY }} \
-e StripeOptions__EventsWebhookSecret=${{ secrets.STRIPE_EVENTS_WEBHOOK_SECRET }} \ -e StripeOptions__EventsWebhookSecret=${{ secrets.STRIPE_EVENTS_WEBHOOK_SECRET }} \
-e ContextOptions__DatabaseFilePath=/app/data/groundsforsupport.db \ -e ContextOptions__DatabaseFilePath=/app/data/groundsforsupport.db \
@@ -1,5 +1,6 @@
using GroundsForSupport.Server.Data; using GroundsForSupport.Server.Data;
using GroundsForSupport.Server.Payments.Stripe; using GroundsForSupport.Server.Payments.Stripe;
using GroundsForSupport.Server.TextToSpeech;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -22,7 +23,8 @@ internal static class EventsEndpoint
HttpContext httpContext, HttpContext httpContext,
[FromServices] IOptions<StripeOptions> options, [FromServices] IOptions<StripeOptions> options,
[FromServices] Context dbContext, [FromServices] Context dbContext,
[FromServices] TimeProvider timeProvider [FromServices] TimeProvider timeProvider,
[FromServices] IStreamerBotService streamerBotService
) )
{ {
var json = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); var json = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
@@ -59,6 +61,12 @@ internal static class EventsEndpoint
dbContext.Payments.Add(payment); dbContext.Payments.Add(payment);
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
await streamerBotService.TriggerTextToSpeechAsync(
name,
paymentIntent.AmountReceived,
message
);
return Results.Ok(); return Results.Ok();
} }
catch (StripeException e) catch (StripeException e)
+4
View File
@@ -5,6 +5,7 @@ using GroundsForSupport.Server.Logging;
using GroundsForSupport.Server.Payments.Endpoints; using GroundsForSupport.Server.Payments.Endpoints;
using GroundsForSupport.Server.Payments.Stripe; using GroundsForSupport.Server.Payments.Stripe;
using GroundsForSupport.Server.RateLimiting; using GroundsForSupport.Server.RateLimiting;
using GroundsForSupport.Server.TextToSpeech;
using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.HttpOverrides;
@@ -34,6 +35,9 @@ builder.Services.AddHostedService<MigrationService>();
builder.Services.ConfigureOptions<StripeOptionsSetup>(); builder.Services.ConfigureOptions<StripeOptionsSetup>();
builder.Services.AddSingleton<IStripeService, StripeService>(); builder.Services.AddSingleton<IStripeService, StripeService>();
builder.Services.ConfigureOptions<StreamerBotOptionsSetup>();
builder.Services.AddSingleton<IStreamerBotService, StreamerBotService>();
builder.Services.ConfigureHttpJsonOptions( builder.Services.ConfigureHttpJsonOptions(
static options => options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase static options => options.SerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
); );
@@ -0,0 +1,11 @@
namespace GroundsForSupport.Server.TextToSpeech;
internal interface IStreamerBotService
{
Task TriggerTextToSpeechAsync(
string name,
long amount,
string message,
CancellationToken cancellationToken = default
);
}
@@ -0,0 +1,32 @@
using Microsoft.Extensions.Options;
namespace GroundsForSupport.Server.TextToSpeech;
internal sealed record StreamerBotOptions
{
public string Host { get; init; } = string.Empty;
public int Port { get; init; }
public string ApiKey { get; init; } = string.Empty;
public string TextToSpeechActionId { get; init; } = string.Empty;
public string GetBaseUrl()
{
return $"http://{Host}:{Port}";
}
}
internal sealed record StreamerBotOptionsSetup : IConfigureOptions<StreamerBotOptions>
{
private const string SectionName = nameof(StreamerBotOptions);
private readonly IConfiguration _configuration;
public StreamerBotOptionsSetup(IConfiguration configuration)
{
_configuration = configuration;
}
public void Configure(StreamerBotOptions options)
{
_configuration.GetSection(SectionName).Bind(options);
}
}
@@ -0,0 +1,46 @@
using System.Text.Json;
using Microsoft.Extensions.Options;
namespace GroundsForSupport.Server.TextToSpeech;
internal sealed class StreamerBotService(
IHttpClientFactory httpClientFactory,
IOptions<StreamerBotOptions> options
) : IStreamerBotService
{
private const string DoActionEndpoint = "/DoAction";
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
private readonly StreamerBotOptions _options = options.Value;
public Task TriggerTextToSpeechAsync(string name, long amount, string message, CancellationToken cancellationToken = default)
{
var client = _httpClientFactory.CreateClient();
var url = new UriBuilder(_options.GetBaseUrl())
{
Path = DoActionEndpoint
}.Uri;
var request = new HttpRequestMessage(HttpMethod.Post, url);
var body = new
{
action = new
{
id = _options.TextToSpeechActionId
},
args = new
{
name,
amount = $"${amount / 100.0:F2}",
message,
apiKey = _options.ApiKey
}
};
var json = JsonSerializer.Serialize(body);
request.Content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
return client.SendAsync(request, cancellationToken);
}
}
@@ -5,5 +5,11 @@
}, },
"ContextOptions": { "ContextOptions": {
"DatabaseFilePath": "DatabaseFilePath" "DatabaseFilePath": "DatabaseFilePath"
},
"StreamerBotOptions": {
"Host": "Host",
"Port": 1234,
"ApiKey": "ApiKey",
"TextToSpeechActionId": "TextToSpeechActionId"
} }
} }
@@ -1,11 +1,12 @@
using Microsoft.AspNetCore.Hosting; using System.Text;
using Microsoft.AspNetCore.Mvc.Testing; using System.Text.Json;
using GroundsForSupport.Server.Data; using GroundsForSupport.Server.Data;
using GroundsForSupport.Server.Payments.Stripe; using GroundsForSupport.Server.Payments.Stripe;
using System.Text.Json; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System.Text;
namespace GroundsForSupport.Server.Tests.Integration.Infra; namespace GroundsForSupport.Server.Tests.Integration.Infra;