feat: create discord message when stream goes live
This commit is contained in:
@@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StevesBot.Worker.Tests", "t
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StevesBot.Webhook.Tests", "tests\StevesBot.Webhook.Tests\StevesBot.Webhook.Tests.csproj", "{3875E45D-8400-46D9-89CE-485BADED0736}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StevesBot.Webhook.Tests", "tests\StevesBot.Webhook.Tests\StevesBot.Webhook.Tests.csproj", "{3875E45D-8400-46D9-89CE-485BADED0736}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StevesBot.Library", "src\StevesBot.Library\StevesBot.Library.csproj", "{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StevesBot.Library.Tests", "tests\StevesBot.Library.Tests\StevesBot.Library.Tests.csproj", "{F67829A2-3600-4DD9-B324-18DDF4A0809B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -73,6 +77,30 @@ Global
|
|||||||
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x64.Build.0 = Release|Any CPU
|
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x86.ActiveCfg = Release|Any CPU
|
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x86.Build.0 = Release|Any CPU
|
{3875E45D-8400-46D9-89CE-485BADED0736}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -82,5 +110,7 @@ Global
|
|||||||
{7C243CA8-55A0-4D73-9FF6-F5E801B7146D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{7C243CA8-55A0-4D73-9FF6-F5E801B7146D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{388EAD53-CCA4-4532-8DEC-5E9177776117} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
{388EAD53-CCA4-4532-8DEC-5E9177776117} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
{3875E45D-8400-46D9-89CE-485BADED0736} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
{3875E45D-8400-46D9-89CE-485BADED0736} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
|
{07B3FA35-02B7-46E6-BEDC-D64C5D0FBE81} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
{F67829A2-3600-4DD9-B324-18DDF4A0809B} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[*.cs]
|
||||||
|
dotnet_diagnostic.CA1056.severity = none
|
||||||
|
dotnet_diagnostic.CA1054.severity = none
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
namespace StevesBot.Worker.Discord.Shared;
|
namespace StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
internal sealed class DiscordClientOptions
|
public sealed class DiscordClientOptions
|
||||||
{
|
{
|
||||||
public string ApiUrl { get; init; } = string.Empty;
|
public string ApiUrl { get; init; } = string.Empty;
|
||||||
public string AppToken { get; init; } = string.Empty;
|
public string AppToken { get; init; } = string.Empty;
|
||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
namespace StevesBot.Worker.Discord.Shared;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
internal sealed record DiscordMessage
|
namespace StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
public sealed record DiscordMessage
|
||||||
{
|
{
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public string Id { get; init; } = string.Empty;
|
public string Id { get; init; } = string.Empty;
|
||||||
+4
-2
@@ -1,6 +1,8 @@
|
|||||||
namespace StevesBot.Worker.Discord.Shared;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
internal sealed record DiscordMessageReference(
|
namespace StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
public sealed record DiscordMessageReference(
|
||||||
[property: JsonPropertyName("type")] int Type,
|
[property: JsonPropertyName("type")] int Type,
|
||||||
[property: JsonPropertyName("message_id")] string MessageId,
|
[property: JsonPropertyName("message_id")] string MessageId,
|
||||||
[property: JsonPropertyName("channel_id")] string ChannelId,
|
[property: JsonPropertyName("channel_id")] string ChannelId,
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
public static class DiscordMessageReferenceTypes
|
||||||
|
{
|
||||||
|
public const int Default = 0;
|
||||||
|
public const int Forward = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
public sealed record DiscordUser
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public string Id { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
+10
-2
@@ -1,6 +1,14 @@
|
|||||||
namespace StevesBot.Worker.Discord.Rest;
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
internal sealed class DiscordRestClient : IDiscordRestClient
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
using StevesBot.Library.Discord.Rest.Requests;
|
||||||
|
using StevesBot.Library.Discord.Rest.Responses;
|
||||||
|
using StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Discord.Rest;
|
||||||
|
|
||||||
|
public sealed class DiscordRestClient : IDiscordRestClient
|
||||||
{
|
{
|
||||||
private readonly ILogger<DiscordRestClient> _logger;
|
private readonly ILogger<DiscordRestClient> _logger;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
namespace StevesBot.Worker.Discord.Rest;
|
namespace StevesBot.Library.Discord.Rest;
|
||||||
|
|
||||||
internal sealed class DiscordRestClientException : Exception
|
public sealed class DiscordRestClientException : Exception
|
||||||
{
|
{
|
||||||
public DiscordRestClientException()
|
public DiscordRestClientException()
|
||||||
{
|
{
|
||||||
+5
-2
@@ -1,6 +1,9 @@
|
|||||||
namespace StevesBot.Worker.Discord.Rest;
|
using StevesBot.Library.Discord.Rest.Requests;
|
||||||
|
using StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
internal interface IDiscordRestClient
|
namespace StevesBot.Library.Discord.Rest;
|
||||||
|
|
||||||
|
public interface IDiscordRestClient
|
||||||
{
|
{
|
||||||
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken = default);
|
Task<string> GetGatewayUrlAsync(CancellationToken cancellationToken = default);
|
||||||
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken = default);
|
Task<DiscordMessage> CreateMessageAsync(string channelId, CreateMessageRequest request, CancellationToken cancellationToken = default);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
using StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Discord.Rest.Requests;
|
||||||
|
|
||||||
|
public sealed record CreateMessageRequest(
|
||||||
|
[property: JsonPropertyName("content")] string Content,
|
||||||
|
[property: JsonPropertyName("message_reference")] DiscordMessageReference? MessageReference
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Discord.Rest.Responses;
|
||||||
|
|
||||||
|
public sealed record GatewayResponse(
|
||||||
|
[property: JsonPropertyName("url")] string Url
|
||||||
|
);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
using StevesBot.Library.Discord.Common;
|
||||||
|
using StevesBot.Library.Discord.Rest;
|
||||||
|
using StevesBot.Library.Telemetry;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Discord;
|
||||||
|
|
||||||
|
public static class ServicesExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddDiscordRestClient(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services
|
||||||
|
.AddHttpClient<IDiscordRestClient, DiscordRestClient>(static (sp, c) =>
|
||||||
|
{
|
||||||
|
var discordOptions = sp.GetRequiredService<DiscordClientOptions>();
|
||||||
|
c.BaseAddress = new Uri(discordOptions.ApiUrl);
|
||||||
|
c.DefaultRequestHeaders.Authorization = new("Bot", discordOptions.AppToken);
|
||||||
|
|
||||||
|
var userAgentString = $"DiscordBot (https://github.com/StevanFreeborn/steves-bot, {StevesBotInstrumentation.SourceVersion})";
|
||||||
|
c.DefaultRequestHeaders.Add("User-Agent", userAgentString);
|
||||||
|
})
|
||||||
|
.AddStandardResilienceHandler();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+18
-7
@@ -1,10 +1,21 @@
|
|||||||
namespace StevesBot.Worker.Telemetry;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
internal static class Extensions
|
using OpenTelemetry.Exporter;
|
||||||
|
using OpenTelemetry.Logs;
|
||||||
|
using OpenTelemetry.Resources;
|
||||||
|
using OpenTelemetry.Trace;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Telemetry;
|
||||||
|
|
||||||
|
public static class HostExtensions
|
||||||
{
|
{
|
||||||
public static HostApplicationBuilder AddTelemetry(this HostApplicationBuilder builder)
|
public static HostApplicationBuilder AddTelemetry(this HostApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddSingleton<Instrumentation>();
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<StevesBotInstrumentation>();
|
||||||
|
|
||||||
var seq = new SeqOptions();
|
var seq = new SeqOptions();
|
||||||
builder.Configuration.GetSection(nameof(SeqOptions)).Bind(seq);
|
builder.Configuration.GetSection(nameof(SeqOptions)).Bind(seq);
|
||||||
@@ -17,11 +28,11 @@ internal static class Extensions
|
|||||||
builder.Services.AddOpenTelemetry()
|
builder.Services.AddOpenTelemetry()
|
||||||
.ConfigureResource(resource =>
|
.ConfigureResource(resource =>
|
||||||
{
|
{
|
||||||
resource.AddService(Instrumentation.SourceName, Instrumentation.SourceVersion);
|
resource.AddService(StevesBotInstrumentation.SourceName, StevesBotInstrumentation.SourceVersion);
|
||||||
resource.AddAttributes(new Dictionary<string, object>
|
resource.AddAttributes(new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
["service.name"] = Instrumentation.SourceName,
|
["service.name"] = StevesBotInstrumentation.SourceName,
|
||||||
["service.version"] = Instrumentation.SourceVersion,
|
["service.version"] = StevesBotInstrumentation.SourceVersion,
|
||||||
["service.instance.id"] = Environment.MachineName,
|
["service.instance.id"] = Environment.MachineName,
|
||||||
["service.namespace"] = "stevesbot",
|
["service.namespace"] = "stevesbot",
|
||||||
["service.environment"] = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "production",
|
["service.environment"] = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "production",
|
||||||
@@ -38,7 +49,7 @@ internal static class Extensions
|
|||||||
})
|
})
|
||||||
.WithTracing(tb =>
|
.WithTracing(tb =>
|
||||||
{
|
{
|
||||||
tb.AddSource(Instrumentation.SourceName);
|
tb.AddSource(StevesBotInstrumentation.SourceName);
|
||||||
tb.AddAspNetCoreInstrumentation();
|
tb.AddAspNetCoreInstrumentation();
|
||||||
tb.AddHttpClientInstrumentation();
|
tb.AddHttpClientInstrumentation();
|
||||||
tb.AddOtlpExporter(o =>
|
tb.AddOtlpExporter(o =>
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace StevesBot.Worker.Telemetry;
|
namespace StevesBot.Library.Telemetry;
|
||||||
|
|
||||||
internal sealed class SeqOptions
|
internal sealed class SeqOptions
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace StevesBot.Library.Telemetry;
|
||||||
|
|
||||||
|
public sealed class StevesBotInstrumentation : IDisposable
|
||||||
|
{
|
||||||
|
public const string SourceName = "StevesBot.Worker";
|
||||||
|
public const string SourceVersion = "0.0.0";
|
||||||
|
public ActivitySource Source { get; } = new(SourceName, SourceVersion);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Source.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using StevesBot.Library.Discord.Rest;
|
||||||
|
using StevesBot.Library.Discord.Rest.Requests;
|
||||||
|
|
||||||
namespace StevesBot.Webhook.Handlers;
|
namespace StevesBot.Webhook.Handlers;
|
||||||
|
|
||||||
// TODO: Implement logic to do the following:
|
// TODO: Implement logic to do the following:
|
||||||
@@ -7,11 +10,13 @@ internal static class NotificationHandler
|
|||||||
public static async Task<IResult> HandleAsync(
|
public static async Task<IResult> HandleAsync(
|
||||||
HttpContext context,
|
HttpContext context,
|
||||||
[FromServices] ILogger<Program> logger,
|
[FromServices] ILogger<Program> logger,
|
||||||
[FromServices] IYouTubeDataApiClient youTubeDataApiClient
|
[FromServices] IYouTubeDataApiClient youTubeDataApiClient,
|
||||||
|
[FromServices] IDiscordRestClient discordRestClient,
|
||||||
|
CancellationToken cancellationToken
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
using StreamReader stream = new(context.Request.Body);
|
using StreamReader stream = new(context.Request.Body);
|
||||||
var body = await stream.ReadToEndAsync();
|
var body = await stream.ReadToEndAsync(cancellationToken);
|
||||||
|
|
||||||
var videoIdRegex = VideoIdRegex.Regex();
|
var videoIdRegex = VideoIdRegex.Regex();
|
||||||
var match = videoIdRegex.Match(body);
|
var match = videoIdRegex.Match(body);
|
||||||
@@ -24,7 +29,7 @@ internal static class NotificationHandler
|
|||||||
|
|
||||||
var videoId = match.Groups[1].Value;
|
var videoId = match.Groups[1].Value;
|
||||||
var parts = new string[] { "liveStreamingDetails", "snippet" };
|
var parts = new string[] { "liveStreamingDetails", "snippet" };
|
||||||
var video = await youTubeDataApiClient.GetVideoByIdAsync(videoId, parts);
|
var video = await youTubeDataApiClient.GetVideoByIdAsync(videoId, parts, cancellationToken);
|
||||||
|
|
||||||
if (video is null)
|
if (video is null)
|
||||||
{
|
{
|
||||||
@@ -40,6 +45,16 @@ internal static class NotificationHandler
|
|||||||
|
|
||||||
logger.LogInformation("Video ID {VideoId} is a live stream.", videoId);
|
logger.LogInformation("Video ID {VideoId} is a live stream.", videoId);
|
||||||
|
|
||||||
|
await discordRestClient.CreateMessageAsync(
|
||||||
|
channelId: "1372680179121524778",
|
||||||
|
request: new CreateMessageRequest(
|
||||||
|
Content: "@everyone Stevan is trying to be a streamer again! " +
|
||||||
|
$"Check out the stream here: https://www.youtube.com/watch?v={videoId}",
|
||||||
|
MessageReference: null
|
||||||
|
),
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
|
||||||
return Results.Ok();
|
return Results.Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using StevesBot.Library.Discord.Common;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services
|
builder.Services
|
||||||
@@ -41,6 +43,21 @@ builder.Services.AddSingleton(TimeProvider.System);
|
|||||||
builder.Services.AddSingleton<ConcurrentQueue<SubscribeTask>>();
|
builder.Services.AddSingleton<ConcurrentQueue<SubscribeTask>>();
|
||||||
builder.Services.AddHostedService<SubscriptionWorker>();
|
builder.Services.AddHostedService<SubscriptionWorker>();
|
||||||
|
|
||||||
|
// TODO: This probably should be
|
||||||
|
// moved into the AddDiscordRestClient extension method.
|
||||||
|
// and we should probably just take the dependency
|
||||||
|
// on IOptions<DiscordClientOptions> in DiscordRestClient
|
||||||
|
builder.Services.AddOptions<DiscordClientOptions>()
|
||||||
|
.BindConfiguration(nameof(DiscordClientOptions));
|
||||||
|
|
||||||
|
builder.Services.AddSingleton(static sp =>
|
||||||
|
{
|
||||||
|
var options = sp.GetRequiredService<IOptions<DiscordClientOptions>>().Value;
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddDiscordRestClient();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\StevesBot.Library\StevesBot.Library.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global using System.Text.RegularExpressions;
|
|||||||
global using Microsoft.AspNetCore.Mvc;
|
global using Microsoft.AspNetCore.Mvc;
|
||||||
global using Microsoft.Extensions.Options;
|
global using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
global using StevesBot.Library.Discord;
|
||||||
global using StevesBot.Webhook.Handlers;
|
global using StevesBot.Webhook.Handlers;
|
||||||
global using StevesBot.Webhook.YouTube;
|
global using StevesBot.Webhook.YouTube;
|
||||||
global using StevesBot.Webhook.YouTube.Data;
|
global using StevesBot.Webhook.YouTube.Data;
|
||||||
|
|||||||
@@ -2,21 +2,6 @@ namespace StevesBot.Worker.Discord;
|
|||||||
|
|
||||||
internal static class Extensions
|
internal static class Extensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddDiscordRestClient(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
services
|
|
||||||
.AddHttpClient<IDiscordRestClient, DiscordRestClient>(static (sp, c) =>
|
|
||||||
{
|
|
||||||
var discordOptions = sp.GetRequiredService<DiscordClientOptions>();
|
|
||||||
c.BaseAddress = new Uri(discordOptions.ApiUrl);
|
|
||||||
c.DefaultRequestHeaders.Authorization = new("Bot", discordOptions.AppToken);
|
|
||||||
c.DefaultRequestHeaders.Add("User-Agent", $"DiscordBot (https://github.com/StevanFreeborn/steves-bot, {Instrumentation.SourceVersion})");
|
|
||||||
})
|
|
||||||
.AddStandardResilienceHandler();
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddDiscordGatewayClient(this IServiceCollection services, Action<IDiscordGatewayClient>? configure = null)
|
public static IServiceCollection AddDiscordGatewayClient(this IServiceCollection services, Action<IDiscordGatewayClient>? configure = null)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IDiscordGatewayClient>(sp =>
|
services.AddSingleton<IDiscordGatewayClient>(sp =>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace StevesBot.Worker.Discord.Rest.Requests;
|
|
||||||
|
|
||||||
internal sealed record CreateMessageRequest(
|
|
||||||
[property: JsonPropertyName("content")] string Content,
|
|
||||||
[property: JsonPropertyName("message_reference")] DiscordMessageReference? MessageReference
|
|
||||||
);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace StevesBot.Worker.Discord.Rest.Responses;
|
|
||||||
|
|
||||||
internal sealed record GatewayResponse(
|
|
||||||
[property: JsonPropertyName("url")] string Url
|
|
||||||
);
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace StevesBot.Worker.Discord.Shared;
|
|
||||||
|
|
||||||
internal static class DiscordMessageReferenceTypes
|
|
||||||
{
|
|
||||||
public const int Default = 0;
|
|
||||||
public const int Forward = 1;
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace StevesBot.Worker.Discord.Shared;
|
|
||||||
|
|
||||||
internal sealed record DiscordUser
|
|
||||||
{
|
|
||||||
[JsonPropertyName("id")]
|
|
||||||
public string Id { get; init; } = string.Empty;
|
|
||||||
}
|
|
||||||
@@ -8,15 +8,9 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.Console" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
|
||||||
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
|
<ProjectReference Include="..\StevesBot.Library\StevesBot.Library.csproj" />
|
||||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace StevesBot.Worker.Telemetry;
|
|
||||||
|
|
||||||
internal sealed class Instrumentation : IDisposable
|
|
||||||
{
|
|
||||||
public const string SourceName = "StevesBot.Worker";
|
|
||||||
public const string SourceVersion = "0.0.0";
|
|
||||||
public readonly ActivitySource Source = new(SourceName, SourceVersion);
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Source.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
global using System.Diagnostics;
|
|
||||||
global using System.Net.Http.Json;
|
|
||||||
global using System.Net.WebSockets;
|
global using System.Net.WebSockets;
|
||||||
global using System.Reflection;
|
global using System.Reflection;
|
||||||
global using System.Text;
|
global using System.Text;
|
||||||
@@ -8,21 +6,17 @@ global using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
global using Microsoft.Extensions.Options;
|
global using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
global using OpenTelemetry.Exporter;
|
global using StevesBot.Library.Discord;
|
||||||
global using OpenTelemetry.Logs;
|
global using StevesBot.Library.Telemetry;
|
||||||
global using OpenTelemetry.Resources;
|
|
||||||
global using OpenTelemetry.Trace;
|
|
||||||
|
|
||||||
global using StevesBot.Worker;
|
global using StevesBot.Worker;
|
||||||
global using StevesBot.Worker.Discord;
|
global using StevesBot.Worker.Discord;
|
||||||
global using StevesBot.Worker.Discord.Gateway;
|
global using StevesBot.Worker.Discord.Gateway;
|
||||||
global using StevesBot.Worker.Discord.Gateway.Events;
|
global using StevesBot.Worker.Discord.Gateway.Events;
|
||||||
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||||
global using StevesBot.Worker.Discord.Rest;
|
global using StevesBot.Library.Discord.Rest;
|
||||||
global using StevesBot.Worker.Discord.Rest.Requests;
|
global using StevesBot.Library.Discord.Rest.Requests;
|
||||||
global using StevesBot.Worker.Discord.Rest.Responses;
|
global using StevesBot.Library.Discord.Common;
|
||||||
global using StevesBot.Worker.Discord.Shared;
|
|
||||||
global using StevesBot.Worker.Handlers;
|
global using StevesBot.Worker.Handlers;
|
||||||
global using StevesBot.Worker.Telemetry;
|
|
||||||
global using StevesBot.Worker.Threading;
|
global using StevesBot.Worker.Threading;
|
||||||
global using StevesBot.Worker.WebSockets;
|
global using StevesBot.Worker.WebSockets;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.msbuild">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="FluentAssertions" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||||
|
<PackageReference Include="moq" />
|
||||||
|
<PackageReference Include="RichardSzalay.MockHttp" />
|
||||||
|
<PackageReference Include="xunit" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\StevesBot.Library\StevesBot.Library.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<CollectCoverage>true</CollectCoverage>
|
||||||
|
<CoverletOutput>./TestResults/Coverage/</CoverletOutput>
|
||||||
|
<CoverletOutputFormat>cobertura</CoverletOutputFormat>
|
||||||
|
<Include>[StevesBot.Lib]*</Include>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="GenerateHtmlCoverageReport" AfterTargets="GenerateCoverageResultAfterTest">
|
||||||
|
<Exec Command="reportgenerator -reports:./TestResults/Coverage/*.xml -targetdir:./TestResults/Coverage/Report/ -reporttypes:Html_Dark" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace StevesBot.Webhook.Tests;
|
|
||||||
|
|
||||||
public class UnitTest1
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Test1()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,13 +16,14 @@ global using Moq;
|
|||||||
|
|
||||||
global using RichardSzalay.MockHttp;
|
global using RichardSzalay.MockHttp;
|
||||||
|
|
||||||
|
global using StevesBot.Library.Discord;
|
||||||
|
global using StevesBot.Library.Discord.Common;
|
||||||
|
global using StevesBot.Library.Discord.Rest;
|
||||||
|
global using StevesBot.Library.Discord.Rest.Requests;
|
||||||
global using StevesBot.Worker.Discord;
|
global using StevesBot.Worker.Discord;
|
||||||
global using StevesBot.Worker.Discord.Gateway;
|
global using StevesBot.Worker.Discord.Gateway;
|
||||||
global using StevesBot.Worker.Discord.Gateway.Events;
|
global using StevesBot.Worker.Discord.Gateway.Events;
|
||||||
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
global using StevesBot.Worker.Discord.Gateway.Events.Data;
|
||||||
global using StevesBot.Worker.Discord.Rest;
|
|
||||||
global using StevesBot.Worker.Discord.Rest.Requests;
|
|
||||||
global using StevesBot.Worker.Discord.Shared;
|
|
||||||
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
global using StevesBot.Worker.Tests.Integration.Infrastructure;
|
||||||
global using StevesBot.Worker.Threading;
|
global using StevesBot.Worker.Threading;
|
||||||
global using StevesBot.Worker.WebSockets;
|
global using StevesBot.Worker.WebSockets;
|
||||||
Reference in New Issue
Block a user