MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../Plugins/GitHubRepositoryInfoPlugin.cs

269 lines
11 KiB
C#
Executable File

using MarketAlly.AIPlugin;
using System.Text.Json;
using System.Net.Http;
namespace MarketAlly.AIPlugin.Refactoring.Plugins;
[AIPlugin("github-repository-info", "Get comprehensive repository information from GitHub API including description, topics, and statistics")]
public class GitHubRepositoryInfoPlugin : IAIPlugin
{
[AIParameter("GitHub repository URL to get information for", required: true)]
public string RepositoryUrl { get; set; } = string.Empty;
[AIParameter("GitHub personal access token for private repos (optional)", required: false)]
public string? AccessToken { get; set; }
[AIParameter("Include detailed statistics like languages, contributors", required: false)]
public bool IncludeDetailedStats { get; set; } = true;
public IReadOnlyDictionary<string, Type> SupportedParameters => new Dictionary<string, Type>
{
["repositoryUrl"] = typeof(string),
["repositoryurl"] = typeof(string),
["accessToken"] = typeof(string),
["accesstoken"] = typeof(string),
["includeDetailedStats"] = typeof(bool),
["includedetailedstats"] = typeof(bool)
};
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> parameters)
{
try
{
var repositoryUrl = (parameters.ContainsKey("repositoryUrl") ? parameters["repositoryUrl"] :
parameters.ContainsKey("repositoryurl") ? parameters["repositoryurl"] : null)?.ToString();
var accessToken = parameters.ContainsKey("accessToken") ? parameters["accessToken"]?.ToString() :
parameters.ContainsKey("accesstoken") ? parameters["accesstoken"]?.ToString() : null;
var includeDetailedStats = parameters.ContainsKey("includeDetailedStats") ? Convert.ToBoolean(parameters["includeDetailedStats"]) :
parameters.ContainsKey("includedetailedstats") ? Convert.ToBoolean(parameters["includedetailedstats"]) : true;
// Parse GitHub URL to extract owner and repo
var (owner, repo) = ParseGitHubUrl(repositoryUrl);
if (string.IsNullOrEmpty(owner) || string.IsNullOrEmpty(repo))
{
return new AIPluginResult(new ArgumentException("Invalid GitHub repository URL"), "Failed to parse repository URL");
}
var repositoryInfo = await GetRepositoryInfoAsync(owner, repo, accessToken, includeDetailedStats);
return new AIPluginResult(repositoryInfo, "Repository information retrieved successfully");
}
catch (Exception ex)
{
return new AIPluginResult(ex, "Failed to retrieve repository information");
}
}
private (string owner, string repo) ParseGitHubUrl(string repositoryUrl)
{
try
{
if (!Uri.TryCreate(repositoryUrl, UriKind.Absolute, out var uri))
{
return (string.Empty, string.Empty);
}
if (!uri.Host.Equals("github.com", StringComparison.OrdinalIgnoreCase))
{
return (string.Empty, string.Empty);
}
var pathParts = uri.AbsolutePath.Trim('/').Split('/');
if (pathParts.Length >= 2)
{
var owner = pathParts[0];
var repo = pathParts[1].Replace(".git", "");
return (owner, repo);
}
return (string.Empty, string.Empty);
}
catch
{
return (string.Empty, string.Empty);
}
}
private async Task<GitHubRepositoryInfo> GetRepositoryInfoAsync(string owner, string repo, string? accessToken, bool includeDetailedStats)
{
using var httpClient = new HttpClient();
// Set up GitHub API headers
httpClient.DefaultRequestHeaders.Add("User-Agent", "MarketAlly-AIPlugin/1.0");
httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
if (!string.IsNullOrEmpty(accessToken))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
}
var repositoryInfo = new GitHubRepositoryInfo
{
Owner = owner,
Name = repo,
FullName = $"{owner}/{repo}"
};
try
{
// Get basic repository information
var repoResponse = await httpClient.GetStringAsync($"https://api.github.com/repos/{owner}/{repo}");
var repoData = JsonSerializer.Deserialize<JsonElement>(repoResponse);
// Extract basic information
repositoryInfo.Description = GetJsonProperty(repoData, "description");
repositoryInfo.Homepage = GetJsonProperty(repoData, "homepage");
repositoryInfo.Language = GetJsonProperty(repoData, "language");
repositoryInfo.License = GetJsonProperty(repoData.GetProperty("license"), "name");
repositoryInfo.DefaultBranch = GetJsonProperty(repoData, "default_branch") ?? "main";
repositoryInfo.IsPrivate = repoData.GetProperty("private").GetBoolean();
repositoryInfo.IsFork = repoData.GetProperty("fork").GetBoolean();
repositoryInfo.IsArchived = repoData.GetProperty("archived").GetBoolean();
repositoryInfo.StarCount = repoData.GetProperty("stargazers_count").GetInt32();
repositoryInfo.ForkCount = repoData.GetProperty("forks_count").GetInt32();
repositoryInfo.WatcherCount = repoData.GetProperty("watchers_count").GetInt32();
repositoryInfo.OpenIssuesCount = repoData.GetProperty("open_issues_count").GetInt32();
repositoryInfo.Size = repoData.GetProperty("size").GetInt64();
if (repoData.TryGetProperty("created_at", out var createdAt))
{
repositoryInfo.CreatedAt = DateTime.Parse(createdAt.GetString()!);
}
if (repoData.TryGetProperty("updated_at", out var updatedAt))
{
repositoryInfo.UpdatedAt = DateTime.Parse(updatedAt.GetString()!);
}
if (repoData.TryGetProperty("pushed_at", out var pushedAt))
{
repositoryInfo.LastPushAt = DateTime.Parse(pushedAt.GetString()!);
}
// Get topics/tags
try
{
var topicsResponse = await httpClient.GetStringAsync($"https://api.github.com/repos/{owner}/{repo}/topics");
var topicsData = JsonSerializer.Deserialize<JsonElement>(topicsResponse);
if (topicsData.TryGetProperty("names", out var topicsArray))
{
repositoryInfo.Topics = topicsArray.EnumerateArray()
.Select(t => t.GetString()!)
.Where(t => !string.IsNullOrEmpty(t))
.ToList();
}
}
catch
{
// Topics endpoint might not be available, continue without it
repositoryInfo.Topics = new List<string>();
}
if (includeDetailedStats)
{
// Get language statistics
try
{
var languagesResponse = await httpClient.GetStringAsync($"https://api.github.com/repos/{owner}/{repo}/languages");
var languagesData = JsonSerializer.Deserialize<JsonElement>(languagesResponse);
repositoryInfo.Languages = new Dictionary<string, long>();
foreach (var language in languagesData.EnumerateObject())
{
repositoryInfo.Languages[language.Name] = language.Value.GetInt64();
}
}
catch
{
repositoryInfo.Languages = new Dictionary<string, long>();
}
// Get contributors count
try
{
var contributorsResponse = await httpClient.GetStringAsync($"https://api.github.com/repos/{owner}/{repo}/contributors?per_page=1");
// Parse Link header to get total count if available
repositoryInfo.ContributorsCount = 1; // At least 1 if we get any response
}
catch
{
repositoryInfo.ContributorsCount = 0;
}
// Get README content
try
{
var readmeResponse = await httpClient.GetStringAsync($"https://api.github.com/repos/{owner}/{repo}/readme");
var readmeData = JsonSerializer.Deserialize<JsonElement>(readmeResponse);
if (readmeData.TryGetProperty("download_url", out var downloadUrl))
{
var readmeContent = await httpClient.GetStringAsync(downloadUrl.GetString()!);
repositoryInfo.ReadmeContent = readmeContent;
repositoryInfo.HasReadme = true;
}
}
catch
{
repositoryInfo.HasReadme = false;
}
}
repositoryInfo.RetrievedAt = DateTime.UtcNow;
return repositoryInfo;
}
catch (HttpRequestException ex) when (ex.Message.Contains("404"))
{
throw new ArgumentException($"Repository {owner}/{repo} not found or not accessible");
}
catch (HttpRequestException ex) when (ex.Message.Contains("403"))
{
throw new UnauthorizedAccessException($"Access denied to repository {owner}/{repo}. You may need to provide an access token.");
}
}
private string? GetJsonProperty(JsonElement element, string propertyName)
{
try
{
if (element.TryGetProperty(propertyName, out var property) && property.ValueKind != JsonValueKind.Null)
{
return property.GetString();
}
}
catch
{
// Property doesn't exist or can't be converted to string
}
return null;
}
}
// Data model for GitHub repository information
public class GitHubRepositoryInfo
{
public string Owner { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public string? Description { get; set; }
public string? Homepage { get; set; }
public string? Language { get; set; }
public string? License { get; set; }
public string DefaultBranch { get; set; } = "main";
public bool IsPrivate { get; set; }
public bool IsFork { get; set; }
public bool IsArchived { get; set; }
public bool HasReadme { get; set; }
public int StarCount { get; set; }
public int ForkCount { get; set; }
public int WatcherCount { get; set; }
public int OpenIssuesCount { get; set; }
public int ContributorsCount { get; set; }
public long Size { get; set; } // Size in KB
public int FileCount { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime? LastPushAt { get; set; }
public DateTime RetrievedAt { get; set; }
public List<string> Topics { get; set; } = new();
public Dictionary<string, long> Languages { get; set; } = new();
public string? ReadmeContent { get; set; }
}