feat: implement song submission support, refs #5
This commit is contained in:
		
							
								
								
									
										170
									
								
								song_of_the_day/Data/Migrations/20250601144913_some more model updates.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								song_of_the_day/Data/Migrations/20250601144913_some more model updates.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					// <auto-generated />
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Infrastructure;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 | 
				
			||||||
 | 
					using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace song_of_the_day.DataMigrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    [DbContext(typeof(DataContext))]
 | 
				
			||||||
 | 
					    [Migration("20250601144913_some more model updates")]
 | 
				
			||||||
 | 
					    partial class somemoremodelupdates
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void BuildTargetModel(ModelBuilder modelBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					#pragma warning disable 612, 618
 | 
				
			||||||
 | 
					            modelBuilder
 | 
				
			||||||
 | 
					                .HasAnnotation("ProductVersion", "9.0.3")
 | 
				
			||||||
 | 
					                .HasAnnotation("Relational:MaxIdentifierLength", 63);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("Song", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("SongId")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SongId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Artist")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("Provider")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("SpotifyId")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Url")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("Songs");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SongSuggestion", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<DateTime>("Date")
 | 
				
			||||||
 | 
					                        .HasColumnType("timestamp with time zone");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("HasUsedSuggestion")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("SongId")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int>("SuggestionHelperId")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("UserHasSubmitted")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("UserId")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("SuggestionHelperId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasIndex("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("SongSuggestions");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SuggestionHelper", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("Id")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Description")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Title")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("Id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("SuggestionHelpers");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("User", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.Property<int>("UserId")
 | 
				
			||||||
 | 
					                        .ValueGeneratedOnAdd()
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("AssociationInProgress")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("IsIntroduced")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("LdapUserName")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("NickName")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("SignalMemberId")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<bool>("WasChosenForSuggestionThisRound")
 | 
				
			||||||
 | 
					                        .HasColumnType("boolean");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasKey("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.ToTable("Users");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            modelBuilder.Entity("SongSuggestion", b =>
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    b.HasOne("Song", "Song")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("SongId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasOne("SuggestionHelper", "SuggestionHelper")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("SuggestionHelperId")
 | 
				
			||||||
 | 
					                        .OnDelete(DeleteBehavior.Cascade)
 | 
				
			||||||
 | 
					                        .IsRequired();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.HasOne("User", "User")
 | 
				
			||||||
 | 
					                        .WithMany()
 | 
				
			||||||
 | 
					                        .HasForeignKey("UserId");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("Song");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("SuggestionHelper");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Navigation("User");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					#pragma warning restore 612, 618
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					using Microsoft.EntityFrameworkCore.Migrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#nullable disable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace song_of_the_day.DataMigrations
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /// <inheritdoc />
 | 
				
			||||||
 | 
					    public partial class somemoremodelupdates : Migration
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Up(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<int>(
 | 
				
			||||||
 | 
					                name: "Provider",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "integer",
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.AddColumn<string>(
 | 
				
			||||||
 | 
					                name: "SpotifyId",
 | 
				
			||||||
 | 
					                table: "Songs",
 | 
				
			||||||
 | 
					                type: "text",
 | 
				
			||||||
 | 
					                nullable: true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        protected override void Down(MigrationBuilder migrationBuilder)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "Provider",
 | 
				
			||||||
 | 
					                table: "Songs");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            migrationBuilder.DropColumn(
 | 
				
			||||||
 | 
					                name: "SpotifyId",
 | 
				
			||||||
 | 
					                table: "Songs");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -35,6 +35,12 @@ namespace song_of_the_day.DataMigrations
 | 
				
			|||||||
                    b.Property<string>("Name")
 | 
					                    b.Property<string>("Name")
 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<int?>("Provider")
 | 
				
			||||||
 | 
					                        .HasColumnType("integer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    b.Property<string>("SpotifyId")
 | 
				
			||||||
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    b.Property<string>("Url")
 | 
					                    b.Property<string>("Url")
 | 
				
			||||||
                        .HasColumnType("text");
 | 
					                        .HasColumnType("text");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using song_of_the_day;
 | 
					using song_of_the_day;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class LinkPreviewAttachment
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string Url { get; set; }
 | 
				
			||||||
 | 
					    public string Title { get; set; }
 | 
				
			||||||
 | 
					    public string Description { get; set; }
 | 
				
			||||||
 | 
					    public string Base64Image { get; set; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class SignalIntegration
 | 
					public class SignalIntegration
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly ILogger<SignalIntegration> logger;
 | 
					    private readonly ILogger<SignalIntegration> logger;
 | 
				
			||||||
@@ -15,7 +23,8 @@ public class SignalIntegration
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var http = new HttpClient()
 | 
					        var http = new HttpClient()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            BaseAddress = new Uri(uri + ":" + port)
 | 
					            BaseAddress = new Uri(uri + ":" + port),
 | 
				
			||||||
 | 
					            Timeout = TimeSpan.FromSeconds(180),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        apiClient = new swaggerClient(http);
 | 
					        apiClient = new swaggerClient(http);
 | 
				
			||||||
        apiClient.BaseUrl = "";
 | 
					        apiClient.BaseUrl = "";
 | 
				
			||||||
@@ -26,6 +35,12 @@ public class SignalIntegration
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private string phoneNumber;
 | 
					    private string phoneNumber;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task GetMessagesAsync()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var messages = await apiClient.ReceiveAsync(this.phoneNumber, "120", "true", "true", "50", "false");
 | 
				
			||||||
 | 
					        logger.LogInformation($"Received {messages.Count} Signal messages.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task ListGroupsAsync()
 | 
					    public async Task ListGroupsAsync()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        logger.LogDebug("Listing all groups for phone number: {PhoneNumber}", this.phoneNumber);
 | 
					        logger.LogDebug("Listing all groups for phone number: {PhoneNumber}", this.phoneNumber);
 | 
				
			||||||
@@ -51,7 +66,7 @@ public class SignalIntegration
 | 
				
			|||||||
            SendMessageV2 data = new SendMessageV2();
 | 
					            SendMessageV2 data = new SendMessageV2();
 | 
				
			||||||
            data.Recipients = new List<string>();
 | 
					            data.Recipients = new List<string>();
 | 
				
			||||||
            data.Recipients.Add(AppConfiguration.Instance.SignalGroupId);
 | 
					            data.Recipients.Add(AppConfiguration.Instance.SignalGroupId);
 | 
				
			||||||
            data.Message = message;
 | 
					            data.Message = (AppConfiguration.Instance.UseBotTag ? "**[Proggy]**\n" : "") + message;
 | 
				
			||||||
            data.Text_mode = SendMessageV2Text_mode.Styled;
 | 
					            data.Text_mode = SendMessageV2Text_mode.Styled;
 | 
				
			||||||
            data.Number = AppConfiguration.Instance.HostPhoneNumber;
 | 
					            data.Number = AppConfiguration.Instance.HostPhoneNumber;
 | 
				
			||||||
            var response = await apiClient.Send2Async(data);
 | 
					            var response = await apiClient.Send2Async(data);
 | 
				
			||||||
@@ -62,6 +77,29 @@ public class SignalIntegration
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task SendMessageToGroupAsync(string message, LinkPreviewAttachment previewData)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SendMessageV2 data = new SendMessageV2();
 | 
				
			||||||
 | 
					            data.Recipients = new List<string>();
 | 
				
			||||||
 | 
					            data.Recipients.Add(AppConfiguration.Instance.SignalGroupId);
 | 
				
			||||||
 | 
					            data.Message = (AppConfiguration.Instance.UseBotTag ? "**[Proggy]**\n" : "") + message;
 | 
				
			||||||
 | 
					            data.Text_mode = SendMessageV2Text_mode.Styled;
 | 
				
			||||||
 | 
					            data.Number = AppConfiguration.Instance.HostPhoneNumber;
 | 
				
			||||||
 | 
					            data.Link_preview = new LinkPreviewType();
 | 
				
			||||||
 | 
					            data.Link_preview.Url = previewData.Url;
 | 
				
			||||||
 | 
					            data.Link_preview.Title = previewData.Title;
 | 
				
			||||||
 | 
					            data.Link_preview.Description = previewData.Description;
 | 
				
			||||||
 | 
					            data.Link_preview.Base64_thumbnail = previewData.Base64Image;
 | 
				
			||||||
 | 
					            var response = await apiClient.Send2Async(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.LogError("Exception (SendMessageToGroupAsync): " + ex.Message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task SendMessageToUserAsync(string message, string userId)
 | 
					    public async Task SendMessageToUserAsync(string message, string userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
@@ -80,6 +118,29 @@ public class SignalIntegration
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task SendMessageToUserAsync(string message, LinkPreviewAttachment previewData, string userId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            SendMessageV2 data = new SendMessageV2();
 | 
				
			||||||
 | 
					            data.Recipients = new List<string>();
 | 
				
			||||||
 | 
					            data.Recipients.Add(userId);
 | 
				
			||||||
 | 
					            data.Message = (AppConfiguration.Instance.UseBotTag ? "**[Proggy]**\n" : "") + message;
 | 
				
			||||||
 | 
					            data.Text_mode = SendMessageV2Text_mode.Styled;
 | 
				
			||||||
 | 
					            data.Number = AppConfiguration.Instance.HostPhoneNumber;
 | 
				
			||||||
 | 
					            data.Link_preview = new LinkPreviewType();
 | 
				
			||||||
 | 
					            data.Link_preview.Url = previewData.Url;
 | 
				
			||||||
 | 
					            data.Link_preview.Title = previewData.Title;
 | 
				
			||||||
 | 
					            data.Link_preview.Description = previewData.Description;
 | 
				
			||||||
 | 
					            data.Link_preview.Base64_thumbnail = previewData.Base64Image;
 | 
				
			||||||
 | 
					            var response = await apiClient.Send2Async(data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (Exception ex)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            logger.LogError("Exception (SendMessageToUserAsync): " + ex.Message);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async Task IntroduceUserAsync(User user)
 | 
					    public async Task IntroduceUserAsync(User user)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (user == null || user.SignalMemberId == null)
 | 
					        if (user == null || user.SignalMemberId == null)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,29 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div class="text-center">
 | 
					<div class="text-center">
 | 
				
			||||||
    <h1 class="display-4">Welcome</h1>
 | 
					    <h1 class="display-4">Submission History</h1>
 | 
				
			||||||
    <p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
 | 
					    <table>
 | 
				
			||||||
 | 
					        <tr>
 | 
				
			||||||
 | 
					            <th>Date</th>
 | 
				
			||||||
 | 
					            <th>Song</th>
 | 
				
			||||||
 | 
					            <th>Submitter</th>
 | 
				
			||||||
 | 
					            <th>Details</th>
 | 
				
			||||||
 | 
					        </tr>
 | 
				
			||||||
 | 
					        @foreach(var songSuggestion in Model.SongSuggestions)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            @if(songSuggestion != null && songSuggestion.Song != null && songSuggestion.User != null && songSuggestion.UserHasSubmitted)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var displayName = string.IsNullOrEmpty(songSuggestion?.User?.NickName) 
 | 
				
			||||||
 | 
					                    ? songSuggestion?.User.Name 
 | 
				
			||||||
 | 
					                    : songSuggestion.User.NickName;
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td>@songSuggestion?.Date.ToString("dd. MM. yyyy")</td>
 | 
				
			||||||
 | 
					                    <td><a href="@songSuggestion?.Song.Url" target="_blank">@string.Format("{0} - {1}", songSuggestion?.Song.Name, songSuggestion?.Song.Artist)</a></td>
 | 
				
			||||||
 | 
					                    <td>@displayName</td>
 | 
				
			||||||
 | 
					                    <td><a href="/SongSubmission/@songSuggestion?.Id">View</a></td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
					using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace sotd.Pages;
 | 
					namespace sotd.Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -12,8 +14,16 @@ public class IndexModel : PageModel
 | 
				
			|||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public void OnGet()
 | 
					    [BindProperty]
 | 
				
			||||||
    {
 | 
					    public List<SongSuggestion> SongSuggestions { get; set; } = new List<SongSuggestion>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task OnGet()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        using var dci = DataContext.Instance;
 | 
				
			||||||
 | 
					        this.SongSuggestions = dci.SongSuggestions.OrderByDescending(s => s.Date)
 | 
				
			||||||
 | 
					            .Take(50)
 | 
				
			||||||
 | 
					            .Include(s => s.Song)
 | 
				
			||||||
 | 
					            .Include(s => s.User)
 | 
				
			||||||
 | 
					            .ToList();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								song_of_the_day/Pages/Shared/SongPartialModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								song_of_the_day/Pages/Shared/SongPartialModel.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					public class SongPartialModel
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public Song InnerSong { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string? Artist => InnerSong.Artist;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string? Name => InnerSong.Name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string? SpotifyId => InnerSong.SpotifyId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string? Url => InnerSong.Url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SongProvider? Provider => InnerSong.Provider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public bool IsPageReadonly { get; set; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -48,7 +48,7 @@
 | 
				
			|||||||
                            this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
					                            this.User.Identity.IsAuthenticated && DoesUserHaveClaimedPhoneNumber())
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            <li class="nav-item">
 | 
					                            <li class="nav-item">
 | 
				
			||||||
                                <a class="nav-link text-dark" asp-area="" asp-page="/SubmitSongs">Submit Songs</a>
 | 
					                                <a class="nav-link text-dark" href="/SongSubmission/">Song Submissions</a>
 | 
				
			||||||
                            </li>
 | 
					                            </li>
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    </ul>
 | 
					                    </ul>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,4 +45,4 @@ button.accept-policy {
 | 
				
			|||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  white-space: nowrap;
 | 
					  white-space: nowrap;
 | 
				
			||||||
  line-height: 60px;
 | 
					  line-height: 60px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
@model Song
 | 
					@model SongPartialModel
 | 
				
			||||||
<label asp-for="Name">Name:</label>
 | 
					<label asp-for="Name">Name:</label>
 | 
				
			||||||
<input asp-for="Name" />
 | 
					<input asp-for="Name" oninput="UpdateSongSuggestions()" disabled="@Model.IsPageReadonly" />
 | 
				
			||||||
<label asp-for="Artist">Artist:</label>
 | 
					<label asp-for="Artist">Artist:</label>
 | 
				
			||||||
<input asp-for="Artist" />
 | 
					<input asp-for="Artist" oninput="UpdateSongSuggestions()" disabled="@Model.IsPageReadonly" />
 | 
				
			||||||
<label asp-for="SpotifyId">Spotify ID:</label>
 | 
					<label asp-for="SpotifyId">Spotify ID:</label>
 | 
				
			||||||
<input asp-for="SpotifyId" />
 | 
					<input asp-for="SpotifyId" readonly="@Model.Provider.Equals(SongProvider.Spotify)" disabled="@Model.IsPageReadonly" />
 | 
				
			||||||
 | 
					<input asp-for="Provider" hidden />
 | 
				
			||||||
@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					@model List<SpotifyAPI.Web.FullTrack>
 | 
				
			||||||
 | 
					<div class="spotifySongSelector">
 | 
				
			||||||
 | 
					    @foreach(var track in Model)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        <div class="songSelectorButton" onclick="clickHandler(this)">
 | 
				
			||||||
 | 
					            <div class="songName">@track.Name</div>
 | 
				
			||||||
 | 
					            <div class="artist">@track.Artists[0].Name</div>
 | 
				
			||||||
 | 
					            <div class="album">@track.Album.Name</div>
 | 
				
			||||||
 | 
					            <div class="id" hidden>@track.Id</div>
 | 
				
			||||||
 | 
					            <div class="albumCover">
 | 
				
			||||||
 | 
					                <img src="@track.Album.Images[0].Url" width="50px" height="50px"/>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    function clickHandler(t) {
 | 
				
			||||||
 | 
					        // remove previous selection
 | 
				
			||||||
 | 
					        $('.songSelectorButton').removeClass("selected");
 | 
				
			||||||
 | 
					        t.classList.add("selected");
 | 
				
			||||||
 | 
					        var idElement = t.getElementsByClassName("id")[0]
 | 
				
			||||||
 | 
					        console.log(idElement);
 | 
				
			||||||
 | 
					        SetSpotifyId(idElement.textContent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function initializeSelection() {
 | 
				
			||||||
 | 
					        var currentSpotifyId = GetSpotifyId();
 | 
				
			||||||
 | 
					        $('.songSelectorButton').each(function(i, e) {
 | 
				
			||||||
 | 
					            if(e.getElementsByClassName("id")[0].textContent == currentSpotifyId)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                e.classList.add("selected");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initializeSelection();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					.songSelectorButton {
 | 
				
			||||||
 | 
					  background-color: #e3e6e7;
 | 
				
			||||||
 | 
					  padding: 5px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.songSelectorButton:nth-child(2n) {
 | 
				
			||||||
 | 
					  background-color: #cacbce;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.songSelectorButton.selected {
 | 
				
			||||||
 | 
					  background-color: #a0c3e5;
 | 
				
			||||||
 | 
					  border-radius: 2px;
 | 
				
			||||||
 | 
					  border-style: solid;
 | 
				
			||||||
 | 
					  border-width: 2px;
 | 
				
			||||||
 | 
					  border-color: #285786;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										158
									
								
								song_of_the_day/Pages/SongSubmission.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								song_of_the_day/Pages/SongSubmission.cshtml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					@page "{submissionIndex:int?}"
 | 
				
			||||||
 | 
					@model SongSubmissionModel
 | 
				
			||||||
 | 
					@{
 | 
				
			||||||
 | 
					    ViewData["Title"] = this.Model.SubmissionIndex == null ? "Song Submissions" : "New Song Submission";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@if(Model.SubmissionIndex == null)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    <div class="text-left">
 | 
				
			||||||
 | 
					        <h1>Your Submission History:</h1>
 | 
				
			||||||
 | 
					        <table id="submissionTable">
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					                <th>Date</th>
 | 
				
			||||||
 | 
					                <th>Suggestion</th>
 | 
				
			||||||
 | 
					                <th>Song</th>
 | 
				
			||||||
 | 
					                <th></th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            @foreach(var submission in Model.UserSongSubmissions)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                    <td class="submissionDate">
 | 
				
			||||||
 | 
					                        @submission.Date.ToString("dd. MM. yyyy")
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td class="submissionSuggestion">
 | 
				
			||||||
 | 
					                        @if(submission.Song == null || submission.HasUsedSuggestion)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            @submission.SuggestionHelper.Title
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td class="submissionSong">
 | 
				
			||||||
 | 
					                        @if(submission.Song != null)
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            @string.Format("{0} - {1}", submission.Song.Name, submission.Song.Artist);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        else
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            <div style="font-style: italic;">No submission yet!</div>
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                    <td class="=viewLink">
 | 
				
			||||||
 | 
					                        @{
 | 
				
			||||||
 | 
					                            var buttonClass = submission.Song == null ? "submitButton" : "viewButton";
 | 
				
			||||||
 | 
					                            var buttonText = submission.Song == null ? "Submit Song" : "View";
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        <button class=@buttonClass onclick="redirectTo(@submission.Id)">@buttonText</button>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    <div class="text-left">
 | 
				
			||||||
 | 
					        <div class="suggestionHelper">
 | 
				
			||||||
 | 
					            <div class="title">Today's suggestionHelper is: <b>@Model.SuggestionHelper.Title</b></div>
 | 
				
			||||||
 | 
					            <div class="description" style="font-style: italic;">@Model.SuggestionHelper.Description</div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <form method="post">
 | 
				
			||||||
 | 
					            <label asp-for="SubmitUrl" >Song Url:</label>
 | 
				
			||||||
 | 
					            <input asp-for="SubmitUrl" oninput="Update(this)"  disabled="@Model.IsPageReadonly" />
 | 
				
			||||||
 | 
					            <label asp-for="HasUsedSuggestion">Submission has used suggestion.</label>
 | 
				
			||||||
 | 
					            <input asp-for="HasUsedSuggestion" checked  disabled="@Model.IsPageReadonly" />
 | 
				
			||||||
 | 
					            @{
 | 
				
			||||||
 | 
					                var songPartialModel = new SongPartialModel() {
 | 
				
			||||||
 | 
					                    InnerSong = Model.SongData,
 | 
				
			||||||
 | 
					                    IsPageReadonly = Model.IsPageReadonly
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            <div id="songdata">
 | 
				
			||||||
 | 
					                <partial name="_SongPartial" model="@songPartialModel" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            @if(!Model.IsPageReadonly)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                <div id="suggestionPicker">
 | 
				
			||||||
 | 
					                    <partial name="_SpotifySongSuggestionsPartial" model="@Model.SpotifySuggestions" />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            <input type="submit" id="songsubmit" title="Submit" value="Submit" disabled="@Model.CanSubmit" />
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@if(Model.SubmissionIndex == null)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // inject relevant scripts
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        function redirectTo(id) {
 | 
				
			||||||
 | 
					            window.location.href = '/SongSubmission/' + id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					else
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        var skipUpdate = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function ReevaluateSubmit(callback) {
 | 
				
			||||||
 | 
					            var canSubmit = $('#songdata #Name').val().length > 0 && $('#songdata #Artist').val().length > 0;
 | 
				
			||||||
 | 
					            if(canSubmit)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                $('#songsubmit').removeAttr("disabled");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                $('#songsubmit').attr("disabled", "disabled");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(callback)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                callback();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function Update(t) {
 | 
				
			||||||
 | 
					            var songDataUrl = '?handler=Update&&SubmitUrl=' + $(t).val();
 | 
				
			||||||
 | 
					            $('#songdata').load(songDataUrl, null, function() {
 | 
				
			||||||
 | 
					                ReevaluateSubmit(function() {
 | 
				
			||||||
 | 
					                    UpdateSongSuggestions(t);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function UpdateSongSuggestions() {
 | 
				
			||||||
 | 
					            if(skipUpdate)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }  
 | 
				
			||||||
 | 
					            skipUpdate = true;
 | 
				
			||||||
 | 
					            var trackName = $('#songdata #Name').val();
 | 
				
			||||||
 | 
					            var artistName = $('#songdata #Artist').val();
 | 
				
			||||||
 | 
					            var submitUrl = $('#SubmitUrl').val();
 | 
				
			||||||
 | 
					            var provider = $('#songdata #Provider').val();
 | 
				
			||||||
 | 
					            var spotifyId = $('#songdata #SpotifyId').val();
 | 
				
			||||||
 | 
					            var suggestionDataUrl = '?handler=SpotifySuggestions&song=' + encodeURIComponent(trackName) 
 | 
				
			||||||
 | 
					                + '&artist=' + encodeURIComponent(artistName)
 | 
				
			||||||
 | 
					                + '&submitUrl=' + encodeURI(submitUrl)
 | 
				
			||||||
 | 
					                + '&provider=' + encodeURI(provider)
 | 
				
			||||||
 | 
					                + '&spotifyId=' + encodeURI(spotifyId);
 | 
				
			||||||
 | 
					            $('#suggestionPicker').load(suggestionDataUrl, null, function() {
 | 
				
			||||||
 | 
					                ReevaluateSubmit();
 | 
				
			||||||
 | 
					                skipUpdate = false;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function SetSpotifyId(id) {
 | 
				
			||||||
 | 
					            $('#SpotifyId').val(id);
 | 
				
			||||||
 | 
					            ReevaluateSubmit();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        function GetSpotifyId() {
 | 
				
			||||||
 | 
					            return $('#SpotifyId').val();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.getElementById('songdata').oninput = ReevaluateSubmit;
 | 
				
			||||||
 | 
					        ReevaluateSubmit();
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										237
									
								
								song_of_the_day/Pages/SongSubmission.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								song_of_the_day/Pages/SongSubmission.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
				
			|||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Components;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Http.HttpResults;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
 | 
					using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
				
			||||||
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
 | 
					using Microsoft.VisualBasic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace sotd.Pages;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class SongSubmissionModel : PageModel
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private readonly ILogger<UserModel> _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private SongResolver songResolver;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string _submitUrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private SpotifyApiClient spotifyApiClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private SignalIntegration signalIntegration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SongSubmissionModel(ILogger<UserModel> logger, SongResolver songResolver, SpotifyApiClient spotifyApiClient, SignalIntegration signalIntegration)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        _logger = logger;
 | 
				
			||||||
 | 
					        this.spotifyApiClient = spotifyApiClient;
 | 
				
			||||||
 | 
					        this.songResolver = songResolver;
 | 
				
			||||||
 | 
					        this.signalIntegration = signalIntegration;
 | 
				
			||||||
 | 
					        _submitUrl = string.Empty;
 | 
				
			||||||
 | 
					        SongData = new Song();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [Parameter]
 | 
				
			||||||
 | 
					    public int? SubmissionIndex { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public bool IsValidUrl { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public bool IsPageReadonly { get; set; } = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public SuggestionHelper SuggestionHelper { get; set; } = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public List<SongSuggestion> UserSongSubmissions { get; set; } = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty(SupportsGet = true)]
 | 
				
			||||||
 | 
					    public string SubmitUrl
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return _submitUrl;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        set
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _submitUrl = value == null ? string.Empty : value.ToString();
 | 
				
			||||||
 | 
					            Uri? newValue = default;
 | 
				
			||||||
 | 
					            try
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                newValue = new Uri(_submitUrl);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (UriFormatException)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IsValidUrl = false;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            IsValidUrl = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.songResolver.CanValidate(newValue))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public List<SpotifyAPI.Web.FullTrack> SpotifySuggestions
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var suggestionList = new List<SpotifyAPI.Web.FullTrack>();
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(this.SongData.Name) && !string.IsNullOrEmpty(this.SongData.Artist))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                suggestionList.AddRange(spotifyApiClient.GetTrackCandidatesAsync(this.SongData.Name, this.SongData.Artist).Result);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return suggestionList;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public IActionResult OnGetSpotifySuggestions(string song, string artist, string submitUrl, SongProvider provider, string spotifyId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.SongData.Name = song;
 | 
				
			||||||
 | 
					        this.SongData.Artist = artist;
 | 
				
			||||||
 | 
					        this.SongData.Url = submitUrl;
 | 
				
			||||||
 | 
					        this.SongData.Provider = provider;
 | 
				
			||||||
 | 
					        this.SongData.SpotifyId = spotifyId;
 | 
				
			||||||
 | 
					        return Partial("_SpotifySongSuggestionsPartial", this.SpotifySuggestions);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty]
 | 
				
			||||||
 | 
					    public string CanSubmit
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        get
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var disableCondition = !(string.IsNullOrEmpty(SongData?.Artist) && string.IsNullOrEmpty(SongData?.Name));
 | 
				
			||||||
 | 
					            return disableCondition ? "" : "disabled";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty(SupportsGet = true)]
 | 
				
			||||||
 | 
					    public Song SongData { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [BindProperty(SupportsGet = true)]
 | 
				
			||||||
 | 
					    public bool HasUsedSuggestion { get; set; } = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private async Task UpdateGroup(SongSuggestion suggestion)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var isItToday = suggestion.Date.Date == DateTime.Today;
 | 
				
			||||||
 | 
					        var dateString = isItToday ? "submission for today" : "submission from " + suggestion.Date.ToString("dd. MM. yyyy");
 | 
				
			||||||
 | 
					        var displayName = string.IsNullOrEmpty(suggestion.User.NickName) ? suggestion.User.Name : suggestion.User.NickName;
 | 
				
			||||||
 | 
					        var imageBuilder = await songResolver.GetPreviewImageAsync(suggestion.Song.SpotifyId);
 | 
				
			||||||
 | 
					        var previewData = new LinkPreviewAttachment()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Title = suggestion.Song.Name,
 | 
				
			||||||
 | 
					            Description = suggestion.Song.Artist,
 | 
				
			||||||
 | 
					            Url = suggestion.Song.Url,
 | 
				
			||||||
 | 
					            Base64Image = imageBuilder.ToString(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        await signalIntegration.SendMessageToGroupAsync($"**{displayName}**'s " + dateString + $" is: \n\n {suggestion.Song.Url}", previewData);
 | 
				
			||||||
 | 
					        if (suggestion.HasUsedSuggestion)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            await signalIntegration.SendMessageToGroupAsync($"The suggestion used for this pick was: \n\n **{suggestion.SuggestionHelper.Title}**'s \n\n {suggestion.SuggestionHelper.Description}");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<IActionResult> OnPost(int? submissionIndex)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (submissionIndex == null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Attempt to submit song without submissionId");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Todo implement save submission
 | 
				
			||||||
 | 
					        using var dci = DataContext.Instance;
 | 
				
			||||||
 | 
					        var suggestion = dci.SongSuggestions
 | 
				
			||||||
 | 
					                .Include(s => s.User)
 | 
				
			||||||
 | 
					                .Include(s => s.SuggestionHelper)
 | 
				
			||||||
 | 
					                .Where(s => s.Id == submissionIndex)
 | 
				
			||||||
 | 
					                .FirstOrDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (suggestion == null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            throw new Exception("Attempt to submit song with invalid submissionId");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.SongData.Artist = Request.Form["Artist"];
 | 
				
			||||||
 | 
					        this.SongData.Url = Request.Form["SubmitUrl"];
 | 
				
			||||||
 | 
					        this.SongData.Name = Request.Form["Name"];
 | 
				
			||||||
 | 
					        this.SongData.SpotifyId = Request.Form["SpotifyId"];
 | 
				
			||||||
 | 
					        this.SongData.Provider = Enum.Parse<SongProvider>(Request.Form["Provider"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Let's check if we already have this song in the DB
 | 
				
			||||||
 | 
					        var songToUse = dci.Songs.Where(s => s.SpotifyId == this.SongData.SpotifyId).FirstOrDefault();
 | 
				
			||||||
 | 
					        if (songToUse == null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Song was not in DB yet, creating a new one
 | 
				
			||||||
 | 
					            var newSong = new Song()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                SpotifyId = this.SongData.SpotifyId,
 | 
				
			||||||
 | 
					                Artist = this.SongData.Artist,
 | 
				
			||||||
 | 
					                Name = this.SongData.Name,
 | 
				
			||||||
 | 
					                Url = this.SongData.Url,
 | 
				
			||||||
 | 
					                Provider = this.SongData.Provider,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            var dbRecord = dci.Songs.Add(newSong);
 | 
				
			||||||
 | 
					            var newId = await dci.SaveChangesAsync();
 | 
				
			||||||
 | 
					            songToUse = dbRecord.Entity;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        suggestion.Song = songToUse;
 | 
				
			||||||
 | 
					        suggestion.UserHasSubmitted = true;
 | 
				
			||||||
 | 
					        suggestion.HasUsedSuggestion = this.HasUsedSuggestion;
 | 
				
			||||||
 | 
					        dci.Update(suggestion);
 | 
				
			||||||
 | 
					        await dci.SaveChangesAsync();
 | 
				
			||||||
 | 
					        // load overview page again after submitting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await UpdateGroup(suggestion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return RedirectToPage("SongSubmission");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void OnGet(int? submissionIndex)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.SubmissionIndex = submissionIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // we want to show overview, so we need to fetch user submission list
 | 
				
			||||||
 | 
					        if (submissionIndex == null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            using var dci = DataContext.Instance;
 | 
				
			||||||
 | 
					            var currentUserName = this.User.Identity.Name;
 | 
				
			||||||
 | 
					            this.UserSongSubmissions = dci.SongSuggestions
 | 
				
			||||||
 | 
					                            .Include(s => s.SuggestionHelper)
 | 
				
			||||||
 | 
					                            .Include(s => s.Song)
 | 
				
			||||||
 | 
					                            .Where(s => s.User.LdapUserName.Equals(currentUserName))
 | 
				
			||||||
 | 
					                            .OrderByDescending(s => s.Date)
 | 
				
			||||||
 | 
					                            .ToList();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            using var dci = DataContext.Instance;
 | 
				
			||||||
 | 
					            var songSuggestion = dci.SongSuggestions
 | 
				
			||||||
 | 
					                            .Include(s => s.SuggestionHelper)
 | 
				
			||||||
 | 
					                            .Include(s => s.Song)
 | 
				
			||||||
 | 
					                            .Where(s => s.Id.Equals(submissionIndex.Value))
 | 
				
			||||||
 | 
					                            .First();
 | 
				
			||||||
 | 
					            this.SuggestionHelper = songSuggestion.SuggestionHelper;
 | 
				
			||||||
 | 
					            this.SongData = songSuggestion.Song == null ? new Song() : songSuggestion.Song;
 | 
				
			||||||
 | 
					            this.SubmitUrl = this.SongData.Url;
 | 
				
			||||||
 | 
					            if (!string.IsNullOrEmpty(this.SongData.Name) && !string.IsNullOrEmpty(this.SongData.Artist))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                this.IsPageReadonly = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public IActionResult OnGetUpdate()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var songUrl = Request.Query["SubmitUrl"];
 | 
				
			||||||
 | 
					        this.SubmitUrl = songUrl.ToString();
 | 
				
			||||||
 | 
					        var songPartialModel = new SongPartialModel()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            InnerSong = SongData,
 | 
				
			||||||
 | 
					            IsPageReadonly = false
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return Partial("_SongPartial", songPartialModel); ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,25 +0,0 @@
 | 
				
			|||||||
@page
 | 
					 | 
				
			||||||
@model SubmitSongsModel
 | 
					 | 
				
			||||||
@{
 | 
					 | 
				
			||||||
    ViewData["Title"] = "Submit Songs";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="text-left">
 | 
					 | 
				
			||||||
    <form method="post">
 | 
					 | 
				
			||||||
        <label asp-for="SubmitUrl" >Song Url:</label>
 | 
					 | 
				
			||||||
        <input asp-for="SubmitUrl" oninput="Update(this)" />
 | 
					 | 
				
			||||||
    </form>
 | 
					 | 
				
			||||||
        <form method="post">
 | 
					 | 
				
			||||||
            <div id="songdata">
 | 
					 | 
				
			||||||
                <partial name="_SongPartial" model="@Model.SongData" />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <input type="submit" title="Submit" value="Submit" disabled="CanSubmit" />
 | 
					 | 
				
			||||||
        </form>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    function Update(t) {
 | 
					 | 
				
			||||||
        var url = '?handler=Update&&SubmitUrl=' + $(t).val();
 | 
					 | 
				
			||||||
        $('#songdata').load(url)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
@@ -1,81 +0,0 @@
 | 
				
			|||||||
using Microsoft.AspNetCore.Components;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					 | 
				
			||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
 | 
					 | 
				
			||||||
using Microsoft.VisualBasic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace sotd.Pages;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class SubmitSongsModel : PageModel
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private readonly ILogger<UserModel> _logger;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private SongResolver songResolver;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private string _submitUrl;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public SubmitSongsModel(ILogger<UserModel> logger, SongResolver songResolver)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        _logger = logger;
 | 
					 | 
				
			||||||
        this.songResolver = songResolver;
 | 
					 | 
				
			||||||
        _submitUrl = string.Empty;
 | 
					 | 
				
			||||||
        SongData = new Song();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [BindProperty]
 | 
					 | 
				
			||||||
    public bool IsValidUrl { get; set; } = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [BindProperty]
 | 
					 | 
				
			||||||
    public string SubmitUrl
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        get
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return _submitUrl;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        set
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _submitUrl = value.ToString();
 | 
					 | 
				
			||||||
            Uri? newValue = default;
 | 
					 | 
				
			||||||
            try
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                newValue = new Uri(_submitUrl);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (UriFormatException)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IsValidUrl = false;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            IsValidUrl = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (this.songResolver.CanValidate(newValue))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                this.SongData = this.songResolver.ResolveSongAsync(newValue).Result;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [BindProperty]
 | 
					 | 
				
			||||||
    public bool CanSubmit
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        get
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return !string.IsNullOrEmpty(SongData?.Artist) && !string.IsNullOrEmpty(SongData?.Name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [BindProperty]
 | 
					 | 
				
			||||||
    public Song SongData { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void OnPost()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Todo implement save submission
 | 
					 | 
				
			||||||
        var x = SongData.Name;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public IActionResult OnGetUpdate()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        var songUrl = Request.Query["SubmitUrl"];
 | 
					 | 
				
			||||||
        this.SubmitUrl = songUrl.ToString();
 | 
					 | 
				
			||||||
        return Partial("_SongPartial", SongData); ;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -20,6 +20,7 @@ builder.Services.AddSingleton<LdapAuthenticationService>();
 | 
				
			|||||||
builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
					builder.Services.AddSingleton<PhoneClaimCodeProviderService>();
 | 
				
			||||||
builder.Services.AddSingleton<SignalIntegration>();
 | 
					builder.Services.AddSingleton<SignalIntegration>();
 | 
				
			||||||
builder.Services.AddSingleton<LdapIntegration>();
 | 
					builder.Services.AddSingleton<LdapIntegration>();
 | 
				
			||||||
 | 
					builder.Services.AddSingleton<SpotifyApiClient>();
 | 
				
			||||||
builder.Services.AddSingleton<SongResolver>();
 | 
					builder.Services.AddSingleton<SongResolver>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var app = builder.Build();
 | 
					var app = builder.Build();
 | 
				
			||||||
@@ -143,15 +144,15 @@ pickOfTheDayTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    if (luckyUser.SignalMemberId is string signalId)
 | 
					    if (luckyUser.SignalMemberId is string signalId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        await dci.SongSuggestions.AddAsync(newSongSuggestion);
 | 
					        var result = await dci.SongSuggestions.AddAsync(newSongSuggestion);
 | 
				
			||||||
 | 
					        newSongSuggestion = result.Entity;
 | 
				
			||||||
        luckyUser.WasChosenForSuggestionThisRound = true;
 | 
					        luckyUser.WasChosenForSuggestionThisRound = true;
 | 
				
			||||||
        await dci.SaveChangesAsync();
 | 
					        await dci.SaveChangesAsync();
 | 
				
			||||||
        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
					        var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
				
			||||||
        await signalIntegration.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
					        await signalIntegration.SendMessageToGroupAsync($"Today's chosen person to share a song is: **{userName}**");
 | 
				
			||||||
        await signalIntegration.SendMessageToGroupAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*");
 | 
					 | 
				
			||||||
        await signalIntegration.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
 | 
					        await signalIntegration.SendMessageToUserAsync($"Congratulations, you have been chosen to share a song today!", signalId);
 | 
				
			||||||
        await signalIntegration.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId);
 | 
					        await signalIntegration.SendMessageToUserAsync($"Today's (optional) suggestion helper to help you pick a song is:\n\n**{suggestion.Title}**\n\n*{suggestion.Description}*", signalId);
 | 
				
			||||||
        await signalIntegration.SendMessageToUserAsync($"For now please just share your suggestion with the group - in the future I might ask you to share directly with me or via the website to help me keep track of past suggestions!", luckyUser.SignalMemberId);
 | 
					        await signalIntegration.SendMessageToUserAsync($"Please navigate to https://sord.disi.dev/SongSubmission/{newSongSuggestion.Id} to submit your choice!", luckyUser.SignalMemberId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    await dci.DisposeAsync();
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -197,6 +198,16 @@ ldapAssociationTimer.OnOccurence += async (s, ea) =>
 | 
				
			|||||||
    await dci.DisposeAsync();
 | 
					    await dci.DisposeAsync();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger.LogTrace("Setting up MessageSync timer");
 | 
				
			||||||
 | 
					var messageSyncTimer = new CronTimer("*/10 * * * *", "Europe/Vienna", includingSeconds: false);
 | 
				
			||||||
 | 
					messageSyncTimer.OnOccurence += async (s, ea) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    var signalIntegration = app.Services.GetService<SignalIntegration>();
 | 
				
			||||||
 | 
					    await signalIntegration.GetMessagesAsync();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					// disabled for now, is still buggy
 | 
				
			||||||
 | 
					// messageSyncTimer.Start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// only start interaction timers in production builds
 | 
					// only start interaction timers in production builds
 | 
				
			||||||
// for local/development testing we want those disabled
 | 
					// for local/development testing we want those disabled
 | 
				
			||||||
if (!app.Environment.IsDevelopment())
 | 
					if (!app.Environment.IsDevelopment())
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								song_of_the_day/SongValidators/Base64UrlImageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								song_of_the_day/SongValidators/Base64UrlImageBuilder.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					public class Base64UrlImageBuilder
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public string ContentType { set; get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public string Url
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        set
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var httpClient = new HttpClient();
 | 
				
			||||||
 | 
					            var response = (httpClient.GetAsync(new Uri($"{value}"))).Result;
 | 
				
			||||||
 | 
					            var bytes = (response.Content.ReadAsByteArrayAsync()).Result;
 | 
				
			||||||
 | 
					            FileContents = Convert.ToBase64String(bytes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private string FileContents { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public override string ToString()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //return $"data:{ContentType};base64,{FileContents}";
 | 
				
			||||||
 | 
					        return $"{FileContents}";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,4 +5,6 @@ public interface ISongValidator
 | 
				
			|||||||
    bool CanValidateUri(Uri songUri);
 | 
					    bool CanValidateUri(Uri songUri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Task<bool> CanExtractSongMetadataAsync(Uri songUri);
 | 
					    Task<bool> CanExtractSongMetadataAsync(Uri songUri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    SongProvider GetSongProvider();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using Microsoft.Extensions.Logging;
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					using SpotifyAPI.Web;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class SongResolver
 | 
					public class SongResolver
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -6,16 +7,20 @@ public class SongResolver
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private readonly ILogger<SongResolver> logger;
 | 
					    private readonly ILogger<SongResolver> logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public SongResolver(ILogger<SongResolver> logger)
 | 
					    private SpotifyApiClient spotifyApiClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SongResolver(ILogger<SongResolver> logger, ILoggerFactory loggerFactory, SpotifyApiClient spotifyApiClient)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        this.logger = logger;
 | 
					        this.logger = logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._songValidators = new List<ISongValidator>();
 | 
					        this._songValidators = new List<ISongValidator>();
 | 
				
			||||||
 | 
					        this.spotifyApiClient = spotifyApiClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
 | 
					        foreach (Type mytype in System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
 | 
				
			||||||
                 .Where(mytype => mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.EndsWith("Base"))))
 | 
					                 .Where(mytype => { return (mytype.GetInterfaces().Contains(typeof(ISongValidator)) && !(mytype.Name.Split("`")[0].EndsWith("Base"))); }))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (Activator.CreateInstance(mytype) is ISongValidator validator)
 | 
					            var typedLogger = loggerFactory.CreateLogger(mytype);
 | 
				
			||||||
 | 
					            if (Activator.CreateInstance(mytype, typedLogger, spotifyApiClient) is ISongValidator validator)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
 | 
					                logger.LogDebug("Registering song validator: {ValidatorType}", mytype.Name);
 | 
				
			||||||
                this._songValidators = this._songValidators.Append(validator);
 | 
					                this._songValidators = this._songValidators.Append(validator);
 | 
				
			||||||
@@ -65,4 +70,15 @@ public class SongResolver
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async Task<Base64UrlImageBuilder> GetPreviewImageAsync(string spotifyId)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var track = await spotifyApiClient.GetTrackByIdAsync(spotifyId);
 | 
				
			||||||
 | 
					        var url = track.Album.Images.FirstOrDefault().Url;
 | 
				
			||||||
 | 
					        return new Base64UrlImageBuilder()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Url = url,
 | 
				
			||||||
 | 
					            ContentType = "image/jpeg"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public abstract class SongValidatorBase : ISongValidator
 | 
					public abstract class SongValidatorBase : ISongValidator
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -8,9 +9,22 @@ public abstract class SongValidatorBase : ISongValidator
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public abstract bool CanValidateUri(Uri songUri);
 | 
					    public abstract bool CanValidateUri(Uri songUri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected string LookupSpotifyId(string songName, string songArtist)
 | 
					    public abstract SongProvider GetSongProvider();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected SpotifyApiClient _spotifyApiClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ILogger _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public SongValidatorBase(ILogger logger, SpotifyApiClient spotifyApiClient)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // TODO: Implement Spotify ID lookup logic
 | 
					        _spotifyApiClient = spotifyApiClient;
 | 
				
			||||||
        return songName + " by " + songArtist;
 | 
					        _logger = logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected async Task<string> LookupSpotifyIdAsync(string songName, string songArtist)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        var candidates = await _spotifyApiClient.GetTrackCandidatesAsync(songName, songArtist);
 | 
				
			||||||
 | 
					        return candidates.Any() ? candidates[0].Id : "";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -7,24 +7,25 @@ public class SpotifyValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public override string UriValidatorRegex => @"^(https?://)?open.spotify.com/track/([a-zA-Z0-9_-]{22})(\?si=[a-zA-Z0-9_-]+)?$";
 | 
					    public override string UriValidatorRegex => @"^(https?://)?open.spotify.com/track/([a-zA-Z0-9_-]{22})(\?si=[a-zA-Z0-9_-]+)?$";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private SpotifyApiClient spotifyApiClient;
 | 
					    public SpotifyValidator(ILogger _logger, SpotifyApiClient spotifyApiClient) : base(_logger, spotifyApiClient)
 | 
				
			||||||
 | 
					    {}
 | 
				
			||||||
    public SpotifyValidator()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        spotifyApiClient = new SpotifyApiClient();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
					    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return Task.FromResult(this.CanValidateUri(songUri));
 | 
					        return Task.FromResult(this.CanValidateUri(songUri));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public override SongProvider GetSongProvider()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return SongProvider.Spotify;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
					    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
 | 
					        var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
 | 
				
			||||||
        var trackIdMatch = regexp.Match(songUri.ToString()).Groups[2].Value;
 | 
					        var trackIdMatch = regexp.Match(songUri.ToString()).Groups[2].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var track = await spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
 | 
					        var track = await _spotifyApiClient.GetTrackByIdAsync(trackIdMatch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var song = new Song
 | 
					        var song = new Song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,9 +4,17 @@ public abstract class UriBasedSongValidatorBase : SongValidatorBase
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public abstract string UriValidatorRegex { get; }
 | 
					    public abstract string UriValidatorRegex { get; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override bool CanValidateUri(Uri songUri)
 | 
					    public UriBasedSongValidatorBase(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
 | 
				
			||||||
 | 
					    {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Match GetUriMatch(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
 | 
					        var regexp = new Regex(UriValidatorRegex, RegexOptions.IgnoreCase);
 | 
				
			||||||
        return regexp.Match(songUri.ToString()).Success;
 | 
					        return regexp.Match(songUri.ToString());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public override bool CanValidateUri(Uri songUri)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return GetUriMatch(songUri).Success;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,22 @@
 | 
				
			|||||||
using AngleSharp;
 | 
					using AngleSharp;
 | 
				
			||||||
using AngleSharp.Dom;
 | 
					using AngleSharp.Dom;
 | 
				
			||||||
 | 
					using YouTubeMusicAPI.Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
					public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public override string UriValidatorRegex => @"^(https?://)?(music\.youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
 | 
					    public override string UriValidatorRegex => @"^(https?://)?(music\.youtube\.com/playlist\?list=)([a-zA-Z0-9_-]+)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private YouTubeMusicClient youtubeClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public YoutubeMusicValidator(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        youtubeClient = new YouTubeMusicClient(logger, "AT", null, null, null);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public override SongProvider GetSongProvider()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return SongProvider.YoutubeMusic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
					    public override Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -12,35 +25,23 @@ public class YoutubeMusicValidator : UriBasedSongValidatorBase
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
					    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var title = string.Empty;
 | 
					        var match = this.GetUriMatch(songUri);
 | 
				
			||||||
        var artist = string.Empty;
 | 
					        var playlistId = match.Groups[3].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using (HttpClient httpClient = new HttpClient())
 | 
					        var playlistResult = await youtubeClient.GetCommunityPlaylistInfoAsync(playlistId);
 | 
				
			||||||
        {
 | 
					        var songData = playlistResult.Songs[0];
 | 
				
			||||||
            var response = await httpClient.GetAsync(songUri);
 | 
					
 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					        var title = songData.Name;
 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					        var artist = songData.Artists[0].Name;
 | 
				
			||||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("song-title")[0].innerHTML
 | 
					 | 
				
			||||||
                title = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".song-title")?.InnerHtml;
 | 
					 | 
				
			||||||
                // document.getElementsByTagName("ytmusic-player-queue-item")[0].getElementsByClassName("byline")[0].innerHTML
 | 
					 | 
				
			||||||
                artist = document.QuerySelector(".ytmusic-player-queue-item")?.QuerySelector(".byline")?.InnerHtml;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
        var song = new Song
 | 
					        var song = new Song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Name = title,
 | 
					            Name = title,
 | 
				
			||||||
            Artist = artist,
 | 
					            Artist = artist,
 | 
				
			||||||
            Url = songUri.ToString(),
 | 
					            Url = songUri.ToString(),
 | 
				
			||||||
            Provider = SongProvider.YouTube,
 | 
					            Provider = SongProvider.YoutubeMusic,
 | 
				
			||||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
					            SpotifyId = await this.LookupSpotifyIdAsync(title, artist)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return song;
 | 
					        return song;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,60 +1,47 @@
 | 
				
			|||||||
using AngleSharp;
 | 
					using AngleSharp;
 | 
				
			||||||
using AngleSharp.Dom;
 | 
					using AngleSharp.Dom;
 | 
				
			||||||
using AngleSharp.Html.Dom;
 | 
					using AngleSharp.Html.Dom;
 | 
				
			||||||
 | 
					using YouTubeMusicAPI.Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class YoutubeValidator : UriBasedSongValidatorBase
 | 
					public class YoutubeValidator : UriBasedSongValidatorBase
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    private YouTubeMusicClient youtubeClient;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public YoutubeValidator(ILogger logger, SpotifyApiClient spotifyApiClient) : base(logger, spotifyApiClient)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        youtubeClient = new("AT");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override string UriValidatorRegex => @"^(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
 | 
					    public override string UriValidatorRegex => @"^(https?://)?(www\.)?(youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
					    public override async Task<bool> CanExtractSongMetadataAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        using (HttpClient httpClient = new HttpClient())
 | 
					        return this.CanValidateUri(songUri);
 | 
				
			||||||
        {
 | 
					    }
 | 
				
			||||||
            var response = await httpClient.GetAsync(songUri);
 | 
					 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					 | 
				
			||||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
 | 
					 | 
				
			||||||
                var documentContents = (document.ChildNodes[1] as HtmlElement).InnerHtml;
 | 
					 | 
				
			||||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
 | 
					 | 
				
			||||||
                var titleElement = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0];
 | 
					 | 
				
			||||||
                var artistParentElement = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return titleElement != null && artistParentElement != null && artistParentElement.Children.Length > 0;
 | 
					    public override SongProvider GetSongProvider()
 | 
				
			||||||
            }
 | 
					    {
 | 
				
			||||||
        }
 | 
					        return SongProvider.YouTube;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
					    public override async Task<Song> ValidateAsync(Uri songUri)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var title = string.Empty;
 | 
					        var match = this.GetUriMatch(songUri);
 | 
				
			||||||
        var artist = string.Empty;
 | 
					        var songId = match.Groups[4].Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        using (HttpClient httpClient = new HttpClient())
 | 
					        var songData = await youtubeClient.GetSongVideoInfoAsync(songId);
 | 
				
			||||||
        {
 | 
					
 | 
				
			||||||
            var response = await httpClient.GetAsync(songUri);
 | 
					        var title = songData.Name;
 | 
				
			||||||
            var config = Configuration.Default.WithDefaultLoader();
 | 
					        var artist = songData.Artists[0].Name;
 | 
				
			||||||
            var context = BrowsingContext.New(config);
 | 
					 | 
				
			||||||
            using (var document = await context.OpenAsync(async req => req.Content(await response.Content.ReadAsStringAsync())))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                title = document.QuerySelectorAll(".yt-video-attribute-view-model__title")[0]?.InnerHtml;
 | 
					 | 
				
			||||||
                artist = document.QuerySelectorAll(".yt-video-attribute-view-model__secondary-subtitle")[0]?.Children[0]?.InnerHtml;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
#pragma warning disable CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
        var song = new Song
 | 
					        var song = new Song
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Name = title,
 | 
					            Name = title,
 | 
				
			||||||
            Artist = artist,
 | 
					            Artist = artist,
 | 
				
			||||||
            Url = songUri.ToString(),
 | 
					            Url = songUri.ToString(),
 | 
				
			||||||
            Provider = SongProvider.YouTube,
 | 
					            Provider = SongProvider.YouTube,
 | 
				
			||||||
            SpotifyId = this.LookupSpotifyId(title, artist)
 | 
					            SpotifyId = await this.LookupSpotifyIdAsync(title, artist)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
#pragma warning restore CS8604 // Possible null reference argument.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return song;
 | 
					        return song;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@
 | 
				
			|||||||
    <PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
 | 
					    <PackageReference Include="NSwag.ApiDescription.Client" Version="13.0.5" />
 | 
				
			||||||
    <PackageReference Include="Scalar.AspNetCore" Version="2.1.*" />
 | 
					    <PackageReference Include="Scalar.AspNetCore" Version="2.1.*" />
 | 
				
			||||||
    <PackageReference Include="SpotifyAPI.Web" Version="7.2.1" />
 | 
					    <PackageReference Include="SpotifyAPI.Web" Version="7.2.1" />
 | 
				
			||||||
 | 
					    <PackageReference Include="YouTubeMusicAPI" Version="2.2.8" />
 | 
				
			||||||
    <PackageReference Include="System.DirectoryServices.Protocols" Version="*" />
 | 
					    <PackageReference Include="System.DirectoryServices.Protocols" Version="*" />
 | 
				
			||||||
  </ItemGroup>
 | 
					  </ItemGroup>
 | 
				
			||||||
  <ItemGroup>
 | 
					  <ItemGroup>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2423,6 +2423,9 @@
 | 
				
			|||||||
                "edit_timestamp": {
 | 
					                "edit_timestamp": {
 | 
				
			||||||
                    "type": "integer"
 | 
					                    "type": "integer"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "link_preview": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/data.LinkPreviewType"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "mentions": {
 | 
					                "mentions": {
 | 
				
			||||||
                    "type": "array",
 | 
					                    "type": "array",
 | 
				
			||||||
                    "items": {
 | 
					                    "items": {
 | 
				
			||||||
@@ -2817,6 +2820,23 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "data.LinkPreviewType": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "base64_thumbnail": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "description": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "title": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "url": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "data.MessageMention": {
 | 
					        "data.MessageMention": {
 | 
				
			||||||
            "type": "object",
 | 
					            "type": "object",
 | 
				
			||||||
            "properties": {
 | 
					            "properties": {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user