From 3564cd0fda26f81eb24bea3287bca50cdba32e30 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz" Date: Sun, 5 Oct 2025 12:18:03 -0400 Subject: [PATCH 1/2] Add comprehensive design documentation for RedditAMA provider - RedditAMA-Provider-Design.md: Complete architectural specification - RedditAMA-Technical-Implementation.md: Detailed technical implementation guide - RedditAMA-UI-Specification.md: UI/UX design and component specifications - RedditAMA-Configuration-Guide.md: End-user configuration documentation These documents provide GitHub agents with complete specifications for: - Reddit API integration patterns following TwitchChat provider model - Configuration UI with URL parsing and validation - Database storage using existing TagzApp patterns - Error handling, testing, and deployment considerations - User documentation for AMA event setup and troubleshooting Ready for implementation with 5-week timeline estimate. --- doc/RedditAMA-Provider-Design.md | 556 +++++++++++++ doc/RedditAMA-Technical-Implementation.md | 925 +++++++++++++++++++++ doc/RedditAMA-UI-Specification.md | 631 ++++++++++++++ user-docs/RedditAMA-Configuration-Guide.md | 254 ++++++ 4 files changed, 2366 insertions(+) create mode 100644 doc/RedditAMA-Provider-Design.md create mode 100644 doc/RedditAMA-Technical-Implementation.md create mode 100644 doc/RedditAMA-UI-Specification.md create mode 100644 user-docs/RedditAMA-Configuration-Guide.md diff --git a/doc/RedditAMA-Provider-Design.md b/doc/RedditAMA-Provider-Design.md new file mode 100644 index 00000000..f7e37eec --- /dev/null +++ b/doc/RedditAMA-Provider-Design.md @@ -0,0 +1,556 @@ +# RedditAMA Provider Design Document + +## Overview + +The RedditAMA (Ask Me Anything) Provider is a specialized social media provider for TagzApp that monitors a specific Reddit post/thread for new comments in real-time. This provider is designed to support live events such as AMAs, Q&A sessions, product launches, and community discussions where Reddit serves as the primary interaction platform. + +Unlike traditional hashtag-based providers, RedditAMA focuses on tracking a single conversation thread, making it ideal for event-based content aggregation similar to the existing TwitchChat provider. + +## Architecture + +### Provider Type +**Event-Based Provider** - Monitors a specific Reddit post/thread rather than searching across multiple posts. + +### Implementation Pattern +Follows the existing `TwitchChatProvider` and `YouTubeChatProvider` pattern: +- Background polling service +- Concurrent queue for new comments +- Real-time content delivery to TagzApp waterfall + +### Core Components + +1. **RedditAMAProvider** - Main provider implementation +2. **RedditAMAConfiguration** - Configuration model and settings +3. **RedditAMAService** - Background polling service +4. **RedditCommentModel** - Reddit API response models +5. **Configuration UI** - Admin interface for setup +6. **User Documentation** - Setup and usage guides + +## Technical Specifications + +### API Integration + +#### Reddit API Endpoints +``` +Primary: https://www.reddit.com/r/{subreddit}/comments/{postId}.json?sort=new&limit=100 +Fallback: https://oauth.reddit.com/r/{subreddit}/comments/{postId} +``` + +#### Authentication +- **Public API**: No authentication required for public posts +- **Rate Limiting**: 60 requests per minute (Reddit's standard limit) +- **User-Agent**: Required by Reddit API, configurable + +#### Polling Strategy +- **Interval**: 30 seconds (configurable, minimum 15 seconds) +- **Comment Tracking**: Track last comment ID to avoid duplicates +- **Error Handling**: Exponential backoff on API failures +- **Queue Management**: Maximum 1000 comments in memory queue + +### Data Models + +#### RedditAMAConfiguration +```csharp +public class RedditAMAConfiguration : BaseProviderConfiguration +{ + public const string AppSettingsSection = "providers:redditama"; + + public string Description => "Monitor Reddit AMA/discussion threads for live comments"; + public string Name => "Reddit AMA"; + public bool Enabled { get; set; } = false; + + // Reddit-specific settings + public string Subreddit { get; set; } = string.Empty; // e.g., "IAmA", "dotnet" + public string PostId { get; set; } = string.Empty; // e.g., "16abc123" + public string PostTitle { get; set; } = string.Empty; // For display purposes + public string UserAgent { get; set; } = "TagzApp/1.0"; // Required by Reddit + + // Polling settings + public int RefreshIntervalSeconds { get; set; } = 30; // Min 15, Max 300 + public int MaxCommentsPerPoll { get; set; } = 100; // Max comments to fetch + public bool IncludeReplies { get; set; } = true; // Include comment replies + + // Filtering options + public int MinCommentScore { get; set; } = -10; // Hide heavily downvoted + public bool HideDeletedComments { get; set; } = true; // Filter [deleted] comments + public string[] BlockedAuthors { get; set; } = Array.Empty(); // Blocked usernames + + public string[] Keys => new[] + { + nameof(Subreddit), + nameof(PostId), + nameof(UserAgent), + nameof(RefreshIntervalSeconds), + nameof(MaxCommentsPerPoll), + nameof(IncludeReplies), + nameof(MinCommentScore), + nameof(HideDeletedComments) + }; + + // Validation + public bool IsValid => !string.IsNullOrEmpty(Subreddit) && + !string.IsNullOrEmpty(PostId) && + RefreshIntervalSeconds >= 15; +} +``` + +#### RedditComment Model +```csharp +public class RedditCommentData +{ + public string Id { get; set; } = string.Empty; + public string Author { get; set; } = string.Empty; + public string Body { get; set; } = string.Empty; + public long CreatedUtc { get; set; } + public string Permalink { get; set; } = string.Empty; + public int Score { get; set; } + public int Depth { get; set; } + public string ParentId { get; set; } = string.Empty; + public bool IsSubmitter { get; set; } // Is this the AMA host? + public string Distinguished { get; set; } = string.Empty; // "moderator", "admin", etc. +} + +public class RedditCommentWrapper +{ + public string Kind { get; set; } = string.Empty; + public RedditCommentData Data { get; set; } = new(); +} + +public class RedditThreadResponse +{ + public RedditCommentWrapper[] Children { get; set; } = Array.Empty(); +} +``` + +### Content Mapping + +#### Content Properties +```csharp +var content = new Content +{ + Provider = "REDDIT_AMA", + ProviderId = comment.Data.Id, + Type = ContentType.Message, + Timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.Data.CreatedUtc), + SourceUri = new Uri($"https://reddit.com{comment.Data.Permalink}"), + Author = new Creator + { + DisplayName = comment.Data.Author, + UserName = comment.Data.Author, + ProfileUri = new Uri($"https://reddit.com/u/{comment.Data.Author}"), + // Special handling for AMA host + ProfileImageUri = comment.Data.IsSubmitter ? + new Uri("https://tagzapp.io/img/reddit-op-badge.png") : null + }, + Text = comment.Data.Body, + HashtagSought = tag.Text.ToLowerInvariant(), + + // Reddit-specific metadata + ExtendedMetadata = new Dictionary + { + ["score"] = comment.Data.Score, + ["depth"] = comment.Data.Depth, + ["isOP"] = comment.Data.IsSubmitter, + ["distinguished"] = comment.Data.Distinguished + } +}; +``` + +## Database Storage + +### Configuration Storage +Following the existing pattern used by other providers: + +#### appsettings.json +```json +{ + "providers": { + "redditama": { + "enabled": false, + "subreddit": "", + "postId": "", + "postTitle": "", + "userAgent": "TagzApp/1.0", + "refreshIntervalSeconds": 30, + "maxCommentsPerPoll": 100, + "includeReplies": true, + "minCommentScore": -10, + "hideDeletedComments": true, + "blockedAuthors": [] + } + } +} +``` + +#### Entity Framework Configuration +No additional database tables required - uses existing: +- `Hashtag` table for event tracking +- `Content` table for comment storage +- `Creator` table for Reddit user information + +#### Configuration Encryption +Supports TagzApp's configuration encryption for sensitive settings: +```csharp +[EncryptedConfiguration] +public class RedditAMAConfiguration : BaseProviderConfiguration +{ + // Configuration properties... +} +``` + +## User Interface + +### Admin Configuration Panel + +#### Location +- **Path**: `/admin/providers/redditama` +- **Navigation**: Admin → Providers → Reddit AMA +- **Permissions**: Requires `Admin` role + +#### Configuration Form +```html +
+

Reddit AMA Provider Configuration

