-
Notifications
You must be signed in to change notification settings - Fork 17
feat(content): implement ModDB and CnCLabs content-pipeline integration #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Greptile OverviewGreptile SummaryThis PR successfully integrates ModDB and CnCLabs into the content pipeline, enabling discovery, resolution, and delivery of community content. The implementation follows established patterns with discoverers scraping listing pages, resolvers fetching detail pages, and manifest factories generating proper content manifests. Major Changes
Issues Found
Confidence Score: 4/5
Important Files ChangedFile Analysis
Sequence DiagramsequenceDiagram
participant User
participant Orchestrator as ContentOrchestrator
participant Provider as ContentProvider
participant Discoverer as IContentDiscoverer
participant Resolver as IContentResolver
participant Factory as ManifestFactory
participant Deliverer as IContentDeliverer
Note over User,Deliverer: Content Discovery & Resolution Flow
User->>Orchestrator: Request Content (query)
Orchestrator->>Provider: DiscoverAsync(query)
alt ModDB Content
Provider->>Discoverer: ModDBDiscoverer.DiscoverAsync()
Discoverer->>Discoverer: Scrape listing pages (mods/downloads/addons)
Discoverer->>Discoverer: Parse content items with metadata
Discoverer-->>Provider: Return ContentSearchResult[]
Provider-->>Orchestrator: Content items with sourceUrl
Orchestrator->>Resolver: ModDBResolver.ResolveAsync(item)
Resolver->>Resolver: Fetch detail page HTML
Resolver->>Resolver: ParseModDetailPageAsync()
Resolver->>Resolver: Extract metadata (author, date, download URL)
Resolver->>Factory: ModDBManifestFactory.CreateManifest()
Factory->>Factory: Generate manifest ID with date (1.YYYYMMDD.moddb-author...)
Factory->>Factory: Add dependencies and tags
Factory-->>Resolver: ContentManifest
Resolver-->>Orchestrator: ContentManifest
else CNCLabs Content
Provider->>Discoverer: CNCLabsMapDiscoverer.DiscoverAsync()
Discoverer->>Discoverer: Build search URL with filters
Discoverer->>Discoverer: Parse map listings
Discoverer-->>Provider: Return ContentSearchResult[]
Provider-->>Orchestrator: Content items with mapId metadata
Orchestrator->>Resolver: CNCLabsMapResolver.ResolveAsync(item)
Resolver->>Resolver: Fetch detail page HTML
Resolver->>Resolver: ParseMapDetailPageAsync()
Resolver->>Resolver: Extract breadcrumb category
Resolver->>Factory: CNCLabsManifestFactory.CreateManifest()
Factory->>Factory: Generate manifest ID (1.0.cnclabs-author...)
Factory-->>Resolver: ContentManifest
Resolver-->>Orchestrator: ContentManifest
end
Orchestrator->>Deliverer: DeliverAsync(manifest)
Deliverer->>Deliverer: Download and extract content
Deliverer->>Deliverer: Store in CAS
Deliverer-->>Orchestrator: Delivery result
Orchestrator-->>User: Content ready for installation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 files reviewed, 1 comment
GenHub/GenHub/Features/Content/Services/ContentResolvers/ModDBResolver.cs
Outdated
Show resolved
Hide resolved
…Resolver.cs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 files reviewed, 3 comments
| _logger.LogWarning("Could not extract submission date, using current date"); | ||
| return DateTime.UtcNow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: using current date as fallback could cause issues with manifest ID uniqueness and versioning - multiple pieces of content without dates would get the same daily timestamp
Prompt To Fix With AI
This is a comment left during a code review.
Path: GenHub/GenHub/Features/Content/Services/ContentResolvers/ModDBResolver.cs
Line: 218:219
Comment:
**logic:** using current date as fallback could cause issues with manifest ID uniqueness and versioning - multiple pieces of content without dates would get the same daily timestamp
How can I resolve this? If you propose a fix, please make it concise.| manifest.AddRemoteFileAsync( | ||
| fileName, | ||
| details.DownloadUrl, | ||
| ContentSourceType.RemoteDownload).GetAwaiter().GetResult(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: .GetAwaiter().GetResult() blocks async execution - consider making CreateManifest async or restructuring to avoid blocking
Prompt To Fix With AI
This is a comment left during a code review.
Path: GenHub/GenHub/Features/Content/Services/Publishers/ModDBManifestFactory.cs
Line: 142:145
Comment:
**style:** `.GetAwaiter().GetResult()` blocks async execution - consider making `CreateManifest` async or restructuring to avoid blocking
How can I resolve this? If you propose a fix, please make it concise.| return 0; | ||
| } | ||
|
|
||
| var parts = sizeText.Trim().Split(new[] { ' ', ',', '.', }, StringSplitOptions.RemoveEmptyEntries); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: splitting on periods could incorrectly parse file sizes like "2.5 MB" - the numeric part 2 and unit 5 would be separated
Prompt To Fix With AI
This is a comment left during a code review.
Path: GenHub/GenHub/Features/Content/Services/ContentResolvers/ModDBResolver.cs
Line: 264:264
Comment:
**logic:** splitting on periods could incorrectly parse file sizes like "2.5 MB" - the numeric part `2` and unit `5` would be separated
How can I resolve this? If you propose a fix, please make it concise.| var detailUrl = href.StartsWith("http") ? href : ModDBConstants.BaseUrl + href; | ||
|
|
||
| // Extract author | ||
| var authorLink = item.QuerySelector("a[href*='/members/'], a[href*='/company/']"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems familiar to some of the constants I've seen. Do you plan on using such constants here as well?
| /// Parses HTML detail pages and generates content manifests. | ||
| /// </summary> | ||
| public class CNCLabsMapResolver(HttpClient httpClient, IContentManifestBuilder manifestBuilder, ILogger<CNCLabsMapResolver> logger) : IContentResolver | ||
| public partial class CNCLabsMapResolver( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a partial class now?
Usually that happens by accident in my experience. Did it?
| /// <summary> | ||
| /// Parses the HTML detail page for a ModDB mod and extracts all relevant details. | ||
| /// </summary> | ||
| private async Task<GenHub.Core.Models.ModDB.MapDetails> ParseModDetailPageAsync( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using?
| // Ensure absolute URL | ||
| if (!string.IsNullOrEmpty(downloadUrl) && !downloadUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| downloadUrl = ModDBConstants.BaseUrl + downloadUrl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure you've tested this, but I just wanted to make sure: you're not missing any slashes in between, right?
I'm used to seeing URL things like $"{baseUrl}/{downloadUrl} for example. Again, no criticism, just making sure.
| return 0; | ||
| } | ||
|
|
||
| return unit switch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen this a couple of times as well. Maybe that applies to the whole function. Do you need this to be duplicated?
| /// <param name="Rating">Content rating.</param> | ||
| /// <param name="TargetGame">Target game (Generals or Zero Hour).</param> | ||
| /// <param name="ContentType">Content type (Map or Mission).</param> | ||
| public record MapDetails( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a second record MapDetails? They're 90% the same, why not just allow the other two to be null and check inside of the one that allows no nulls on it whether they are or not?
Or, at least change the name to reflect the different implementations.
| manifest.AddRemoteFileAsync( | ||
| fileName, | ||
| details.DownloadUrl, | ||
| ContentSourceType.RemoteDownload).GetAwaiter().GetResult(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't this be awaited? I get that the method is synchronous, and the interface probably forces this, but just make it all async and do return Task.CompletedTask from the classes that don't need it.
| services.AddHttpClient(ModDBConstants.PublisherPrefix, static httpClient => | ||
| { | ||
| httpClient.Timeout = TimeSpan.FromSeconds(45); // ModDB can be slower | ||
| httpClient.DefaultRequestHeaders.Add("User-Agent", "GenHub/1.0"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the GenHub version should be a constant, so that you can update it in one go. But that's up to you.
| // Register named HTTP client for ModDB | ||
| services.AddHttpClient(ModDBConstants.PublisherPrefix, static httpClient => | ||
| { | ||
| httpClient.Timeout = TimeSpan.FromSeconds(45); // ModDB can be slower |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad to see you went with this route this time! Makes me happy :)
This PR integrates ModDBContentProvider and CnCLabsContentProvider for discovering, delivering, resolving content and generating a
ContentManifest