269 lines
11 KiB
C#
Executable File
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; }
|
|
} |