namespace ytLive.Models; public class YouTubeChannel { public string ChannelId { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string ProfileImageUrl { get; set; } = string.Empty; public string AccessToken { get; set; } = string.Empty; public string RefreshToken { get; set; } = string.Empty; public DateTime TokenExpiry { get; set; } } /// The channel's reusable live stream (TASK 5): created once, cached, /// rebound to every broadcast. The RTMP URL the encoder pushes to is the /// ingestion address + "/" + stream name. public record ReusableStream(string Id, string IngestionAddress, string StreamName) { public string RtmpUrl => $"{IngestionAddress}/{StreamName}"; } /// The six monetization events YouTube's live chat feed emits (the ONLY /// native event stream there is — free subscribers produce no event). Each kind /// drives its own celebration animation in the AlertBox. None = a normal /// chat row: it never enqueues an alert (subscriber mentions stay chat rows). public enum ChatEventKind { None, SuperChat, SuperSticker, NewMember, MemberGift, GiftReceived, MemberMilestone, } public class ChatMessage { public string Id { get; init; } = Guid.NewGuid().ToString(); public string AuthorName { get; set; } = string.Empty; public string AuthorChannelId { get; set; } = string.Empty; public string AuthorImageUrl { get; set; } = string.Empty; public string Message { get; set; } = string.Empty; public DateTime Timestamp { get; set; } public bool IsSuperChat { get; set; } public double SuperChatAmount { get; set; } public string? SuperChatCurrency { get; set; } /// YouTube's own pre-rendered amount ("$12.34") — preferred over /// recomposing amount+currency ourselves, which mangles non-dollar locales. public string? SuperChatDisplayString { get; set; } public bool IsMember { get; set; } public string? MembershipLevel { get; set; } public ChatEventKind Kind { get; set; } public string? SuperStickerDescription { get; set; } /// membershipGiftingEvent: how many memberships were bought. public int GiftNewMembersCount { get; set; } /// giftMembershipReceivedEvent: who gifted this one. public string? GifterName { get; set; } /// memberMilestoneChatEvent: streak months (0 = YouTube didn't /// expose a count — the animation falls back to the message text). public int MilestoneMonths { get; set; } /// TASK 41: the Test Stream dock injects synthetic member / SuperChat / /// subscriber events locally (YouTube's insert API only supports text), so the /// buffer can label them honestly — simulated events never reach YouTube. public bool IsSimulated { get; set; } }