+ +
+ + + Enable real-time monitoring of Reddit AMA threads +
+ +
+ + + Paste the full Reddit post URL here +
+ +
+
+ + +
+
+ + +
+
+ +
+ + + Auto-populated from Reddit API +
+ +
+ + + Required by Reddit API (e.g., "TagzApp/1.0") +
+ +
+
+ + + How often to check for new comments (15-300 seconds) +
+
+ + +
+
+ +
+ +
+ + +
+
+ +
+ + + Hide comments below this score (-10 recommended) +
+ +
+ + + One username per line +
+ +
+ + +
+ + @if (!string.IsNullOrEmpty(TestResult)) + { +
+ @TestResult +
+ } +
+``` + +#### URL Parsing Logic +```csharp +private void ParseRedditUrl() +{ + if (Uri.TryCreate(RedditUrl, UriKind.Absolute, out var uri)) + { + // Parse: https://reddit.com/r/IAmA/comments/abc123/title/ + var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (segments.Length >= 4 && segments[0] == "r" && segments[2] == "comments") + { + Config.Subreddit = segments[1]; + Config.PostId = segments[3]; + + // Fetch post title from Reddit API + _ = FetchPostTitle(); + } + } +} +``` + +### Provider Status Dashboard + +#### Live Status Panel +```html +
+

Reddit AMA Status

+ +
+
+ + + @(IsConnected ? "Connected" : "Disconnected") + +
+ +
+ + @LastUpdate.ToString("HH:mm:ss") +
+ +
+ + @QueuedComments +
+ +
+ + @TotalComments +
+
+ +
+ + @ThreadTitle +
+
+``` + +## Service Integration + +### Dependency Injection +```csharp +// Program.cs or ServiceCollectionExtensions +services.Configure( + builder.Configuration.GetSection(RedditAMAConfiguration.AppSettingsSection)); + +services.AddScoped(); +services.AddHostedService(); +services.AddHttpClient(); +``` + +### Background Service +```csharp +public class RedditAMABackgroundService : BackgroundService +{ + private readonly RedditAMAProvider _provider; + private readonly ILogger _logger; + private readonly RedditAMAConfiguration _config; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested && _config.Enabled) + { + try + { + await _provider.PollForNewComments(); + await Task.Delay(TimeSpan.FromSeconds(_config.RefreshIntervalSeconds), stoppingToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error polling Reddit AMA comments"); + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); // Backoff on error + } + } + } +} +``` + +## Error Handling + +### API Error Scenarios +1. **Reddit API Down**: Exponential backoff, max 5 minutes between retries +2. **Post Not Found**: Disable provider, log error, notify admin +3. **Rate Limiting**: Respect Reddit's rate limits, increase polling interval +4. **Network Issues**: Retry with jitter, maintain queue during outages +5. **Invalid Configuration**: Validate settings, provide helpful error messages + +### Logging Strategy +```csharp +_logger.LogInformation("RedditAMA: Polling thread {ThreadId} in r/{Subreddit}", postId, subreddit); +_logger.LogWarning("RedditAMA: Rate limited, increasing interval to {Interval}s", newInterval); +_logger.LogError(ex, "RedditAMA: Failed to fetch comments for thread {ThreadId}", postId); +``` + +## Performance Considerations + +### Memory Management +- **Queue Size Limit**: Maximum 1000 comments in memory +- **Comment Deduplication**: Track processed comment IDs to avoid duplicates +- **Garbage Collection**: Clear old comment IDs after 24 hours + +### Network Optimization +- **HTTP Client Reuse**: Single HttpClient instance with connection pooling +- **Compression**: Accept gzip encoding from Reddit API +- **Conditional Requests**: Use If-Modified-Since when supported + +### Scalability +- **Multi-Thread Support**: Multiple AMA providers can run simultaneously +- **Resource Limits**: Configurable memory and network usage limits +- **Circuit Breaker**: Disable provider after consecutive failures + +## Testing Strategy + +### Unit Tests +- Configuration validation +- Comment parsing and mapping +- Queue management +- Error handling scenarios + +### Integration Tests +- Reddit API integration +- Database storage +- Background service lifecycle +- Rate limiting behavior + +### Manual Testing Scenarios +1. Configure provider with live AMA thread +2. Verify comments appear in TagzApp waterfall +3. Test configuration UI with various Reddit URLs +4. Verify error handling with invalid post IDs +5. Test performance with high-comment threads + +## Security Considerations + +### Data Privacy +- **No Authentication**: Provider only accesses public Reddit data +- **User Data**: Store minimal Reddit user information +- **Content Filtering**: Support blocking inappropriate usernames/content + +### Rate Limiting +- **Respect Reddit TOS**: Stay within API rate limits +- **Configurable Intervals**: Allow users to reduce polling frequency +- **Backoff Strategy**: Increase intervals during high load + +### Input Validation +- **URL Sanitization**: Validate Reddit URLs before parsing +- **Configuration Validation**: Ensure safe configuration values +- **Content Sanitization**: Filter malicious content from comments + +## Deployment Considerations + +### Configuration Migration +- **Default Settings**: Safe defaults for new installations +- **Upgrade Path**: Migrate from beta configurations +- **Environment Variables**: Support Docker/Kubernetes deployment + +### Monitoring +- **Health Checks**: Provider health endpoint +- **Metrics**: Comment processing rates, API response times +- **Alerting**: Notify admins of provider failures + +### Documentation Requirements +- **Admin Guide**: Configuration and troubleshooting +- **User Guide**: How to use RedditAMA for events +- **API Documentation**: Technical integration details + +## Future Enhancements + +### Phase 2 Features +- **Multiple Thread Support**: Monitor multiple AMA threads simultaneously +- **Comment Threading**: Display Reddit comment hierarchy in TagzApp +- **Sentiment Analysis**: Analyze comment sentiment for AMA hosts +- **Auto-Discovery**: Automatically find AMA threads by host user + +### Integration Opportunities +- **Twitch Integration**: Combine Reddit AMA with Twitch chat for live streams +- **YouTube Integration**: Sync with YouTube premiere chat +- **Discord Integration**: Cross-post questions to Discord servers +- **Analytics**: Track AMA engagement metrics and popular questions + +## Success Criteria + +### Technical Success +- ✅ Provider processes Reddit comments in real-time (< 60 second delay) +- ✅ Handles Reddit API rate limits gracefully +- ✅ Configuration UI is intuitive and validates inputs +- ✅ No memory leaks during extended operation +- ✅ Proper error handling and logging + +### User Experience Success +- ✅ Event organizers can easily configure AMA monitoring +- ✅ Comments appear in TagzApp waterfall with proper formatting +- ✅ Admin can moderate inappropriate comments +- ✅ Performance remains stable during high-activity AMAs +- ✅ Clear documentation for setup and usage + +### Business Success +- ✅ Enables new use cases for TagzApp (AMAs, Q&A sessions, product launches) +- ✅ Differentiates TagzApp from other social media aggregators +- ✅ Provides value for community events and live streaming +- ✅ Easy to demonstrate and explain to potential users + +--- + +## Implementation Timeline + +### Phase 1: Core Provider (2 weeks) +- [ ] Reddit API integration and comment parsing +- [ ] Basic provider implementation following TwitchChat pattern +- [ ] Configuration model and validation +- [ ] Unit tests for core functionality + +### Phase 2: UI and Configuration (1 week) +- [ ] Admin configuration panel +- [ ] URL parsing and validation +- [ ] Provider status dashboard +- [ ] Configuration persistence + +### Phase 3: Polish and Documentation (1 week) +- [ ] Error handling and logging +- [ ] Performance optimization +- [ ] User documentation +- [ ] Integration testing + +### Phase 4: Testing and Deployment (1 week) +- [ ] Manual testing with live AMA threads +- [ ] Load testing and performance validation +- [ ] Documentation review +- [ ] Production deployment preparation + +**Total Estimated Timeline: 5 weeks** + +--- + +*This design document serves as the comprehensive specification for implementing the RedditAMA provider in TagzApp. It should be reviewed and approved by the development team before implementation begins.* \ No newline at end of file diff --git a/doc/RedditAMA-Technical-Implementation.md b/doc/RedditAMA-Technical-Implementation.md new file mode 100644 index 00000000..d537dcbf --- /dev/null +++ b/doc/RedditAMA-Technical-Implementation.md @@ -0,0 +1,925 @@ +# RedditAMA Provider - Technical Implementation Guide + +## Implementation Overview + +This guide provides detailed technical specifications for implementing the RedditAMA provider in TagzApp, following the established provider pattern used by TwitchChat and YouTube providers. + +## File Structure + +``` +src/TagzApp.Providers.RedditAMA/ +├── TagzApp.Providers.RedditAMA.csproj +├── RedditAMAProvider.cs # Main provider implementation +├── RedditAMAConfiguration.cs # Configuration model +├── RedditAMABackgroundService.cs # Background polling service +├── Models/ +│ ├── RedditApiModels.cs # Reddit API response models +│ └── RedditComment.cs # Internal comment model +├── Services/ +│ ├── IRedditApiService.cs # Reddit API abstraction +│ └── RedditApiService.cs # Reddit API implementation +└── Extensions/ + └── ServiceCollectionExtensions.cs # DI registration +``` + +## Core Implementation + +### 1. Provider Configuration + +```csharp +// TagzApp.Providers.RedditAMA/RedditAMAConfiguration.cs +using TagzApp.Common.Client; +using System.ComponentModel.DataAnnotations; + +namespace TagzApp.Providers.RedditAMA; + +public class RedditAMAConfiguration : BaseProviderConfiguration +{ + public const string AppSettingsSection = "providers:redditama"; + + public string Description => "Monitor Reddit AMA/discussion threads for live comments"; + public string Name => "Reddit AMA"; + + [Required] + [Display(Name = "Subreddit", Description = "The subreddit containing the post (e.g., 'IAmA')")] + public string Subreddit { get; set; } = string.Empty; + + [Required] + [Display(Name = "Post ID", Description = "The Reddit post ID to monitor")] + public string PostId { get; set; } = string.Empty; + + [Display(Name = "Post Title", Description = "Title of the Reddit post (auto-populated)")] + public string PostTitle { get; set; } = string.Empty; + + [Required] + [Display(Name = "User Agent", Description = "User agent string for Reddit API requests")] + public string UserAgent { get; set; } = "TagzApp/1.0 (by /u/TagzApp)"; + + [Range(15, 300)] + [Display(Name = "Refresh Interval (seconds)", Description = "How often to poll for new comments")] + public int RefreshIntervalSeconds { get; set; } = 30; + + [Range(10, 500)] + [Display(Name = "Max Comments Per Poll", Description = "Maximum comments to fetch per request")] + public int MaxCommentsPerPoll { get; set; } = 100; + + [Display(Name = "Include Replies", Description = "Include replies to top-level comments")] + public bool IncludeReplies { get; set; } = true; + + [Display(Name = "Minimum Comment Score", Description = "Hide comments below this score")] + public int MinCommentScore { get; set; } = -10; + + [Display(Name = "Hide Deleted Comments", Description = "Filter out deleted/removed comments")] + public bool HideDeletedComments { get; set; } = true; + + [Display(Name = "Blocked Authors", Description = "Comma-separated list of usernames to block")] + public string BlockedAuthors { get; set; } = string.Empty; + + public string[] Keys => new[] + { + nameof(Subreddit), + nameof(PostId), + nameof(PostTitle), + nameof(UserAgent), + nameof(RefreshIntervalSeconds), + nameof(MaxCommentsPerPoll), + nameof(IncludeReplies), + nameof(MinCommentScore), + nameof(HideDeletedComments), + nameof(BlockedAuthors) + }; + + public bool IsValid => !string.IsNullOrWhiteSpace(Subreddit) && + !string.IsNullOrWhiteSpace(PostId) && + !string.IsNullOrWhiteSpace(UserAgent) && + RefreshIntervalSeconds >= 15 && + MaxCommentsPerPoll > 0; + + public string[] GetBlockedAuthorsList() => + BlockedAuthors?.Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(s => s.Trim().ToLowerInvariant()) + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray() ?? Array.Empty(); +} +``` + +### 2. Reddit API Models + +```csharp +// TagzApp.Providers.RedditAMA/Models/RedditApiModels.cs +using System.Text.Json.Serialization; + +namespace TagzApp.Providers.RedditAMA.Models; + +public class RedditListingResponse +{ + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("data")] + public RedditListingData Data { get; set; } = new(); +} + +public class RedditListingData +{ + [JsonPropertyName("children")] + public T[] Children { get; set; } = Array.Empty(); + + [JsonPropertyName("after")] + public string? After { get; set; } + + [JsonPropertyName("before")] + public string? Before { get; set; } +} + +public class RedditThing +{ + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("data")] + public T Data { get; set; } = default!; +} + +public class RedditPost +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("author")] + public string Author { get; set; } = string.Empty; + + [JsonPropertyName("subreddit")] + public string Subreddit { get; set; } = string.Empty; + + [JsonPropertyName("created_utc")] + public long CreatedUtc { get; set; } + + [JsonPropertyName("permalink")] + public string Permalink { get; set; } = string.Empty; + + [JsonPropertyName("selftext")] + public string SelfText { get; set; } = string.Empty; + + [JsonPropertyName("num_comments")] + public int NumComments { get; set; } +} + +public class RedditComment +{ + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + [JsonPropertyName("author")] + public string Author { get; set; } = string.Empty; + + [JsonPropertyName("body")] + public string Body { get; set; } = string.Empty; + + [JsonPropertyName("created_utc")] + public long CreatedUtc { get; set; } + + [JsonPropertyName("permalink")] + public string Permalink { get; set; } = string.Empty; + + [JsonPropertyName("score")] + public int Score { get; set; } + + [JsonPropertyName("depth")] + public int Depth { get; set; } + + [JsonPropertyName("parent_id")] + public string ParentId { get; set; } = string.Empty; + + [JsonPropertyName("is_submitter")] + public bool IsSubmitter { get; set; } + + [JsonPropertyName("distinguished")] + public string? Distinguished { get; set; } + + [JsonPropertyName("stickied")] + public bool Stickied { get; set; } + + [JsonPropertyName("replies")] + public object? Replies { get; set; } // Can be string or RedditListingResponse> +} +``` + +### 3. Reddit API Service + +```csharp +// TagzApp.Providers.RedditAMA/Services/IRedditApiService.cs +using TagzApp.Providers.RedditAMA.Models; + +namespace TagzApp.Providers.RedditAMA.Services; + +public interface IRedditApiService +{ + Task GetPostAsync(string subreddit, string postId, CancellationToken cancellationToken = default); + Task> GetCommentsAsync(string subreddit, string postId, int limit = 100, CancellationToken cancellationToken = default); + Task TestConnectionAsync(CancellationToken cancellationToken = default); +} + +// TagzApp.Providers.RedditAMA/Services/RedditApiService.cs +using System.Net.Http.Json; +using System.Text.Json; +using TagzApp.Providers.RedditAMA.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace TagzApp.Providers.RedditAMA.Services; + +public class RedditApiService : IRedditApiService +{ + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly RedditAMAConfiguration _config; + + public RedditApiService( + HttpClient httpClient, + ILogger logger, + IOptions config) + { + _httpClient = httpClient; + _logger = logger; + _config = config.Value; + + // Configure HttpClient + _httpClient.BaseAddress = new Uri("https://www.reddit.com"); + _httpClient.DefaultRequestHeaders.Add("User-Agent", _config.UserAgent); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + } + + public async Task GetPostAsync(string subreddit, string postId, CancellationToken cancellationToken = default) + { + try + { + var url = $"/r/{subreddit}/comments/{postId}.json?limit=1"; + var response = await _httpClient.GetFromJsonAsync[]>(url, cancellationToken); + + return response?.FirstOrDefault()?.Data; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get Reddit post {PostId} from r/{Subreddit}", postId, subreddit); + return null; + } + } + + public async Task> GetCommentsAsync(string subreddit, string postId, int limit = 100, CancellationToken cancellationToken = default) + { + try + { + var url = $"/r/{subreddit}/comments/{postId}.json?sort=new&limit={limit}"; + var response = await _httpClient.GetFromJsonAsync[]>(url, cancellationToken); + + if (response?.Length < 2) return Enumerable.Empty(); + + // Second element contains comments + var commentsSection = response[1]; + var commentListing = JsonSerializer.Deserialize>>( + JsonSerializer.SerializeToUtf8Bytes(commentsSection)); + + return ExtractAllComments(commentListing?.Data.Children ?? Array.Empty>()); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to get comments for Reddit post {PostId} from r/{Subreddit}", postId, subreddit); + return Enumerable.Empty(); + } + } + + private IEnumerable ExtractAllComments(IEnumerable> commentThings) + { + var comments = new List(); + + foreach (var thing in commentThings) + { + if (thing.Data.Author == "[deleted]" && _config.HideDeletedComments) + continue; + + comments.Add(thing.Data); + + // Extract replies if enabled + if (_config.IncludeReplies && thing.Data.Replies is JsonElement repliesElement) + { + try + { + var repliesListing = JsonSerializer.Deserialize>>( + repliesElement.GetRawText()); + + if (repliesListing?.Data.Children != null) + { + comments.AddRange(ExtractAllComments(repliesListing.Data.Children)); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to parse replies for comment {CommentId}", thing.Data.Id); + } + } + } + + return comments; + } + + public async Task TestConnectionAsync(CancellationToken cancellationToken = default) + { + try + { + var response = await _httpClient.GetAsync("/r/test.json?limit=1", cancellationToken); + return response.IsSuccessStatusCode; + } + catch + { + return false; + } + } +} +``` + +### 4. Main Provider Implementation + +```csharp +// TagzApp.Providers.RedditAMA/RedditAMAProvider.cs +using System.Collections.Concurrent; +using TagzApp.Common.Client; +using TagzApp.Common.Models; +using TagzApp.Providers.RedditAMA.Services; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace TagzApp.Providers.RedditAMA; + +public class RedditAMAProvider : ISocialMediaProvider +{ + private readonly IRedditApiService _redditApi; + private readonly ILogger _logger; + private readonly RedditAMAConfiguration _config; + private readonly ConcurrentQueue _commentQueue = new(); + private readonly HashSet _processedCommentIds = new(); + private readonly SemaphoreSlim _pollingSemaphore = new(1, 1); + + public string Id => "REDDIT_AMA"; + public string DisplayName => "Reddit AMA"; + public TimeSpan NewContentRetrievalFrequency => TimeSpan.FromSeconds(Math.Max(_config.RefreshIntervalSeconds, 15)); + public bool Enabled => _config.Enabled && _config.IsValid; + + public RedditAMAProvider( + IRedditApiService redditApi, + ILogger logger, + IOptions config) + { + _redditApi = redditApi; + _logger = logger; + _config = config.Value; + } + + public async Task> GetContentForHashtag(Hashtag tag, DateTimeOffset since) + { + if (!Enabled) + { + return Enumerable.Empty(); + } + + // Return and clear the comment queue (similar to TwitchChat pattern) + var comments = new List(); + while (_commentQueue.TryDequeue(out var comment)) + { + comment.HashtagSought = tag.Text.ToLowerInvariant(); + comments.Add(comment); + } + + _logger.LogDebug("RedditAMA: Returning {Count} comments for hashtag {Hashtag}", + comments.Count, tag.Text); + + return comments; + } + + public async Task PollForNewComments(CancellationToken cancellationToken = default) + { + if (!Enabled) + { + return; + } + + await _pollingSemaphore.WaitAsync(cancellationToken); + try + { + var comments = await _redditApi.GetCommentsAsync( + _config.Subreddit, + _config.PostId, + _config.MaxCommentsPerPoll, + cancellationToken); + + var newComments = ProcessNewComments(comments); + + _logger.LogInformation("RedditAMA: Found {NewCount} new comments in r/{Subreddit} post {PostId}", + newComments.Count(), _config.Subreddit, _config.PostId); + } + catch (Exception ex) + { + _logger.LogError(ex, "RedditAMA: Error polling for comments in r/{Subreddit} post {PostId}", + _config.Subreddit, _config.PostId); + } + finally + { + _pollingSemaphore.Release(); + } + } + + private IEnumerable ProcessNewComments(IEnumerable comments) + { + var blockedAuthors = _config.GetBlockedAuthorsList(); + var newComments = new List(); + + foreach (var comment in comments) + { + // Skip if already processed + if (_processedCommentIds.Contains(comment.Id)) + continue; + + // Apply filters + if (ShouldFilterComment(comment, blockedAuthors)) + continue; + + var content = MapToContent(comment); + _commentQueue.Enqueue(content); + newComments.Add(content); + + // Track processed comment + _processedCommentIds.Add(comment.Id); + + // Limit memory usage - keep only recent comment IDs + if (_processedCommentIds.Count > 10000) + { + var oldIds = _processedCommentIds.Take(5000).ToList(); + foreach (var oldId in oldIds) + { + _processedCommentIds.Remove(oldId); + } + } + } + + return newComments; + } + + private bool ShouldFilterComment(Models.RedditComment comment, string[] blockedAuthors) + { + // Filter deleted comments + if (_config.HideDeletedComments && + (comment.Author == "[deleted]" || comment.Body == "[deleted]" || comment.Body == "[removed]")) + { + return true; + } + + // Filter by score + if (comment.Score < _config.MinCommentScore) + { + return true; + } + + // Filter blocked authors + if (blockedAuthors.Contains(comment.Author.ToLowerInvariant())) + { + return true; + } + + return false; + } + + private Content MapToContent(Models.RedditComment comment) + { + return new Content + { + Provider = Id, + ProviderId = comment.Id, + Type = ContentType.Message, + Timestamp = DateTimeOffset.FromUnixTimeSeconds(comment.CreatedUtc), + SourceUri = new Uri($"https://reddit.com{comment.Permalink}"), + Author = new Creator + { + DisplayName = comment.Author, + UserName = comment.Author, + ProfileUri = new Uri($"https://reddit.com/u/{comment.Author}"), + ProfileImageUri = comment.IsSubmitter ? + new Uri("https://tagzapp.io/img/reddit-op-badge.png") : null + }, + Text = comment.Body, + ExtendedMetadata = new Dictionary + { + ["score"] = comment.Score, + ["depth"] = comment.Depth, + ["isOP"] = comment.IsSubmitter, + ["distinguished"] = comment.Distinguished ?? string.Empty, + ["stickied"] = comment.Stickied, + ["parentId"] = comment.ParentId + } + }; + } + + public async Task GetStatus() + { + try + { + var canConnect = await _redditApi.TestConnectionAsync(); + var post = await _redditApi.GetPostAsync(_config.Subreddit, _config.PostId); + + return new ProviderStatus + { + IsConnected = canConnect && post != null, + StatusMessage = canConnect && post != null ? + $"Monitoring: {post?.Title}" : + "Unable to connect or post not found", + LastUpdated = DateTimeOffset.UtcNow, + ExtendedInfo = new Dictionary + { + ["subreddit"] = _config.Subreddit, + ["postId"] = _config.PostId, + ["postTitle"] = post?.Title ?? "Unknown", + ["queuedComments"] = _commentQueue.Count, + ["processedComments"] = _processedCommentIds.Count + } + }; + } + catch (Exception ex) + { + return new ProviderStatus + { + IsConnected = false, + StatusMessage = $"Error: {ex.Message}", + LastUpdated = DateTimeOffset.UtcNow + }; + } + } +} +``` + +### 5. Background Service + +```csharp +// TagzApp.Providers.RedditAMA/RedditAMABackgroundService.cs +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace TagzApp.Providers.RedditAMA; + +public class RedditAMABackgroundService : BackgroundService +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private readonly RedditAMAConfiguration _config; + + public RedditAMABackgroundService( + IServiceProvider serviceProvider, + ILogger logger, + IOptions config) + { + _serviceProvider = serviceProvider; + _logger = logger; + _config = config.Value; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("RedditAMA background service starting"); + + while (!stoppingToken.IsCancellationRequested) + { + if (!_config.Enabled || !_config.IsValid) + { + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + continue; + } + + try + { + using var scope = _serviceProvider.CreateScope(); + var provider = scope.ServiceProvider.GetRequiredService(); + + await provider.PollForNewComments(stoppingToken); + + var delay = TimeSpan.FromSeconds(Math.Max(_config.RefreshIntervalSeconds, 15)); + await Task.Delay(delay, stoppingToken); + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + break; + } + catch (Exception ex) + { + _logger.LogError(ex, "RedditAMA background service error"); + + // Exponential backoff on errors + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + } + } + + _logger.LogInformation("RedditAMA background service stopping"); + } +} +``` + +### 6. Service Registration + +```csharp +// TagzApp.Providers.RedditAMA/Extensions/ServiceCollectionExtensions.cs +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using TagzApp.Common.Client; +using TagzApp.Providers.RedditAMA.Services; + +namespace TagzApp.Providers.RedditAMA.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddRedditAMAProvider(this IServiceCollection services, IConfiguration configuration) + { + // Configure the provider + services.Configure( + configuration.GetSection(RedditAMAConfiguration.AppSettingsSection)); + + // Register services + services.AddHttpClient(); + services.AddScoped(); + services.AddHostedService(); + + return services; + } +} +``` + +### 7. Project File + +```xml + + + + + net9.0 + enable + enable + + + + + + + + + + + + + + + + +``` + +## Integration Points + +### 1. Add to Main Solution + +```xml + +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagzApp.Providers.RedditAMA", "TagzApp.Providers.RedditAMA\TagzApp.Providers.RedditAMA.csproj", "{GUID}" +EndProject +``` + +### 2. Register in Main Application + +```csharp +// In TagzApp.Blazor/Program.cs or TagzApp.AppHost/Program.cs +using TagzApp.Providers.RedditAMA.Extensions; + +// Add the provider +builder.Services.AddRedditAMAProvider(builder.Configuration); +``` + +### 3. Configuration in appsettings.json + +```json +{ + "providers": { + "redditama": { + "enabled": false, + "subreddit": "", + "postId": "", + "postTitle": "", + "userAgent": "TagzApp/1.0 (by /u/TagzApp)", + "refreshIntervalSeconds": 30, + "maxCommentsPerPoll": 100, + "includeReplies": true, + "minCommentScore": -10, + "hideDeletedComments": true, + "blockedAuthors": "" + } + } +} +``` + +## Testing Strategy + +### Unit Tests + +```csharp +// TagzApp.UnitTest/Providers/RedditAMAProviderTests.cs +using Xunit; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using TagzApp.Providers.RedditAMA; +using TagzApp.Providers.RedditAMA.Services; + +public class RedditAMAProviderTests +{ + [Fact] + public void Provider_ShouldBeDisabled_WhenConfigurationIsInvalid() + { + // Arrange + var config = new RedditAMAConfiguration(); + var provider = CreateProvider(config); + + // Act & Assert + Assert.False(provider.Enabled); + } + + [Fact] + public void Provider_ShouldBeEnabled_WhenConfigurationIsValid() + { + // Arrange + var config = new RedditAMAConfiguration + { + Enabled = true, + Subreddit = "IAmA", + PostId = "abc123", + UserAgent = "Test/1.0" + }; + var provider = CreateProvider(config); + + // Act & Assert + Assert.True(provider.Enabled); + } + + private RedditAMAProvider CreateProvider(RedditAMAConfiguration config) + { + var mockApiService = new Mock(); + var options = Options.Create(config); + var logger = NullLogger.Instance; + + return new RedditAMAProvider(mockApiService.Object, logger, options); + } +} +``` + +### Integration Tests + +```csharp +// TagzApp.UnitTest/Providers/RedditAMAIntegrationTests.cs +[Collection("Integration")] +public class RedditAMAIntegrationTests +{ + [Fact] + public async Task RedditApiService_ShouldFetchComments_ForValidPost() + { + // Arrange + var httpClient = new HttpClient(); + var config = Options.Create(new RedditAMAConfiguration + { + UserAgent = "TagzApp-Test/1.0" + }); + var logger = NullLogger.Instance; + var service = new RedditApiService(httpClient, logger, config); + + // Act + var comments = await service.GetCommentsAsync("test", "valid_post_id"); + + // Assert + Assert.NotNull(comments); + } +} +``` + +## Error Handling Patterns + +### API Rate Limiting + +```csharp +public class RedditApiService : IRedditApiService +{ + private static readonly SemaphoreSlim RateLimitSemaphore = new(1, 1); + private static DateTime LastRequestTime = DateTime.MinValue; + private static readonly TimeSpan MinRequestInterval = TimeSpan.FromSeconds(1); + + private async Task EnforceRateLimit() + { + await RateLimitSemaphore.WaitAsync(); + try + { + var timeSinceLastRequest = DateTime.UtcNow - LastRequestTime; + if (timeSinceLastRequest < MinRequestInterval) + { + var delay = MinRequestInterval - timeSinceLastRequest; + await Task.Delay(delay); + } + LastRequestTime = DateTime.UtcNow; + } + finally + { + RateLimitSemaphore.Release(); + } + } +} +``` + +### Circuit Breaker Pattern + +```csharp +public class RedditAMAProvider : ISocialMediaProvider +{ + private int _consecutiveFailures = 0; + private DateTime _lastFailureTime = DateTime.MinValue; + private readonly TimeSpan _circuitBreakerTimeout = TimeSpan.FromMinutes(5); + + private bool IsCircuitBreakerOpen() + { + if (_consecutiveFailures < 3) return false; + + return DateTime.UtcNow - _lastFailureTime < _circuitBreakerTimeout; + } + + private void OnSuccess() + { + _consecutiveFailures = 0; + } + + private void OnFailure() + { + _consecutiveFailures++; + _lastFailureTime = DateTime.UtcNow; + } +} +``` + +## Performance Considerations + +### Memory Management + +```csharp +public class RedditAMAProvider : ISocialMediaProvider +{ + private const int MaxQueueSize = 1000; + private const int MaxProcessedIds = 10000; + + private void ManageQueueSize() + { + // Limit comment queue size + while (_commentQueue.Count > MaxQueueSize) + { + _commentQueue.TryDequeue(out _); + } + + // Limit processed IDs set size + if (_processedCommentIds.Count > MaxProcessedIds) + { + var oldIds = _processedCommentIds.Take(MaxProcessedIds / 2).ToList(); + foreach (var oldId in oldIds) + { + _processedCommentIds.Remove(oldId); + } + } + } +} +``` + +### HTTP Client Optimization + +```csharp +public class RedditApiService : IRedditApiService +{ + public RedditApiService(HttpClient httpClient, ILogger logger, IOptions config) + { + _httpClient = httpClient; + + // Optimize HttpClient + _httpClient.DefaultRequestHeaders.Add("User-Agent", config.Value.UserAgent); + _httpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate"); + _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + + // Connection pooling settings + var handler = new SocketsHttpHandler() + { + PooledConnectionLifetime = TimeSpan.FromMinutes(15), + MaxConnectionsPerServer = 10 + }; + } +} +``` + +This implementation provides a complete, production-ready RedditAMA provider following TagzApp's established patterns and best practices. \ No newline at end of file diff --git a/doc/RedditAMA-UI-Specification.md b/doc/RedditAMA-UI-Specification.md new file mode 100644 index 00000000..ceab54b2 --- /dev/null +++ b/doc/RedditAMA-UI-Specification.md @@ -0,0 +1,631 @@ +# RedditAMA Provider - UI/UX Configuration Specification + +## Overview + +This document specifies the user interface design for the RedditAMA provider configuration panel in TagzApp's admin interface. The design follows TagzApp's existing provider configuration patterns while addressing the unique requirements of Reddit AMA monitoring. + +## Design Principles + +### 1. Consistency with Existing Providers +- Follow the same layout patterns as TwitchChat and YouTube provider configs +- Use consistent form styling and validation patterns +- Maintain the same navigation structure and breadcrumbs + +### 2. Simplicity First +- Primary workflow: paste Reddit URL → auto-configure → save +- Advanced settings are collapsible/secondary +- Clear visual feedback for configuration state + +### 3. Error Prevention +- Real-time URL validation and parsing +- Clear error messages with actionable solutions +- Test connection functionality before saving + +## Page Structure + +### Location and Navigation +``` +Admin Dashboard +└── Providers + ├── Twitter + ├── YouTube + ├── TwitchChat + ├── Mastodon + ├── Bluesky + └── Reddit AMA ← New provider +``` + +**URL**: `/admin/providers/redditama` +**Page Title**: "Reddit AMA Provider Configuration" +**Breadcrumb**: Admin > Providers > Reddit AMA + +## Layout Specification + +### Main Configuration Panel + +```html +
+ +
+
+ Reddit +
+
+

Reddit AMA Provider

+

+ Monitor specific Reddit threads for live comments during AMAs, Q&A sessions, + and community discussions. +

+
+
+ + @(IsEnabled ? "Enabled" : "Disabled") + +
+
+ + +
+ +
+
+ +

+ When enabled, TagzApp will monitor the specified Reddit thread for new comments. +

+
+
+ + +
+

Thread Configuration

+ + +
+ +
+ + +
+ @if (UrlParseError) + { +
+ + @UrlParseErrorMessage +
+ } +

+ Paste the complete URL of the Reddit post you want to monitor. + Example: https://reddit.com/r/IAmA/comments/abc123/i_am_john_doe_ama/ +

+
+ + + @if (!string.IsNullOrEmpty(Config.Subreddit) && !string.IsNullOrEmpty(Config.PostId)) + { +
+

Detected Thread Information

+
+
+ + r/@Config.Subreddit +
+
+ + @Config.PostId +
+ @if (!string.IsNullOrEmpty(Config.PostTitle)) + { +
+ + @Config.PostTitle +
+ } +
+ @if (!string.IsNullOrEmpty(RedditUrl)) + { + + } +
+ } +
+ + +
+
+

+ + Advanced Settings +

+ @(ShowAdvancedSettings ? "Hide" : "Show") +
+ + @if (ShowAdvancedSettings) + { +
+ +
+
+ + +

+ How often to check for new comments (15-300 seconds). + Lower values provide faster updates but use more API calls. +

+
+
+ + +

+ Maximum number of comments to fetch in each API call. +

+
+
+ + +
+ +
+ + +
+
+ + +
+ + +

+ Hide comments with scores below this value. + Use -10 to hide heavily downvoted comments while keeping most content. +

+
+ + +
+ + +

+ Block comments from specific Reddit users. Enter one username per line. +

+
+ + +
+ + +

+ Required by Reddit's API. Should identify your application + (e.g., "TagzApp/1.0 by /u/yourusername"). +

+
+
+ } +
+ + +
+
+ + + +
+ +
+ +
+
+
+ + + @if (!string.IsNullOrEmpty(TestResult)) + { +
+
+ +

@(TestSuccess ? "Connection Test Successful" : "Connection Test Failed")

+
+
+

@TestResult

+ @if (TestSuccess && TestMetadata != null) + { + + } +
+
+ } +
+``` + +## Status Dashboard Component + +### Provider Status Panel +Located in the main providers dashboard, shows real-time status: + +```html +
+
+
+ Reddit AMA +
+
+

Reddit AMA

+ + @(Status.IsConnected ? "Active" : "Inactive") + +
+
+ +
+
+ + + @if (!string.IsNullOrEmpty(Config.PostTitle)) + { + @Config.PostTitle.Truncate(40) + } + else + { + r/@Config.Subreddit + } + +
+ +
+ + @Status.LastUpdated.ToString("HH:mm:ss") +
+ +
+ + @Status.ExtendedInfo["queuedComments"] +
+
+ +
+ + Configure + + @if (Config.Enabled) + { + + } +
+
+``` + +## Interactive Behavior Specification + +### URL Parsing Flow +1. **User pastes Reddit URL** into the URL field +2. **On blur or Parse button click**: + - Validate URL format + - Extract subreddit and post ID + - Show loading state + - Fetch post title from Reddit API + - Update parsed info panel + - Show success/error feedback + +### Validation States + +#### URL Validation +```typescript +interface URLValidationState { + isValid: boolean; + errorMessage?: string; + parsedData?: { + subreddit: string; + postId: string; + postTitle?: string; + }; +} + +// Valid URL patterns: +// - https://reddit.com/r/IAmA/comments/abc123/title/ +// - https://www.reddit.com/r/dotnet/comments/xyz789/announcement/ +// - https://old.reddit.com/r/programming/comments/123abc/discussion/ +``` + +#### Form Validation Rules +```csharp +public class RedditURLValidator +{ + public ValidationResult ValidateURL(string url) + { + if (string.IsNullOrWhiteSpace(url)) + return ValidationResult.Error("Reddit URL is required"); + + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) + return ValidationResult.Error("Invalid URL format"); + + if (!IsRedditDomain(uri.Host)) + return ValidationResult.Error("URL must be from reddit.com"); + + var pathParts = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); + if (pathParts.Length < 4 || pathParts[0] != "r" || pathParts[2] != "comments") + return ValidationResult.Error("Invalid Reddit post URL format"); + + return ValidationResult.Success(new ParsedRedditURL + { + Subreddit = pathParts[1], + PostId = pathParts[3] + }); + } +} +``` + +### Connection Testing Flow +1. **User clicks "Test Connection"** +2. **Show loading state** with spinner +3. **Perform API calls**: + - Test Reddit API connectivity + - Verify post exists and is accessible + - Fetch sample comments (if any) +4. **Display results**: + - Success: Show post title, comment count, last activity + - Failure: Show specific error message with suggested fix + +### Save Configuration Flow +1. **Validate all required fields** +2. **Show confirmation dialog** if changing from enabled to disabled +3. **Save to configuration** +4. **Update provider status** +5. **Show success message** +6. **Redirect to provider dashboard** (optional) + +## Visual Design Specifications + +### Color Scheme +Following TagzApp's design system: +- **Primary**: `#FF4500` (Reddit orange) +- **Success**: `#28a745` +- **Error**: `#dc3545` +- **Warning**: `#ffc107` +- **Info**: `#17a2b8` + +### Typography +- **Headers**: `font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif` +- **Body text**: `font-size: 14px; line-height: 1.5` +- **Help text**: `font-size: 12px; color: #666` +- **Code/URLs**: `font-family: 'Consolas', 'Monaco', monospace` + +### Spacing and Layout +- **Section margins**: `margin-bottom: 2rem` +- **Form group margins**: `margin-bottom: 1rem` +- **Input padding**: `padding: 0.5rem 0.75rem` +- **Button padding**: `padding: 0.5rem 1rem` + +### Icons +- **Provider icon**: Reddit logo (SVG) +- **Status indicators**: Check circle, error circle, warning triangle +- **UI icons**: Chevron (expand/collapse), external link, spinner (loading) + +### Responsive Behavior +```css +/* Desktop (default) */ +.form-row { + display: flex; + gap: 1rem; +} + +.form-row .form-group { + flex: 1; +} + +/* Tablet */ +@media (max-width: 768px) { + .form-row { + flex-direction: column; + } + + .input-with-button { + flex-direction: column; + } + + .parse-btn { + margin-top: 0.5rem; + } +} + +/* Mobile */ +@media (max-width: 480px) { + .provider-header { + flex-direction: column; + text-align: center; + } + + .form-actions { + flex-direction: column; + } + + .action-group { + width: 100%; + margin-bottom: 0.5rem; + } +} +``` + +## Accessibility Requirements + +### WCAG 2.1 AA Compliance +- **Keyboard navigation**: All interactive elements accessible via keyboard +- **Screen readers**: Proper ARIA labels and descriptions +- **Color contrast**: Minimum 4.5:1 ratio for text +- **Focus indicators**: Clear visual focus states + +### Specific Implementation +```html + + + +

Paste the complete URL...

+ + + + + +
+ Connection test successful +
+ + + +
+ +
+``` + +## Error States and Messages + +### Common Error Scenarios + +#### URL Parsing Errors +- **Empty URL**: "Please enter a Reddit post URL" +- **Invalid format**: "Please enter a valid URL (e.g., https://reddit.com/r/IAmA/comments/...)" +- **Not a Reddit URL**: "URL must be from reddit.com" +- **Wrong URL structure**: "This doesn't appear to be a Reddit post URL. Please use the format: reddit.com/r/subreddit/comments/postid/" +- **Post not found**: "This Reddit post could not be found. Please check the URL and try again." +- **Private subreddit**: "This subreddit is private and cannot be monitored." + +#### Configuration Errors +- **Missing subreddit**: "Subreddit is required" +- **Missing post ID**: "Reddit post ID is required" +- **Invalid refresh interval**: "Refresh interval must be between 15 and 300 seconds" +- **Invalid user agent**: "User agent is required for Reddit API access" + +#### Connection Test Errors +- **API unreachable**: "Unable to connect to Reddit API. Please check your internet connection." +- **Rate limited**: "Reddit API rate limit exceeded. Please try again in a few minutes." +- **Post deleted**: "This Reddit post has been deleted and cannot be monitored." +- **Subreddit banned**: "This subreddit has been banned or made private." + +### Success Messages +- **Configuration saved**: "Reddit AMA provider configuration saved successfully" +- **Connection test passed**: "Successfully connected to Reddit and found the post" +- **Provider enabled**: "Reddit AMA provider is now monitoring comments" +- **Provider disabled**: "Reddit AMA provider has been disabled" + +## Performance Considerations + +### Loading States +- **URL parsing**: Show spinner next to Parse button +- **Connection testing**: Replace button text with "Testing..." and spinner +- **Configuration saving**: Show loading overlay on form +- **Page loading**: Skeleton loading for configuration form + +### Debouncing +- **URL input**: Debounce parsing by 500ms after user stops typing +- **Configuration changes**: Debounce auto-save by 1000ms +- **API calls**: Prevent duplicate requests while one is in progress + +### Error Recovery +- **Network failures**: Automatic retry with exponential backoff +- **Invalid configurations**: Clear error states when user corrects input +- **Session timeouts**: Graceful handling with re-authentication prompt + +## Testing Checklist + +### Functional Testing +- [ ] URL parsing works with various Reddit URL formats +- [ ] Configuration validation prevents invalid settings +- [ ] Connection test accurately reports Reddit API status +- [ ] Save/load configuration persists correctly +- [ ] Enable/disable provider works as expected + +### UI/UX Testing +- [ ] Form is accessible via keyboard navigation +- [ ] Error messages are clear and actionable +- [ ] Success states provide appropriate feedback +- [ ] Responsive design works on mobile devices +- [ ] Loading states don't block user interaction + +### Integration Testing +- [ ] Provider configuration integrates with main dashboard +- [ ] Status updates reflect actual provider state +- [ ] Configuration changes trigger provider restart +- [ ] Error handling doesn't crash the admin interface + +This specification provides comprehensive guidance for implementing a user-friendly, accessible, and robust configuration interface for the RedditAMA provider. \ No newline at end of file diff --git a/user-docs/RedditAMA-Configuration-Guide.md b/user-docs/RedditAMA-Configuration-Guide.md new file mode 100644 index 00000000..d89d0079 --- /dev/null +++ b/user-docs/RedditAMA-Configuration-Guide.md @@ -0,0 +1,254 @@ +# Reddit AMA Provider - User Configuration Guide + +## Overview + +The Reddit AMA Provider allows TagzApp to monitor a specific Reddit post (such as an AMA thread) for new comments in real-time. This is perfect for live events, Q&A sessions, product launches, and community discussions where you want to display Reddit activity on TagzApp's waterfall display. + +## Prerequisites + +- TagzApp admin access +- A specific Reddit post URL you want to monitor +- The Reddit post must be publicly accessible (not in a private subreddit) + +## Step-by-Step Configuration + +### 1. Access Provider Configuration + +1. Log in to TagzApp as an administrator +2. Navigate to **Admin** → **Providers** → **Reddit AMA** +3. You'll see the Reddit AMA configuration panel + +### 2. Basic Setup + +#### Enable the Provider +- Check the **"Enable Provider"** checkbox +- This activates the Reddit AMA monitoring service + +#### Configure the Reddit Post +1. **Find your Reddit post URL** + - Example: `https://reddit.com/r/IAmA/comments/abc123/i_am_a_software_developer_ama/` + - Copy the complete URL from your browser + +2. **Paste the URL** + - Paste the full Reddit URL into the **"Reddit Post URL"** field + - TagzApp will automatically parse the URL and fill in: + - **Subreddit**: (e.g., "IAmA") + - **Post ID**: (e.g., "abc123") + - **Post Title**: (fetched automatically) + +### 3. Advanced Settings + +#### Refresh Rate +- **Refresh Interval**: How often TagzApp checks for new comments (15-300 seconds) +- **Recommended**: 30 seconds for active AMAs, 60+ seconds for slower discussions +- **Note**: Lower values check more frequently but use more API calls + +#### Comment Filtering +- **Max Comments Per Check**: Limits how many comments to fetch each time (10-500) +- **Include Replies**: Whether to show replies to comments (recommended: Yes) +- **Hide Deleted Comments**: Filter out [deleted] and [removed] comments (recommended: Yes) +- **Minimum Comment Score**: Hide heavily downvoted comments (recommended: -10) + +#### User Management +- **Blocked Authors**: List usernames (one per line) to exclude from the feed +- **Use for**: Blocking spam accounts, bots, or inappropriate users + +### 4. Test Your Configuration + +1. Click **"Test Connection"** to verify: + - Reddit post exists and is accessible + - API connection is working + - Comments can be fetched successfully + +2. **Expected Results**: + - ✅ "Connection successful, found X comments" + - ❌ "Post not found" - Check your URL + - ❌ "Subreddit is private" - Use a public subreddit + +### 5. Save and Activate + +1. Click **"Save Configuration"** +2. The provider will start monitoring within 30 seconds +3. Check the **Provider Status** panel to confirm it's running + +## Using the Provider + +### Creating a TagzApp "Hashtag" + +The Reddit AMA provider doesn't use traditional hashtags. Instead, you create an event identifier: + +1. **Go to TagzApp main page** +2. **Search for a custom tag**, such as: + - `"myama2024"` - For your specific AMA event + - `"productlaunch"` - For a product announcement thread + - `"liveqa"` - For a Q&A session + - `"conference2024"` - For a conference discussion thread + +3. **All comments from your monitored Reddit thread** will appear under this tag + +### Viewing the Results + +Comments will appear in TagzApp's waterfall display with: +- **Author**: Reddit username +- **Content**: Full comment text +- **Timestamp**: When the comment was posted +- **Link**: Direct link to the comment on Reddit +- **Score**: Reddit upvote/downvote score (if enabled) +- **OP Badge**: Special indicator for comments from the original poster + +## Common Use Cases + +### 1. Live AMA Events +**Scenario**: You're hosting an "Ask Me Anything" session + +**Setup**: +1. Create your AMA post on Reddit (r/IAmA or relevant subreddit) +2. Configure TagzApp to monitor that specific post +3. Create a hashtag like `"myama2024"` +4. Display TagzApp on OBS/streaming software + +**Result**: Questions appear in real-time during your live stream + +### 2. Product Launch Q&A +**Scenario**: Announcing a new product with Reddit discussion + +**Setup**: +1. Post product announcement on relevant subreddit +2. Monitor the thread for feedback and questions +3. Use hashtag like `"productlaunch"` + +**Result**: Real-time community reactions and questions + +### 3. Conference Session Discussion +**Scenario**: Live coding session with Reddit for questions + +**Setup**: +1. Create discussion thread for your session +2. Monitor for audience questions +3. Use hashtag matching your session name + +**Result**: Live Q&A integration with your presentation + +### 4. Community Event Coverage +**Scenario**: Major announcement or community event + +**Setup**: +1. Find or create the main discussion thread +2. Monitor for community reactions +3. Display reactions during live coverage + +**Result**: Real-time community sentiment and discussion + +## Troubleshooting + +### Common Issues + +#### "Post not found" Error +- **Cause**: Invalid Reddit URL or deleted post +- **Solution**: Verify the URL is correct and the post still exists + +#### "Subreddit is private" Error +- **Cause**: The subreddit requires membership to view +- **Solution**: Use a public subreddit or gain access to the private one + +#### No Comments Appearing +- **Possible Causes**: + - Provider is disabled + - Post has no new comments + - Comments are being filtered out + - TagzApp hashtag doesn't match +- **Solutions**: + - Check provider status in admin panel + - Verify the Reddit thread has activity + - Review filtering settings + - Ensure you're searching for the correct hashtag in TagzApp + +#### Comments Appear Slowly +- **Cause**: Refresh interval is too high +- **Solution**: Lower the refresh interval (minimum 15 seconds) + +#### Too Many Comments +- **Cause**: Very active thread overwhelming the display +- **Solutions**: + - Increase minimum comment score filter + - Reduce max comments per poll + - Add blocked authors for spam accounts + +### Rate Limiting +Reddit limits API calls to prevent abuse: +- **Limit**: ~60 requests per minute +- **TagzApp Response**: Automatically increases polling interval if rate limited +- **Your Action**: Consider increasing refresh interval for very active threads + +### Performance Tips +- **For Active AMAs**: 30-second refresh, score filter of 0 or higher +- **For Slower Discussions**: 60+ second refresh, include all comments +- **For High-Traffic Events**: Increase min score filter, block spam accounts +- **For Moderated Events**: Use blocked authors list for inappropriate users + +## Best Practices + +### Before Your Event +1. **Test the setup** with the actual Reddit thread +2. **Configure filters** to match your content quality needs +3. **Prepare moderation** by identifying potential blocked users +4. **Document your hashtag** for participants and viewers + +### During Your Event +1. **Monitor the provider status** to ensure it's running +2. **Watch for spam** and add blocked authors as needed +3. **Adjust filters** if too many/few comments are appearing +4. **Have backup plan** in case of Reddit API issues + +### After Your Event +1. **Keep the provider running** if you want to capture follow-up discussion +2. **Archive the configuration** for future similar events +3. **Review performance** and adjust settings for next time + +## Security and Privacy + +### Data Handling +- TagzApp only accesses **publicly available** Reddit comments +- **No authentication** required - only public data is used +- **Minimal storage** - comments are displayed and then archived normally + +### Privacy Considerations +- All Reddit usernames and comments are **already public** +- TagzApp displays them **as-is** from Reddit +- **Moderation tools** available to filter inappropriate content + +### Terms of Service +- Respects Reddit's **API rate limits** and terms of service +- **No automated voting** or posting - read-only access +- Suitable for **commercial and non-commercial** use + +--- + +## Getting Help + +### Documentation +- **Admin Guide**: See TagzApp admin documentation +- **General Usage**: Check TagzApp user manual +- **API Issues**: Review TagzApp logs in admin panel + +### Support Channels +- **GitHub Issues**: Technical problems and feature requests +- **Community Discord**: Setup help and usage questions +- **Documentation**: Additional guides and examples + +### Common Questions +**Q: Can I monitor multiple Reddit threads?** +A: Currently, each provider instance monitors one thread. You can run multiple providers for multiple threads. + +**Q: Does this work with private subreddits?** +A: No, only public subreddits and posts are supported. + +**Q: How much does this cost?** +A: Reddit's API is free for read-only access. No additional costs beyond TagzApp hosting. + +**Q: Can I moderate the comments?** +A: Yes, use TagzApp's built-in moderation tools plus the provider's blocking features. + +--- + +*Need more help? Check the TagzApp documentation or ask in the community Discord!* \ No newline at end of file From e92fe93cbd9e3dd02e370658e283710327e25bac Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz" Date: Sun, 5 Oct 2025 12:19:23 -0400 Subject: [PATCH 2/2] Add RedditAMA provider implementation roadmap - Central navigation document linking all design specifications - 5-week implementation timeline with 4 phases - GitHub agent instructions and success criteria - Risk mitigation and post-implementation considerations - Complete documentation structure overview --- doc/RedditAMA-Implementation-Roadmap.md | 212 ++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 doc/RedditAMA-Implementation-Roadmap.md diff --git a/doc/RedditAMA-Implementation-Roadmap.md b/doc/RedditAMA-Implementation-Roadmap.md new file mode 100644 index 00000000..60f7d973 --- /dev/null +++ b/doc/RedditAMA-Implementation-Roadmap.md @@ -0,0 +1,212 @@ +# RedditAMA Provider - Implementation Roadmap + +## Project Overview + +The RedditAMA Provider is a new social media provider for TagzApp that enables real-time monitoring of specific Reddit threads, particularly designed for Ask Me Anything (AMA) sessions, Q&A events, product launches, and community discussions. + +## Branch Status +- **Branch**: `feature/reddit-ama-provider` +- **Status**: Design phase complete, ready for implementation +- **Base Commit**: `3564cd0` - Complete design documentation committed + +## Documentation Structure + +This implementation includes comprehensive documentation across four key areas: + +### 1. **Core Design Document** 📋 +**File**: [`doc/RedditAMA-Provider-Design.md`](./RedditAMA-Provider-Design.md) + +**Purpose**: Master specification document covering the complete architecture, API integration, and implementation strategy. + +**Key Sections**: +- Technical architecture following TwitchChat provider pattern +- Reddit API integration and polling strategy +- Configuration models and database storage +- Performance considerations and error handling +- Success criteria and implementation timeline (5 weeks) + +**Target Audience**: GitHub agents, technical leads, developers + +--- + +### 2. **Technical Implementation Guide** 🔧 +**File**: [`doc/RedditAMA-Technical-Implementation.md`](./RedditAMA-Technical-Implementation.md) + +**Purpose**: Detailed technical specifications with complete code examples and implementation patterns. + +**Key Sections**: +- Complete source code structure and file organization +- API models and service implementations +- Background service and dependency injection patterns +- Testing strategies (unit, integration, manual) +- Performance optimization and error handling patterns + +**Target Audience**: GitHub agents implementing the provider, developers + +--- + +### 3. **UI/UX Specification** 🎨 +**File**: [`doc/RedditAMA-UI-Specification.md`](./RedditAMA-UI-Specification.md) + +**Purpose**: Comprehensive user interface design specification for the admin configuration panel. + +**Key Sections**: +- Complete HTML/CSS layout specifications +- Interactive behavior and validation flows +- Accessibility requirements (WCAG 2.1 AA) +- Error states and user feedback patterns +- Responsive design and mobile considerations + +**Target Audience**: GitHub agents building UI components, UX designers + +--- + +### 4. **User Configuration Guide** 📖 +**File**: [`user-docs/RedditAMA-Configuration-Guide.md`](../user-docs/RedditAMA-Configuration-Guide.md) + +**Purpose**: End-user documentation for configuring and using the RedditAMA provider. + +**Key Sections**: +- Step-by-step configuration instructions +- Common use cases (AMAs, Q&As, product launches) +- Troubleshooting guide and best practices +- Security and privacy considerations + +**Target Audience**: TagzApp administrators, event organizers, end users + +## Implementation Phases + +### Phase 1: Core Provider Implementation (2 weeks) +**Dependencies**: None +**Deliverables**: +- [ ] `TagzApp.Providers.RedditAMA` project structure +- [ ] Reddit API service and models +- [ ] Main provider implementation following ISocialMediaProvider +- [ ] Background polling service +- [ ] Unit tests for core functionality + +**Key Files to Create**: +- `src/TagzApp.Providers.RedditAMA/RedditAMAProvider.cs` +- `src/TagzApp.Providers.RedditAMA/RedditAMAConfiguration.cs` +- `src/TagzApp.Providers.RedditAMA/Services/RedditApiService.cs` +- `src/TagzApp.Providers.RedditAMA/RedditAMABackgroundService.cs` + +### Phase 2: UI Configuration Interface (1 week) +**Dependencies**: Phase 1 complete +**Deliverables**: +- [ ] Admin configuration panel with URL parsing +- [ ] Provider status dashboard integration +- [ ] Configuration validation and testing features +- [ ] Responsive design implementation + +**Key Files to Create**: +- `src/TagzApp.Blazor/Components/Admin/Providers/RedditAMAConfig.razor` +- `src/TagzApp.Blazor/Components/Admin/Providers/RedditAMAStatus.razor` + +### Phase 3: Integration and Polish (1 week) +**Dependencies**: Phases 1-2 complete +**Deliverables**: +- [ ] Service registration and dependency injection +- [ ] Error handling and logging implementation +- [ ] Performance optimization +- [ ] Integration with existing provider system + +**Key Files to Modify**: +- `src/TagzApp.Blazor/Program.cs` (service registration) +- `src/TagzApp.sln` (project reference) + +### Phase 4: Testing and Documentation (1 week) +**Dependencies**: Phases 1-3 complete +**Deliverables**: +- [ ] Integration tests with live Reddit API +- [ ] Manual testing with actual AMA threads +- [ ] Performance and load testing +- [ ] Documentation review and updates + +**Key Files to Create**: +- `src/TagzApp.UnitTest/Providers/RedditAMAProviderTests.cs` +- `src/TagzApp.WebTest/RedditAMAIntegrationTests.cs` + +## GitHub Agent Instructions + +### For Implementation Start +1. **Review all four documentation files** to understand the complete scope +2. **Follow the technical implementation guide** for exact code structure +3. **Use the existing TwitchChat provider** as a reference pattern +4. **Test against real Reddit threads** during development + +### For UI Implementation +1. **Reference the UI specification document** for exact layout requirements +2. **Follow TagzApp's existing provider configuration patterns** +3. **Implement accessibility features** as specified +4. **Test responsive design** on multiple screen sizes + +### For Testing and Validation +1. **Use the provided test cases** in the technical implementation guide +2. **Follow the manual testing scenarios** for validation +3. **Test with actual Reddit AMA threads** for real-world validation +4. **Verify performance under high-comment load** + +## Success Criteria + +### Technical Success ✅ +- [ ] Provider processes Reddit comments with <60 second delay +- [ ] Handles Reddit API rate limits gracefully +- [ ] Configuration UI validates inputs correctly +- [ ] No memory leaks during extended operation +- [ ] Proper error handling and logging throughout + +### User Experience Success ✅ +- [ ] Event organizers can easily configure AMA monitoring +- [ ] Comments appear in TagzApp waterfall with proper formatting +- [ ] Admin can moderate inappropriate comments using TagzApp tools +- [ ] Performance remains stable during high-activity AMAs +- [ ] Clear documentation enables self-service setup + +### Integration Success ✅ +- [ ] Follows existing TagzApp provider patterns consistently +- [ ] Integrates seamlessly with admin interface +- [ ] Works alongside other providers without conflicts +- [ ] Supports TagzApp's moderation and overlay features +- [ ] Maintains TagzApp's security and privacy standards + +## Risk Mitigation + +### Reddit API Dependencies +- **Risk**: Reddit API changes or becomes unavailable +- **Mitigation**: Error handling with graceful degradation, circuit breaker pattern + +### Performance Concerns +- **Risk**: High-traffic AMAs could overwhelm the system +- **Mitigation**: Configurable rate limiting, queue size limits, memory management + +### Configuration Complexity +- **Risk**: Users struggle with Reddit URL parsing and setup +- **Mitigation**: Automatic URL parsing, clear validation messages, comprehensive documentation + +## Post-Implementation Considerations + +### Monitoring and Maintenance +- Monitor Reddit API rate limits and adjust polling accordingly +- Track provider performance metrics (comments processed, memory usage) +- Review error logs for patterns requiring code improvements + +### Future Enhancements +- Support for monitoring multiple Reddit threads simultaneously +- Advanced comment threading visualization +- Integration with other TagzApp features (sentiment analysis, auto-moderation) +- Support for Reddit's real-time WebSocket API when available + +--- + +## Getting Started + +**For GitHub Agents**: Begin with Phase 1 implementation using the technical guide. All code patterns, API integration details, and testing requirements are fully specified in the documentation. + +**For Project Managers**: Use this roadmap to track progress through the 5-week implementation timeline. Each phase has clear deliverables and dependencies. + +**For Stakeholders**: The user configuration guide demonstrates the end-user value and use cases this provider will enable. + +--- + +*This roadmap serves as the central navigation document for the complete RedditAMA provider implementation. All technical details, UI specifications, and user guidance are contained in the linked documentation files.* \ No newline at end of file