From 2ad24ee6b833c977516ac61246891814b2070dc1 Mon Sep 17 00:00:00 2001 From: Kim <71221724+liebki@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:07:24 +0100 Subject: [PATCH] Reworked everything, new methods, new types, new structure and new README.. --- GithubNet/GithubNet.csproj | 14 +- GithubNet/GithubNetClient.cs | 55 -- GithubNet/GithubNetManager.cs | 275 ---------- GithubNet/Managers/GithubNetClient.cs | 136 +++++ GithubNet/Managers/GithubNetManager.cs | 485 ++++++++++++++++++ GithubNet/Managers/UtilManager.cs | 153 ++++++ GithubNet/Models/Extra/TopValues.cs | 24 + GithubNet/Models/ItemBase.cs | 65 --- .../Models/Repositories/FullRepository.cs | 56 ++ .../Models/Repositories/RepositoryBase.cs | 32 ++ .../Models/Repositories/TrendRepository.cs | 20 + .../Models/Repositories/UserRepository.cs | 27 + GithubNet/Models/Repository.cs | 38 -- GithubNet/Models/TrendItem.cs | 36 -- .../Models/Userprofiles/FullUserprofile.cs | 29 ++ .../Models/Userprofiles/LightUserprofile.cs | 50 ++ GithubNetDemo/GithubNetDemo.csproj | 2 + GithubNetDemo/Program.cs | 99 +++- README.md | 113 ++-- 19 files changed, 1171 insertions(+), 538 deletions(-) delete mode 100644 GithubNet/GithubNetClient.cs delete mode 100644 GithubNet/GithubNetManager.cs create mode 100644 GithubNet/Managers/GithubNetClient.cs create mode 100644 GithubNet/Managers/GithubNetManager.cs create mode 100644 GithubNet/Managers/UtilManager.cs create mode 100644 GithubNet/Models/Extra/TopValues.cs delete mode 100644 GithubNet/Models/ItemBase.cs create mode 100644 GithubNet/Models/Repositories/FullRepository.cs create mode 100644 GithubNet/Models/Repositories/RepositoryBase.cs create mode 100644 GithubNet/Models/Repositories/TrendRepository.cs create mode 100644 GithubNet/Models/Repositories/UserRepository.cs delete mode 100644 GithubNet/Models/Repository.cs delete mode 100644 GithubNet/Models/TrendItem.cs create mode 100644 GithubNet/Models/Userprofiles/FullUserprofile.cs create mode 100644 GithubNet/Models/Userprofiles/LightUserprofile.cs diff --git a/GithubNet/GithubNet.csproj b/GithubNet/GithubNet.csproj index 253b741..e65f3b2 100644 --- a/GithubNet/GithubNet.csproj +++ b/GithubNet/GithubNet.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -6,19 +6,19 @@ enable True GithubNet - 0.2 + 0.3 liebki - This library allows you to retrieve trending GitHub repositories and their information. + Github(Data)Net is a simple C# library, using HtmlAgilityPack to retrieve several things from GitHub, things like trending repositories, profiles of users, the repositories of users and related information. liebki github.com/liebki/githubnet README.md - github.com/liebki/githubnet - github, crawler, github-trends, crawling, microsoft, git + https://github.com/liebki/githubnet + github, crawler, github-trends, crawling, microsoft, git, htmlagilitypack LICENSE.txt True icon.png GithubDataNet - The update includes a new GetRepositoryInfoAsync() method for retrieving detailed information about GitHub repositories, a 'Repository' class to support it, and improved data types (TotalStars and TotalForks changed to integers). + Reworked everything, new methods, new types, new structure and new README.. @@ -38,7 +38,7 @@ - + diff --git a/GithubNet/GithubNetClient.cs b/GithubNet/GithubNetClient.cs deleted file mode 100644 index a161acb..0000000 --- a/GithubNet/GithubNetClient.cs +++ /dev/null @@ -1,55 +0,0 @@ -using GithubNet.Models; - -namespace GithubNet -{ - public class GithubNetClient - { - public async Task> GetTrendItemsAsync(bool loadTrendItemDetails = false, string customQuery = "https://github.com/trending") - { - if (!string.IsNullOrEmpty(customQuery) && customQuery.Contains("https://github.com/trending", StringComparison.InvariantCultureIgnoreCase)) - { - return await GithubNetManager.GetAllTrendEntries(loadTrendItemDetails, customQuery); - } - else - { - throw new ArgumentException("The parameter customquery, can't be whitespace, null or empty also it has to contain some kind of https://github.com/trending-url string!"); - } - } - - public async Task GetRepositoryInfoAsync(string repositoryLink) - { - if (!string.IsNullOrEmpty(repositoryLink) && repositoryLink.StartsWith("https://github.com/", StringComparison.InvariantCultureIgnoreCase)) - { - return await GithubNetManager.GetRepositoryInfo(repositoryLink); - } - else - { - throw new ArgumentException("The parameter customquery, can't be whitespace, null or empty also it has to contain some kind of https://github.com/trending-url string!"); - } - } - - public async Task GetTrendItemDetailsAsync(TrendItem entryItem) - { - if (entryItem != null && !string.IsNullOrEmpty(entryItem.RespositoryLink) && !string.IsNullOrWhiteSpace(entryItem.RespositoryLink)) - { - return await GithubNetManager.GetTrendDetails(entryItem); - } - else - { - throw new ArgumentException("The parameter entryItem, can't be null also the RespositoryLink inside entryItem, has to contain a value and may not be null or empty!"); - } - } - - public string GetTopicUrlFromTopicName(string topicName) - { - if (!string.IsNullOrEmpty(topicName) && !string.IsNullOrWhiteSpace(topicName)) - { - return GithubNetManager.ParseTopicNameToUrl(topicName); - } - else - { - throw new ArgumentException("The parameter topicName, can't be whitespace, null or empty, it has to contain some kind of value!"); - } - } - } -} \ No newline at end of file diff --git a/GithubNet/GithubNetManager.cs b/GithubNet/GithubNetManager.cs deleted file mode 100644 index 345cc54..0000000 --- a/GithubNet/GithubNetManager.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System.Net; -using System.Text; -using HtmlAgilityPack; -using GithubNet.Models; - -namespace GithubNet -{ - internal static class GithubNetManager - { - internal static async Task> GetAllTrendEntries(bool loadTrendItemDetails = false, string customQuery = "https://github.com/trending") - { - List TrendingRepositoriesList = new(); - HtmlWeb Github = new(); - - HtmlDocument GithubDoc = Github.Load(customQuery); - IEnumerable nodes = GithubDoc.QuerySelectorAll("article.Box-row"); - - foreach (HtmlNode productElement in nodes) - { - try - { - HtmlNode Description = productElement.QuerySelector("article.Box-row > p"); - HtmlNode Username = productElement.QuerySelector("article.Box-row span.text-normal"); - - HtmlNode RepositoryLink = productElement.QuerySelector("article.Box-row > h2 > a"); - IList TotalForksAndStars = productElement.QuerySelectorAll("article.Box-row > div > a.Link--muted"); - - string DescriptionFiltered = string.Empty; - if (Description != null) - { - DescriptionFiltered = FilterLineBreaks(Description.InnerText); - DescriptionFiltered = WebUtility.HtmlDecode(DescriptionFiltered); - } - string UsernameFiltered = FilterLineBreaks(Username.InnerText.Replace(" /", string.Empty)); - - string RepositoryLinkFiltered = $"https://github.com{RepositoryLink.GetAttributeValue("href", string.Empty)}"; - string[] RepositoryNameParse = RepositoryLinkFiltered.Split('/'); - - string RepositoryName = "An error occured"; - - if (RepositoryNameParse?.Length >= 2) - { - RepositoryName = RepositoryNameParse[4]; - } - - string TotalStars = FilterLineBreaks(TotalForksAndStars[0].InnerText); - int.TryParse(TotalStars.Replace(",", string.Empty), out int TotalStarsFiltered); - - string TotalForks = FilterLineBreaks(TotalForksAndStars[1].InnerText); - int.TryParse(TotalForks.Replace(",", string.Empty), out int TotalForksFiltered); - - string ProgrammingLanguage = "None"; - if (productElement.QuerySelectorAll("article.Box-row > div > span > span") != null) - { - IList ProgrammingLanguageElementCount = productElement.QuerySelectorAll("article.Box-row > div > span > span"); - if (ProgrammingLanguageElementCount.Count > 0) - { - ProgrammingLanguage = ProgrammingLanguageElementCount[1].InnerText; - } - } - - if (loadTrendItemDetails) - { - TrendItem trendItem = new(ProgrammingLanguage, false, false, string.Empty, string.Empty, UsernameFiltered, RepositoryLinkFiltered, RepositoryName, DescriptionFiltered, TotalStarsFiltered, TotalForksFiltered, false, string.Empty, false, Array.Empty()); - TrendingRepositoriesList.Add(await GetTrendDetails(trendItem)); - } - else - { - TrendingRepositoriesList.Add(new(ProgrammingLanguage, false, false, string.Empty, string.Empty, UsernameFiltered, RepositoryLinkFiltered, RepositoryName, DescriptionFiltered, TotalStarsFiltered, TotalForksFiltered, false, string.Empty, false, Array.Empty())); - } - } - catch (Exception) - { - //Skip TrendItem if corrupt - } - } - - return TrendingRepositoriesList; - } - - private static async Task FillBaseData(HtmlNode node, string repositoryUrl) - { - HtmlNode Username = node.SelectSingleNode("/html/body/div[1]/div[4]/div/main/div/div[1]/div[1]/div/span[1]/a"); - HtmlNode RepositoryName = node.SelectSingleNode("/html/body/div[1]/div[4]/div/main/div/div[1]/div[1]/div/strong/a"); - - HtmlNode Description = node.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[1]/div/p"); - HtmlNode TotalForks = node.SelectSingleNode("//*[@id=\"repo-network-counter\"]"); - - HtmlNode TotalStars = node.SelectSingleNode("//*[@id=\"repo-stars-counter-star\"]"); - HtmlNode ProjectUrl = node.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[1]/div/div[1]/span/a"); - - HtmlNode Topics = node.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[1]/div/div[2]/div"); - - string DescriptionFiltered = FilterLineBreaks(Description.InnerText); - if (!string.IsNullOrWhiteSpace(DescriptionFiltered)) - { - DescriptionFiltered = WebUtility.HtmlDecode(DescriptionFiltered); - } - - List TopicsFiltered = new(); - bool HasTopics = false; - - if (Topics != null) - { - HasTopics = true; - IList TopicsValues = Topics.QuerySelectorAll("a.topic-tag"); - - foreach (HtmlNode t in TopicsValues) - { - TopicsFiltered.Add(FilterLineBreaks(t.InnerHtml)); - } - } - - string ProjectUrlFiltered = string.Empty; - bool HasProjectUrl = false; - - if (ProjectUrl != null) - { - HasProjectUrl = true; - ProjectUrlFiltered = ProjectUrl.GetAttributeValue("href", string.Empty); - } - - string ForksValue = TotalForks.GetAttributeValue("title", string.Empty); - int.TryParse(ForksValue.Replace(",", string.Empty), out int ForksFiltered); - - string StarsValue = TotalStars.GetAttributeValue("title", string.Empty); - int.TryParse(StarsValue.Replace(",", string.Empty), out int StarsFiltered); - - string UsernameFiltered = FilterLineBreaks(Username.InnerText.Replace(" /", string.Empty)); - string RepositoryNameFiltered = FilterLineBreaks(RepositoryName.InnerText.Replace(" /", string.Empty)); - - return new(UsernameFiltered, repositoryUrl, RepositoryNameFiltered, DescriptionFiltered, StarsFiltered, ForksFiltered, HasProjectUrl, ProjectUrlFiltered, HasTopics, TopicsFiltered.ToArray()); - } - - internal static async Task GetRepositoryInfo(string repositoryLink) - { - HtmlWeb CommitUrl = new(); - HtmlDocument TrendingRep = CommitUrl.Load(repositoryLink); - - ItemBase baseData = await FillBaseData(TrendingRep.DocumentNode, repositoryLink); - HtmlNode OpenIssuesNumber = TrendingRep.DocumentNode.SelectSingleNode("//*[@id=\"issues-repo-tab-count\"]"); - - HtmlNode OpenPullRequestsNumber = TrendingRep.DocumentNode.SelectSingleNode("//*[@id=\"pull-requests-repo-tab-count\"]"); - HtmlNode TotalCommitsNumber = TrendingRep.DocumentNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[2]/div[1]/div/div[4]/ul/li/a/span/strong"); - - HtmlNode TotalContributorsNumber = TrendingRep.DocumentNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[5]/div/h2/a/span"); - - string OpenIssuesNumberFiltered = FilterLineBreaks(OpenIssuesNumber.InnerHtml); - int.TryParse(OpenIssuesNumberFiltered.Replace(",", string.Empty), out int OpenIssuesNumberValue); - - string OpenPullRequestsNumberFiltered = FilterLineBreaks(OpenPullRequestsNumber.InnerHtml); - int.TryParse(OpenPullRequestsNumberFiltered.Replace(",", string.Empty), out int OpenPullRequestsNumberValue); - - string TotalCommitsNumberFiltered = FilterLineBreaks(TotalCommitsNumber.InnerText); - int.TryParse(TotalCommitsNumberFiltered.Replace(",", string.Empty), out int TotalCommitsNumberValue); - - int TotalContributorsNumberValue = 0; - if (TotalContributorsNumber != null) - { - string TotalContributorsNumberFiltered = FilterLineBreaks(TotalContributorsNumber.InnerHtml); - int.TryParse(TotalContributorsNumberFiltered.Replace(",", string.Empty), out TotalContributorsNumberValue); - } - - return new Repository(OpenIssuesNumberValue, OpenPullRequestsNumberValue, TotalCommitsNumberValue, TotalContributorsNumberValue, baseData.User, baseData.RespositoryLink, baseData.RespositoryName, baseData.Description, baseData.TotalStars, baseData.TotalForks, baseData.HasProjectUrl, baseData.ProjectUrl, baseData.HasTopics, baseData.Topics); - } - - internal static async Task GetTrendDetails(TrendItem entryItem) - { - entryItem.HasDetails = true; - HtmlWeb TrendingRepository = new(); - - HtmlDocument TrendingRep = TrendingRepository.Load(entryItem.RespositoryLink); - HtmlNode ArchiveStatus = TrendingRep.DocumentNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/div[1]"); - - HtmlNode ProjectUrl = TrendingRep.QuerySelector("div.my-3:nth-child(3) > span:nth-child(2) > a:nth-child(1)"); - IList Topics = TrendingRep.QuerySelectorAll("a.topic-tag"); - - if (ArchiveStatus?.InnerHtml.Contains("This repository has been archived by the owner on", StringComparison.InvariantCultureIgnoreCase) == true) - { - entryItem.IsArchived = true; - } - - if (ProjectUrl != null) - { - entryItem.HasProjectUrl = true; - entryItem.ProjectUrl = ProjectUrl.GetAttributeValue("href", string.Empty); - } - - if (Topics.Count > 0) - { - List TopicNames = new(); - foreach (HtmlNode TopicItem in Topics) - { - TopicNames.Add(FilterLineBreaks(TopicItem.InnerHtml)); - } - entryItem.Topics = TopicNames.ToArray(); - entryItem.HasTopics = true; - } - else - { - entryItem.Topics = Array.Empty(); - } - - entryItem.LastCommitTime = $"Last commit: {GetLastCommitTime(entryItem)}"; - return entryItem; - } - - private static string GetLastCommitTime(TrendItem entry) - { - string LastCommitTime = "Error"; - string CommitTime = FetchLastCommitTime(entry, "master"); - - if (string.IsNullOrEmpty(CommitTime)) - { - CommitTime = FetchLastCommitTime(entry, "main"); - if (!string.IsNullOrEmpty(CommitTime)) - { - entry.LastCommitUrl = entry.GetLastCommitUrl("main"); - LastCommitTime = CommitTime; - } - } - else - { - entry.LastCommitUrl = entry.GetLastCommitUrl("master"); - LastCommitTime = CommitTime; - } - - return LastCommitTime; - } - - private static string FetchLastCommitTime(TrendItem entry, string codeBranch) - { - HtmlWeb CommitUrl = new(); - HtmlDocument TrendingRep = CommitUrl.Load(entry.GetLastCommitUrl(codeBranch)); - - HtmlNode LastCommitTimeRaw = TrendingRep.QuerySelector("relative-time.no-wrap"); - - if (LastCommitTimeRaw?.InnerHtml.Length > 0) - { - return FilterLineBreaks(LastCommitTimeRaw.InnerHtml); - } - return null; - } - - private static string FilterLineBreaks(string textIn) - { - StringBuilder sb = new(textIn.Length); - foreach (char i in textIn) - { - if (i != '\n' && i != '\r' && i != '\t') - { - sb.Append(i); - } - } - return sb.ToString().Trim(); - } - - public static string ParseTopicNameToUrl(string topicNameIn) - { - string LanguageName = NormalizeLanguageNameIdentifier(topicNameIn); - return $"https://github.com/topics/{LanguageName}"; - } - - private static string NormalizeLanguageNameIdentifier(string topicNameIn) - { - string normalized = topicNameIn.Replace(" ", "-").ToLowerInvariant(); - return normalized switch - { - "c#" => "csharp", - "c++" => "cpp", - _ => normalized, - }; - } - } -} \ No newline at end of file diff --git a/GithubNet/Managers/GithubNetClient.cs b/GithubNet/Managers/GithubNetClient.cs new file mode 100644 index 0000000..4556008 --- /dev/null +++ b/GithubNet/Managers/GithubNetClient.cs @@ -0,0 +1,136 @@ +using GithubNet.Models.Extra; +using GithubNet.Models.Repositories; +using GithubNet.Models.Userprofiles; + +namespace GithubNet.Managers +{ + public class GithubNetClient + { + #region Trends + public IEnumerable GetAllTrendingRepositories(string customQuery = "https://github.com/trending") + { + if (!string.IsNullOrEmpty(customQuery) && customQuery.Contains("https://github.com/trending", StringComparison.InvariantCultureIgnoreCase)) + { + return GithubNetManager.GetAllTrendRepositories(customQuery); + } + else + { + throw new ArgumentException("The parameter customquery, can't be whitespace, null or empty also it has to contain some kind of https://github.com/trending string!"); + } + } + + public IEnumerable GetUrlsOfTrendingRepositories(string customQuery = "https://github.com/trending") + { + if (!string.IsNullOrEmpty(customQuery) && customQuery.Contains("https://github.com/trending", StringComparison.InvariantCultureIgnoreCase)) + { + return GithubNetManager.GetAllTrendRepositoryUrls(customQuery); + } + else + { + throw new ArgumentException("The parameter customquery, can't be whitespace, null or empty also it has to contain some kind of https://github.com/trending string!"); + } + } + + public IEnumerable GetFullTrendingRepositories(string customQuery = "https://github.com/trending") + { + if (!string.IsNullOrEmpty(customQuery) && customQuery.Contains("https://github.com/trending", StringComparison.InvariantCultureIgnoreCase)) + { + return GithubNetManager.GetAllFullRepositoriesFromTrends(customQuery); + } + else + { + throw new ArgumentException("The parameter customquery, can't be whitespace, null or empty also it has to contain some kind of https://github.com/trending string!"); + } + } + + #endregion Trends + + #region Userprofile + + public FullUserprofile GetFullUserprofile(string Username) + { + if (!string.IsNullOrEmpty(Username)) + { + return GithubNetManager.GetFullUserprofile(Username); + } + else + { + throw new ArgumentException("The parameter Username, can't be whitespace, null or empty!"); + } + } + + public LightUserprofile GetLightUserprofile(string Username) + { + if (!string.IsNullOrEmpty(Username)) + { + return GithubNetManager.GetLightUserprofile(Username); + } + else + { + throw new ArgumentException("The parameter Username, can't be whitespace, null or empty!"); + } + } + + #endregion Userprofile + + #region FullRepository + + public IEnumerable GetFullRepositories(string Username) + { + if (!string.IsNullOrEmpty(Username)) + { + return GithubNetManager.GetFullRepositories(Username); + } + else + { + throw new ArgumentException($"The parameter {nameof(Username)}, can't be whitespace, null or empty!"); + } + } + + public FullRepository GetFullRepository(string RepositoryUrl) + { + if (!string.IsNullOrEmpty(RepositoryUrl) && RepositoryUrl.Contains("https://github.com", StringComparison.InvariantCultureIgnoreCase)) + { + return GithubNetManager.GetFullRepository(RepositoryUrl); + } + else + { + throw new ArgumentException($"The parameter {nameof(RepositoryUrl)}, can't be whitespace, null or empty also it has to contain some kind of https://github.com string!"); + } + } + + #endregion FullRepository + + #region LightUserprofile & UserRepository + + public (LightUserprofile Userprofile, IEnumerable UserRepositories, TopValues topValues) GetLightUserprofileWithRepositories(string Username) + { + if (!string.IsNullOrEmpty(Username)) + { + return GithubNetManager.GetLightUserprofileWithRepositories(Username); + } + else + { + throw new ArgumentException("The parameter Username, can't be whitespace, null or empty!"); + } + } + + #endregion LightUserprofile & UserRepository + + #region Utils + + public string GetTopicUrlFromTopicName(string topicName) + { + if (!string.IsNullOrEmpty(topicName) && !string.IsNullOrWhiteSpace(topicName)) + { + return UtilManager.ParseTopicNameToUrl(topicName); + } + else + { + throw new ArgumentException("The parameter topicName, can't be whitespace, null or empty, it has to contain some kind of value!"); + } + } + + #endregion Utils + } +} \ No newline at end of file diff --git a/GithubNet/Managers/GithubNetManager.cs b/GithubNet/Managers/GithubNetManager.cs new file mode 100644 index 0000000..5d5440f --- /dev/null +++ b/GithubNet/Managers/GithubNetManager.cs @@ -0,0 +1,485 @@ +using GithubNet.Models.Extra; +using GithubNet.Models.Repositories; +using GithubNet.Models.Userprofiles; +using HtmlAgilityPack; +using System; +using System.Net; + +namespace GithubNet.Managers +{ + public static class GithubNetManager + { + + #region GetTrends + + internal static List GetAllTrendRepositoryUrls(string CustomTrendingQuery) + { + List TrendRepositoryUrlList = new(); + HtmlDocument GithubPageDocument = UtilManager.GetHtmlDoc(CustomTrendingQuery, true); + + IEnumerable DocumentNodes = GithubPageDocument.QuerySelectorAll("article.Box-row"); + foreach (HtmlNode docNode in DocumentNodes) + { + HtmlNode RepositoryLink = docNode.QuerySelector("article.Box-row > h2 > a"); + string RepositoryLinkFiltered = $"https://github.com{RepositoryLink.GetAttributeValue("href", string.Empty)}"; + + TrendRepositoryUrlList.Add(RepositoryLinkFiltered); + } + + return TrendRepositoryUrlList; + } + + internal static List GetAllFullRepositoriesFromTrends(string CustomTrendingQuery) + { + List TrendingRepositoriesList = GetAllTrendRepositoryUrls(CustomTrendingQuery); + List FullTrendingRepositoriesList = new(); + + foreach (string repoUrl in TrendingRepositoriesList) + { + FullRepository fullRepository = GetFullRepository(repoUrl); + FullTrendingRepositoriesList.Add(fullRepository); + } + + return FullTrendingRepositoriesList; + } + + internal static List GetAllTrendRepositories(string CustomTrendingQuery = "https://github.com/trending") + { + List TrendingRepositoriesList = new(); + HtmlDocument GithubPageDocument = UtilManager.GetHtmlDoc(CustomTrendingQuery, true); + + IEnumerable DocumentNodes = GithubPageDocument.QuerySelectorAll("article.Box-row"); + foreach (HtmlNode docNode in DocumentNodes) + { + try + { + HtmlNode Description = docNode.QuerySelector("article.Box-row > p"); + HtmlNode Username = docNode.QuerySelector("article.Box-row span.text-normal"); + + HtmlNode RepositoryLink = docNode.QuerySelector("article.Box-row > h2 > a"); + IList TotalForksAndStars = docNode.QuerySelectorAll("article.Box-row > div > a.Link--muted"); + + HtmlNode StarsTodayRaw = docNode.SelectSingleNode(".//span[@class='d-inline-block float-sm-right']"); + string StarsTodayRawValue = UtilManager.ClearStrings(StarsTodayRaw.InnerText.Replace(" ", string.Empty).Replace(",", string.Empty).Replace("starstoday", string.Empty)); + + _ = int.TryParse(StarsTodayRawValue, out int StarsTodayFiltered); + + string DescriptionFiltered = string.Empty; + + if (Description != null) + { + DescriptionFiltered = UtilManager.ClearStrings(Description.InnerText); + DescriptionFiltered = WebUtility.HtmlDecode(DescriptionFiltered); + } + string UsernameFiltered = UtilManager.ClearStrings(Username.InnerText.Replace(" /", string.Empty)); + + string RepositoryLinkFiltered = $"https://github.com{RepositoryLink.GetAttributeValue("href", string.Empty)}"; + string[] RepositoryNameParse = RepositoryLinkFiltered.Split('/'); + + string RepositoryName = "An error occured"; + + if (RepositoryNameParse?.Length >= 2) + { + RepositoryName = RepositoryNameParse[4]; + } + + string TotalStars = UtilManager.ClearStrings(TotalForksAndStars[0].InnerText); + _ = int.TryParse(TotalStars.Replace(",", string.Empty), out int TotalStarsFiltered); + + string TotalForks = UtilManager.ClearStrings(TotalForksAndStars[1].InnerText); + _ = int.TryParse(TotalForks.Replace(",", string.Empty), out int TotalForksFiltered); + + string ProgrammingLanguage = "None"; + if (docNode.QuerySelectorAll("article.Box-row > div > span > span") != null) + { + IList ProgrammingLanguageElementCount = docNode.QuerySelectorAll("article.Box-row > div > span > span"); + if (ProgrammingLanguageElementCount.Count > 0) + { + ProgrammingLanguage = ProgrammingLanguageElementCount[1].InnerText; + } + } + + TrendRepository repository = new(TotalStarsFiltered, ProgrammingLanguage, TotalForksFiltered, StarsTodayFiltered, UsernameFiltered, RepositoryName, DescriptionFiltered); + TrendingRepositoriesList.Add(repository); + } + catch (Exception e) + { + // Skip item if corrupted + } + } + + return TrendingRepositoriesList; + } + + #endregion GetTrends + + #region Userprofile + + internal static LightUserprofile GetLightUserprofile(string Username) + { + (LightUserprofile profileObj, _) = GetUserprofileData(Username); + return profileObj; + } + + internal static FullUserprofile GetFullUserprofile(string Username) + { + (LightUserprofile profileObj, HtmlDocument rawProfileData) = GetUserprofileData(Username); + + HtmlNode LastYearContributionsCount = rawProfileData.QuerySelector(".js-yearly-contributions > div:nth-child(1) > h2"); + int LastYearContributionsCountValue = 0; + + if (LastYearContributionsCount != null) + { + string LastYearContributionsCountRaw = UtilManager.ClearStrings(LastYearContributionsCount.InnerHtml, true, " contributions in the last year"); + LastYearContributionsCountValue = UtilManager.ParseNumberValue(LastYearContributionsCountRaw); + } + + HtmlNode HasSpecialUserReadmeEnabled = rawProfileData.QuerySelector(".text-mono"); + bool HasSpecialUserReadmeEnabledValue = false; + + if (HasSpecialUserReadmeEnabled != null) + { + HasSpecialUserReadmeEnabledValue = true; + } + + FullUserprofile fullUserprofile = new(profileObj, LastYearContributionsCountValue, HasSpecialUserReadmeEnabledValue); + return fullUserprofile; + + } + + #endregion Userprofile + + #region LightUserprofile & UserRepository + + internal static (LightUserprofile Userprofile, IEnumerable UserRepositories, TopValues topValues) GetLightUserprofileWithRepositories(string Username) + { + (LightUserprofile profileObj, _) = GetUserprofileData(Username); + LightUserprofile lightUserprofile = profileObj; + + HtmlDocument UserRepositoriesDocument = UtilManager.GetHtmlDoc($"https://github.com/{Username}?tab=repositories", true); + HtmlNode UserRepositoriesNode = UserRepositoriesDocument.DocumentNode; + + IEnumerable RawUserRepositoriesList = UserRepositoriesNode.QuerySelectorAll("li.col-12"); + List UserRepositoriesList = new(); + + foreach (HtmlNode repo in RawUserRepositoriesList) + { + HtmlNode nameNode = repo.SelectSingleNode(".//h3/a"); + string name = (nameNode != null && !string.IsNullOrEmpty(nameNode.InnerText.Trim())) ? nameNode.InnerText.Trim() : "None"; + + HtmlNode descriptionNode = repo.SelectSingleNode(".//p[@itemprop='description']"); + string description = (descriptionNode != null && !string.IsNullOrEmpty(descriptionNode.InnerText.Trim())) ? descriptionNode.InnerText.Trim() : "None"; + + HtmlNode languageNode = repo.SelectSingleNode(".//span[contains(@class, 'repo-language-color')]/following-sibling::span"); + string language = (languageNode != null && !string.IsNullOrEmpty(languageNode.InnerText.Trim())) ? languageNode.InnerText.Trim() : "None"; + + HtmlNode forkedTextNode = repo.SelectSingleNode(".//span[contains(@class, 'color-fg-muted') and contains(., 'Forked from')]"); + bool isFork = (forkedTextNode != null); + + HtmlNode starCountNode = repo.SelectSingleNode(".//a[contains(@href, '/stargazers')]"); + int starCountValue = 0; + if (starCountNode != null && !string.IsNullOrEmpty(starCountNode.InnerText.Trim())) + { + starCountValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(starCountNode.InnerText.Trim())); + } + + HtmlNode forkCountNode = repo.SelectSingleNode(".//a[contains(@href, '/forks')]"); + int forkCountValue = 0; + if (forkCountNode != null && !string.IsNullOrEmpty(forkCountNode.InnerText.Trim())) + { + forkCountValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(forkCountNode.InnerText.Trim())); + } + + HtmlNode licenseNode = repo.SelectSingleNode("//span[@class='mr-3']/text()[normalize-space()]"); + string licenseValue = (licenseNode != null && !string.IsNullOrEmpty(licenseNode.InnerText.Trim())) ? UtilManager.ClearStrings(licenseNode.InnerText) : "None"; + + HtmlNode lastUpdateNode = repo.SelectSingleNode(".//relative-time/@datetime"); + string lastUpdate = (lastUpdateNode != null && !string.IsNullOrEmpty(lastUpdateNode.GetAttributeValue("datetime", string.Empty).Trim())) ? lastUpdateNode.GetAttributeValue("datetime", string.Empty).Trim() : "None"; + + UserRepository userRepository = new(isFork, licenseValue, lastUpdate, language, starCountValue, forkCountValue, Username, name, description); + UserRepositoriesList.Add(userRepository); + } + + TopValues topValues = UtilManager.GetTopValues(UserRepositoriesList); + return (lightUserprofile, UserRepositoriesList, topValues); + } + + #endregion LightUserprofile & UserRepository + + #region FullRepository + + internal static FullRepository GetFullRepository(string RepoUrl) + { + HtmlDocument RepositoryDocument = UtilManager.GetHtmlDoc(RepoUrl, true); + HtmlNode RepositoryNode = RepositoryDocument.DocumentNode; + + string UsernameValue = UtilManager.GetUsernameFromGitHubUrl(RepoUrl); + + HtmlNode RepoName = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/div/div[1]/div[1]/div/strong/a"); + string RepoNameValue = "None"; + + if (RepoName != null) + { + RepoNameValue = UtilManager.ClearStrings(RepoName.InnerHtml); + } + + HtmlNode Description = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[1]/div/div/p"); + string DescriptionValue = "None"; + + if (Description != null) + { + DescriptionValue = UtilManager.ClearStrings(Description.InnerHtml); + } + + HtmlNode OpenIssueCount = RepositoryNode.SelectSingleNode("//*[@id=\"issues-repo-tab-count\"]"); + int OpenIssueCountValue = 0; + + if (OpenIssueCount != null) + { + OpenIssueCountValue = UtilManager.ParseNumberValue(OpenIssueCount.Attributes["title"].Value); + } + + HtmlNode OpenPullRequestsCount = RepositoryNode.SelectSingleNode("//*[@id=\"pull-requests-repo-tab-count\"]"); + int OpenPullRequestsCountValue = 0; + + if (OpenPullRequestsCount != null) + { + OpenPullRequestsCountValue = UtilManager.ParseNumberValue(OpenPullRequestsCount.Attributes["title"].Value); + } + + HtmlNode TotalCommitsCount = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[2]/div[1]/div/div[4]/ul/li/a/span/strong"); + int TotalCommitsCountValue = 0; + + if (TotalCommitsCount != null) + { + TotalCommitsCountValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(TotalCommitsCount.InnerHtml)); + } + + HtmlNode LastCommitText = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[2]/div[1]/div/div[2]/div[1]/span/a[1]"); + string LastCommitTextValue = "None"; + + if (LastCommitText != null) + { + LastCommitTextValue = UtilManager.ClearStrings(LastCommitText.InnerHtml.Replace(" (", string.Empty)); + } + + int WatcherCountValue = GetWatcherCount(RepositoryNode); + + HtmlNode ContributorCount = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[5]/div/h2/a/span"); + int ContributorCountValue = 0; + + if (ContributorCount != null) + { + ContributorCountValue = UtilManager.ParseNumberValue(ContributorCount.Attributes["title"].Value); + } + + IEnumerable Topics = RepositoryNode.QuerySelectorAll("a.topic-tag"); + IEnumerable TopicValues = new List(); + + if (Topics != null && Topics.Any()) + { + TopicValues = Topics.Select(t => UtilManager.ClearStrings(t.InnerHtml)); + } + + HtmlNode ReleaseCount = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[2]/div/h2/a/span"); + int ReleaseCountValue = 0; + + if (ReleaseCount != null) + { + ReleaseCountValue = UtilManager.ParseNumberValue(ReleaseCount.Attributes["title"].Value); + } + + HtmlNode LatestReleaseText = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[2]/div/div[2]/div/a/div/div[1]/span[1]"); + string LatestReleaseTextValue = "None"; + + if (LatestReleaseText != null) + { + LatestReleaseTextValue = UtilManager.ClearStrings(LatestReleaseText.InnerHtml); + } + + HtmlNode TagCount = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[1]/div[3]/a[2]/strong"); + int TagCountValue = 0; + + if (TagCount != null) + { + TagCountValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(TagCount.InnerHtml)); + } + + HtmlNode BranchCount = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[1]/div[3]/a[1]/strong"); + int BranchCountValue = 0; + + if (BranchCount != null) + { + BranchCountValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(BranchCount.InnerHtml)); + } + + HtmlNode DefaultBranchName = RepositoryNode.SelectSingleNode("/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div[2]/div[1]/div[1]/div[1]/details/summary/span[1]"); + string DefaultBranchNameValue = "None"; + + if (DefaultBranchName != null) + { + DefaultBranchNameValue = UtilManager.ClearStrings(DefaultBranchName.InnerHtml); + } + + IEnumerable MainLanguage = RepositoryNode.QuerySelectorAll("li.d-inline:nth-child(1) > a:nth-child(1) > span"); + string MainLanguageValue = "None"; + + if (MainLanguage != null && MainLanguage.Any()) + { + MainLanguageValue = UtilManager.ClearStrings(MainLanguage.ElementAt(0).InnerHtml); + } + + HtmlNode Stars = RepositoryNode.SelectSingleNode("//*[@id=\"repo-stars-counter-star\"]"); + int StarsValue = 0; + + if (Stars != null) + { + StarsValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(Stars.InnerHtml)); + } + + HtmlNode Forks = RepositoryNode.SelectSingleNode("//*[@id=\"repo-network-counter\"]"); + int ForksValue = 0; + + if (Forks != null) + { + ForksValue = UtilManager.ParseNumberValue(UtilManager.ClearStrings(Forks.InnerHtml)); + } + + FullRepository fullRepository = new($"https://github.com{RepoUrl}", OpenIssueCountValue, OpenPullRequestsCountValue, TotalCommitsCountValue, LastCommitTextValue, WatcherCountValue, ContributorCountValue, TopicValues.ToArray(), ReleaseCountValue, LatestReleaseTextValue, TagCountValue, BranchCountValue, DefaultBranchNameValue, MainLanguageValue, StarsValue, ForksValue, UsernameValue, RepoNameValue, DescriptionValue); + return fullRepository; + } + + internal static IEnumerable GetFullRepositories(string Username) + { + IEnumerable RepositoryUrlsList = GetAllRepositorsUrls(Username); + List FullRepositoriesList = new(); + + foreach (string repoUrl in RepositoryUrlsList) + { + FullRepository fullRepository = GetFullRepository(repoUrl); + FullRepositoriesList.Add(fullRepository); + } + + return FullRepositoriesList; + } + + #endregion FullRepository + + #region Utils + + private static int GetWatcherCount(HtmlNode DocumentNode) + { + int watchersCount = 0; + IEnumerable octiconEyeElements = DocumentNode.QuerySelectorAll(".octicon-eye"); + + if (octiconEyeElements != null) + { + foreach (HtmlNode octiconEyeElement in octiconEyeElements) + { + HtmlNode strongElement = octiconEyeElement.SelectSingleNode("following-sibling::strong"); + + if (strongElement != null) + { + _ = int.TryParse(strongElement.InnerText.Trim(), out watchersCount); + } + } + } + + return watchersCount; + } + + private static IEnumerable GetAllRepositorsUrls(string Username) + { + HtmlDocument UserRepositoriesDocument = UtilManager.GetHtmlDoc($"https://github.com/{Username}?tab=repositories", true); + HtmlNode UserRepositoriesNode = UserRepositoriesDocument.DocumentNode; + + IEnumerable UserRepositorieList = UserRepositoriesNode.QuerySelectorAll("li.col-12"); + List RepositoryUrlList = new(); + + foreach (HtmlNode repository in UserRepositorieList) + { + HtmlNode RepoName = repository.SelectSingleNode(".//h3/a"); + string RepoUrl = "None"; + + if (RepoName != null) + { + RepoUrl = RepoName.Attributes["href"].Value; + } + + RepositoryUrlList.Add(RepoUrl); + } + + return RepositoryUrlList; + } + + private static (LightUserprofile profil, HtmlDocument profilepage) GetUserprofileData(string Username) + { + HtmlDocument GithubProfilepage = UtilManager.GetHtmlDoc($"https://github.com/{Username}", true); + + HtmlNode ProfileName = GithubProfilepage.QuerySelector(".p-name"); + string ProfileNameValue = ProfileName.InnerHtml.Trim(); + + HtmlNode Description = GithubProfilepage.QuerySelector(".p-note > div:nth-child(1)"); + string DescriptionValue = "None"; + + if (Description != null) + { + DescriptionValue = Description.InnerHtml.Trim(); + } + + HtmlNode FollowerCount = GithubProfilepage.QuerySelector("span.text-bold:nth-child(2)"); + int FollowerCountValue = UtilManager.ParseNumberValue(FollowerCount.InnerHtml); + + HtmlNode FollowingCount = GithubProfilepage.QuerySelector("span.text-bold:nth-child(1)"); + int FollowingCountValue = UtilManager.ParseNumberValue(FollowingCount.InnerHtml); + + HtmlNode Location = GithubProfilepage.QuerySelector(".p-label"); + string LocationValue = "None"; + + if (Location != null) + { + LocationValue = Location.InnerHtml.Trim(); + } + + HtmlNode ImageUrl = GithubProfilepage.QuerySelector("div.d-inline-block > a"); + string ImageUrlValue = ImageUrl.Attributes["href"].Value; + + HtmlNode Organization = GithubProfilepage.QuerySelector(".p-org > div:nth-child(1)"); + string OrganizationValue = "None"; + + if (Organization != null) + { + OrganizationValue = Organization.InnerHtml.Trim(); + } + + HtmlNode PrimaryProfileUrl = GithubProfilepage.DocumentNode.SelectSingleNode("//a[@class='Link--primary']"); + string PrimaryProfileUrlValue = "None"; + + if (PrimaryProfileUrl != null) + { + PrimaryProfileUrlValue = PrimaryProfileUrl.Attributes["href"].Value; + } + + HtmlNode RepositoryCount = GithubProfilepage.QuerySelector("div.box-shadow-none > nav:nth-child(1) > a:nth-child(2) > span"); + int RepositoryCountValue = 0; + + if (RepositoryCount != null) + { + RepositoryCountValue = UtilManager.ParseNumberValue(RepositoryCount.Attributes["title"].Value); + } + + HtmlNode StarsGiven = GithubProfilepage.QuerySelector("div.box-shadow-none > nav:nth-child(1) > a:nth-child(5) > span:nth-child(2)"); + int StarsGivenValue = 0; + + if (StarsGiven != null) + { + StarsGivenValue = UtilManager.ParseNumberValue(StarsGiven.Attributes["title"].Value); + } + + return (new(Username, ProfileNameValue, DescriptionValue, OrganizationValue, LocationValue, ImageUrlValue, RepositoryCountValue, FollowerCountValue, FollowingCountValue, PrimaryProfileUrlValue, StarsGivenValue), GithubProfilepage); + } + + #endregion Utils + + } +} \ No newline at end of file diff --git a/GithubNet/Managers/UtilManager.cs b/GithubNet/Managers/UtilManager.cs new file mode 100644 index 0000000..63d72d2 --- /dev/null +++ b/GithubNet/Managers/UtilManager.cs @@ -0,0 +1,153 @@ +using GithubNet.Models.Extra; +using GithubNet.Models.Repositories; +using HtmlAgilityPack; +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace GithubNet.Managers +{ + internal static class UtilManager + { + + private static readonly RegexOptions regexOptions = RegexOptions.Compiled; + private static readonly Regex TooMuchWhitespaceRegex = new("[ ]{2,}", regexOptions, TimeSpan.FromMilliseconds(50)); + + public static TopValues GetTopValues(IEnumerable userRepositories) + { + Dictionary repositoryStats = new Dictionary(); + + int totalStars = 0; + int totalForks = 0; + + foreach (UserRepository repo in userRepositories) + { + totalStars += repo.TotalStars; + totalForks += repo.TotalForks; + + if (!repositoryStats.TryGetValue(repo.MainLanguage, out (int Stars, int Forks, int RepoCount) value)) + { + repositoryStats[repo.MainLanguage] = (repo.TotalStars, repo.TotalForks, 1); + } + else + { + (int Stars, int Forks, int RepoCount) stats = value; + repositoryStats[repo.MainLanguage] = (stats.Stars + repo.TotalStars, stats.Forks + repo.TotalForks, stats.RepoCount + 1); + } + } + + string topLanguage = repositoryStats.OrderByDescending(kv => kv.Value.RepoCount).FirstOrDefault().Key; + TopValues topValues = new(topLanguage, totalStars, totalForks); + + return topValues; + } + + internal static string GetUsernameFromGitHubUrl(string url) + { + if (IsGitHubRepositoryUrl(url)) + { + string[] parts = url.Split('/'); + return parts[3]; + } + else + { + return "None"; + } + } + + private static bool IsGitHubRepositoryUrl(string url) + { + return url.StartsWith("https://github.com/") && url.Count(c => c == '/') >= 4; + } + + internal static HtmlDocument GetHtmlDoc(string Url, bool IsGithubPage = false) + { + string RequestUrl = Url; + if (!Url.StartsWith("https://github.com") && IsGithubPage) + { + RequestUrl = $"https://github.com{RequestUrl}"; + } + + HtmlWeb Page = new() + { + OverrideEncoding = Encoding.UTF8 + }; + + HtmlDocument PageDocument = Page.Load(RequestUrl); + return PageDocument; + } + + internal static int ParseNumberValue(string value) + { + value = value.Replace(",", string.Empty); + + if (value.EndsWith("k", StringComparison.OrdinalIgnoreCase)) + { + if (double.TryParse(value.Substring(0, value.Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out double numericValue)) + { + return (int)(numericValue * 1000); + } + else + { + throw new ArgumentException("Invalid numeric format"); + } + } + else + { + if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int numericValue)) + { + return numericValue; + } + else + { + throw new ArgumentException("Invalid numeric format"); + } + } + } + + internal static string ClearStrings(string textIn, bool NormalizeWhitespace = false, string RemoveThis = "") + { + StringBuilder sb = new(textIn.Length); + foreach (char i in textIn) + { + if (i != '\n' && i != '\r' && i != '\t') + { + sb.Append(i); + } + } + + string ReturnText = sb.ToString().Trim(); + if (NormalizeWhitespace) + { + ReturnText = TooMuchWhitespaceRegex.Replace(ReturnText, @" "); + } + + if (!string.IsNullOrEmpty(RemoveThis)) + { + ReturnText = ReturnText.Replace(RemoveThis, string.Empty); + } + + return ReturnText; + } + + internal static string ParseTopicNameToUrl(string topicNameIn) + { + string LanguageName = NormalizeLanguageNameIdentifier(topicNameIn); + return $"https://github.com/topics/{LanguageName}"; + } + + internal static string NormalizeLanguageNameIdentifier(string topicNameIn) + { + string normalized = topicNameIn.Replace(" ", "-").ToLowerInvariant(); + return normalized switch + { + "c#" => "csharp", + "c++" => "cpp", + _ => normalized, + }; + } + + } +} diff --git a/GithubNet/Models/Extra/TopValues.cs b/GithubNet/Models/Extra/TopValues.cs new file mode 100644 index 0000000..5d1baa9 --- /dev/null +++ b/GithubNet/Models/Extra/TopValues.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Extra +{ + public class TopValues + { + public TopValues(string topLanguage, int allStarsCount, int allForksCount) + { + TopLanguage = topLanguage; + AllStarsCount = allStarsCount; + AllForksCount = allForksCount; + } + + public string TopLanguage { get; set; } + public int AllStarsCount { get; set; } + public int AllForksCount { get; set; } + + public override string ToString() + { + return $"{{{nameof(TopLanguage)}={TopLanguage}, {nameof(AllStarsCount)}={AllStarsCount.ToString()}, {nameof(AllForksCount)}={AllForksCount.ToString()}}}"; + } + } +} diff --git a/GithubNet/Models/ItemBase.cs b/GithubNet/Models/ItemBase.cs deleted file mode 100644 index 6f6f247..0000000 --- a/GithubNet/Models/ItemBase.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace GithubNet.Models -{ - public class ItemBase - { - public ItemBase(string user, string respositoryLink, string respositoryName, string description, int totalStars, int totalForks, bool hasProjectUrl, string projectUrl, bool hasTopics, string[] topics) - { - User = user; - RespositoryLink = respositoryLink; - RespositoryName = respositoryName; - Description = description; - TotalStars = totalStars; - TotalForks = totalForks; - HasProjectUrl = hasProjectUrl; - ProjectUrl = projectUrl; - HasTopics = hasTopics; - Topics = topics; - } - - public string User { get; set; } - - public string RespositoryLink { get; set; } - - public string RespositoryName { get; set; } - - public string Description { get; set; } - - public int TotalStars { get; set; } - - public int TotalForks { get; set; } - - public bool HasProjectUrl { get; set; } - - public string ProjectUrl { get; set; } - - public bool HasTopics { get; set; } - - public string[] Topics { get; set; } - - - public string GetLastCommitUrl(string branch) - { - return $"https://github.com/{this.User}/{this.RespositoryName}/commit/{branch}"; - } - - public string ParseCreatedByText() - { - return $"{this.RespositoryName} created by {this.User}"; - } - - public string GetReadMeUrl(string branch) - { - return $"https://raw.githubusercontent.com/{this.User}/{this.RespositoryName}/{branch}/README.md"; - } - - public string GetForksUrl() - { - return $"https://github.com/{this.User}/{this.RespositoryName}/forks"; - } - - public string GetStarsUrl() - { - return $"https://github.com/{this.User}/{this.RespositoryName}/stargazers"; - } - } -} \ No newline at end of file diff --git a/GithubNet/Models/Repositories/FullRepository.cs b/GithubNet/Models/Repositories/FullRepository.cs new file mode 100644 index 0000000..22bf8e9 --- /dev/null +++ b/GithubNet/Models/Repositories/FullRepository.cs @@ -0,0 +1,56 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Repositories +{ + public class FullRepository : RepositoryBase + { + public FullRepository(string projectUrl, int openIssueCount, int openPullRequestsCount, int totalCommitsCount, string lastCommitText, int watcherCount, int contributorCount, string[] topics, int releaseCount, string lastReleaseText, int tagCount, int branchCount, string defaultBranchName, string mainLanguage, int totalStars, int totalForks, string username, string repositoryName, string description) : base(mainLanguage, totalStars, totalForks, username, repositoryName, description) + { + ProjectUrl = projectUrl; + OpenIssueCount = openIssueCount; + OpenPullRequestsCount = openPullRequestsCount; + TotalCommitsCount = totalCommitsCount; + LastCommitText = lastCommitText; + WatcherCount = watcherCount; + ContributorCount = contributorCount; + Topics = topics; + ReleaseCount = releaseCount; + LastReleaseText = lastReleaseText; + TagCount = tagCount; + BranchCount = branchCount; + DefaultBranchName = defaultBranchName; + } + + public string ProjectUrl { get; set; } + + public int OpenIssueCount { get; set; } + + public int OpenPullRequestsCount { get; set; } + + public int TotalCommitsCount { get; set; } + + public string LastCommitText { get; set; } + + public int WatcherCount { get; set; } + + public int ContributorCount { get; set; } + + public string[] Topics { get; set; } + + public int ReleaseCount { get; set; } + + public string LastReleaseText { get; set; } + + public int TagCount { get; set; } + + public int BranchCount { get; set; } + + public string DefaultBranchName { get; set; } + + public override string ToString() + { + return $"{{{nameof(ProjectUrl)}={ProjectUrl}, {nameof(OpenIssueCount)}={OpenIssueCount.ToString()}, {nameof(OpenPullRequestsCount)}={OpenPullRequestsCount.ToString()}, {nameof(TotalCommitsCount)}={TotalCommitsCount.ToString()}, {nameof(LastCommitText)}={LastCommitText}, {nameof(WatcherCount)}={WatcherCount.ToString()}, {nameof(ContributorCount)}={ContributorCount.ToString()}, {nameof(Topics)}={string.Join(",", Topics)}, {nameof(ReleaseCount)}={ReleaseCount.ToString()}, {nameof(LastReleaseText)}={LastReleaseText}, {nameof(TagCount)}={TagCount.ToString()}, {nameof(BranchCount)}={BranchCount.ToString()}, {nameof(DefaultBranchName)}={DefaultBranchName}, {nameof(MainLanguage)}={MainLanguage}, {nameof(TotalStars)}={TotalStars.ToString()}, {nameof(TotalForks)}={TotalForks.ToString()}, {nameof(Username)}={Username}, {nameof(RepositoryName)}={RepositoryName}, {nameof(Description)}={Description}}}"; + } + } +} diff --git a/GithubNet/Models/Repositories/RepositoryBase.cs b/GithubNet/Models/Repositories/RepositoryBase.cs new file mode 100644 index 0000000..c83bc28 --- /dev/null +++ b/GithubNet/Models/Repositories/RepositoryBase.cs @@ -0,0 +1,32 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Repositories +{ + public class RepositoryBase + { + public RepositoryBase(string mainLanguage, int totalStars, int totalForks, string username, string repositoryName, string description) + { + MainLanguage = mainLanguage; + TotalStars = totalStars; + TotalForks = totalForks; + Username = username; + RepositoryName = repositoryName; + Description = description; + } + + public string MainLanguage { get; set; } + + public int TotalStars { get; set; } + + public int TotalForks { get; set; } + + public string Username { get; set; } + + public string RepositoryName { get; set; } + + public string Description { get; set; } + + + } +} diff --git a/GithubNet/Models/Repositories/TrendRepository.cs b/GithubNet/Models/Repositories/TrendRepository.cs new file mode 100644 index 0000000..159da08 --- /dev/null +++ b/GithubNet/Models/Repositories/TrendRepository.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Repositories +{ + public class TrendRepository : RepositoryBase + { + public TrendRepository(int starsToday, string mainLanguage, int totalStars, int totalForks, string username, string repositoryName, string description) : base(mainLanguage, totalStars, totalForks, username, repositoryName, description) + { + StarsToday = starsToday; + } + + public int StarsToday { get; set; } + + public override string ToString() + { + return $"{{{nameof(StarsToday)}={StarsToday.ToString()}, {nameof(MainLanguage)}={MainLanguage}, {nameof(TotalStars)}={TotalStars.ToString()}, {nameof(TotalForks)}={TotalForks.ToString()}, {nameof(Username)}={Username}, {nameof(RepositoryName)}={RepositoryName}, {nameof(Description)}={Description}}}"; + } + } +} diff --git a/GithubNet/Models/Repositories/UserRepository.cs b/GithubNet/Models/Repositories/UserRepository.cs new file mode 100644 index 0000000..4d2f5b0 --- /dev/null +++ b/GithubNet/Models/Repositories/UserRepository.cs @@ -0,0 +1,27 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Repositories +{ + public class UserRepository : RepositoryBase + { + + public UserRepository(bool isForked, string licenseText, string lastUpdateText, string mainLanguage, int totalStars, int totalForks, string username, string repositoryName, string description) : base(mainLanguage, totalStars, totalForks, username, repositoryName, description) + { + IsForked = isForked; + LicenseText = licenseText; + LastUpdateText = lastUpdateText; + } + + public bool IsForked { get; set; } + + public string LicenseText { get; set; } + + public string LastUpdateText { get; set; } + + public override string ToString() + { + return $"{{{nameof(IsForked)}={IsForked.ToString()}, {nameof(LicenseText)}={LicenseText}, {nameof(LastUpdateText)}={LastUpdateText}, {nameof(MainLanguage)}={MainLanguage}, {nameof(TotalStars)}={TotalStars.ToString()}, {nameof(TotalForks)}={TotalForks.ToString()}, {nameof(Username)}={Username}, {nameof(RepositoryName)}={RepositoryName}, {nameof(Description)}={Description}}}"; + } + } +} diff --git a/GithubNet/Models/Repository.cs b/GithubNet/Models/Repository.cs deleted file mode 100644 index 40148bd..0000000 --- a/GithubNet/Models/Repository.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace GithubNet.Models -{ - public class Repository : ItemBase - { - public Repository(int openIssuesNumber, int openPullRequestsNumber, int totalCommitsNumber, int totalContributorsNumber, string user, string respositoryLink, string respositoryName, string description, int totalStars, int totalForks, bool hasProjectUrl, string projectUrl, bool hasTopics, string[] topics) : base(user, respositoryLink, respositoryName, description, totalStars, totalForks, hasProjectUrl, projectUrl, hasTopics, topics) - { - OpenIssuesNumber = openIssuesNumber; - OpenPullRequestsNumber = openPullRequestsNumber; - TotalCommitsNumber = totalCommitsNumber; - TotalContributorsNumber = totalContributorsNumber; - - base.User = user; - base.RespositoryLink = respositoryLink; - base.RespositoryName = respositoryName; - base.Description = description; - base.TotalStars = totalStars; - base.TotalForks = totalForks; - base.HasProjectUrl = hasProjectUrl; - base.ProjectUrl = projectUrl; - base.HasTopics = hasTopics; - base.Topics = topics; - - } - - public int OpenIssuesNumber { get; set; } - - public int OpenPullRequestsNumber { get; set; } - - public int TotalCommitsNumber { get; set; } - - public int TotalContributorsNumber { get; set; } - - public override string ToString() - { - return $"{{{nameof(OpenIssuesNumber)}={OpenIssuesNumber.ToString()}, {nameof(OpenPullRequestsNumber)}={OpenPullRequestsNumber.ToString()}, {nameof(TotalCommitsNumber)}={TotalCommitsNumber.ToString()}, {nameof(TotalContributorsNumber)}={TotalContributorsNumber.ToString()}, {nameof(User)}={User}, {nameof(RespositoryLink)}={RespositoryLink}, {nameof(RespositoryName)}={RespositoryName}, {nameof(Description)}={Description}, {nameof(TotalStars)}={TotalStars.ToString()}, {nameof(TotalForks)}={TotalForks.ToString()}, {nameof(HasProjectUrl)}={HasProjectUrl.ToString()}, {nameof(ProjectUrl)}={ProjectUrl}, {nameof(HasTopics)}={HasTopics.ToString()}, {nameof(Topics)}={Topics}}}"; - } - } -} \ No newline at end of file diff --git a/GithubNet/Models/TrendItem.cs b/GithubNet/Models/TrendItem.cs deleted file mode 100644 index 4fc7be4..0000000 --- a/GithubNet/Models/TrendItem.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace GithubNet.Models -{ - public class TrendItem : ItemBase - { - - public TrendItem(string programminglanguage, bool hasDetails, bool isArchived, string lastCommitTime, string lastCommitUrl, string user, string respositoryLink, string respositoryName, string description, int totalStars, int totalForks, bool hasProjectUrl, string projectUrl, bool hasTopics, string[] topics) : base(user, respositoryLink, respositoryName, description, totalStars, totalForks, hasProjectUrl, projectUrl, hasTopics, topics) - { - Programminglanguage = programminglanguage; - HasDetails = hasDetails; - IsArchived = isArchived; - LastCommitTime = lastCommitTime; - LastCommitUrl = lastCommitUrl; - base.User = user; - base.RespositoryLink = respositoryLink; - base.RespositoryName = respositoryName; - base.Description = description; - base.TotalStars = totalStars; - base.TotalForks = totalForks; - base.HasProjectUrl = hasProjectUrl; - base.ProjectUrl = projectUrl; - base.HasTopics = hasTopics; - base.Topics = topics; - } - - public string Programminglanguage { get; set; } - - public bool HasDetails { get; set; } - - public bool IsArchived { get; set; } - - public string LastCommitTime { get; set; } - - public string LastCommitUrl { get; set; } - - } -} \ No newline at end of file diff --git a/GithubNet/Models/Userprofiles/FullUserprofile.cs b/GithubNet/Models/Userprofiles/FullUserprofile.cs new file mode 100644 index 0000000..4de5998 --- /dev/null +++ b/GithubNet/Models/Userprofiles/FullUserprofile.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Userprofiles +{ + public class FullUserprofile : LightUserprofile + { + public FullUserprofile(LightUserprofile lightUserProfile, int lastYearContributionsCount, bool hasSpecialUserReadmeEnabled) : base(lightUserProfile.Username, lightUserProfile.Name, lightUserProfile.Description, lightUserProfile.Organization, lightUserProfile.Location, lightUserProfile.ProfileImageUrl, lightUserProfile.RepositoryCount, lightUserProfile.FollowerCount, lightUserProfile.FollowingCount, lightUserProfile.PrimaryProfileUrl, lightUserProfile.StarsGivenCount) + { + LastYearContributionsCount = lastYearContributionsCount; + HasSpecialUserReadmeEnabled = hasSpecialUserReadmeEnabled; + } + + public FullUserprofile(int lastYearContributionsCount, bool hasSpecialUserReadmeEnabled, string username, string name, string description, string organization, string location, string profileImageUrl, int repositoryCount, int followerCount, int followingCount, string primaryProfileUrl, int starsGivenCount) : base(username, name, description, organization, location, profileImageUrl, repositoryCount, followerCount, followingCount, primaryProfileUrl, starsGivenCount) + { + LastYearContributionsCount = lastYearContributionsCount; + HasSpecialUserReadmeEnabled = hasSpecialUserReadmeEnabled; + } + + public int LastYearContributionsCount { get; set; } + + public bool HasSpecialUserReadmeEnabled { get; set; } + + public override string ToString() + { + return $"{{{nameof(LastYearContributionsCount)}={LastYearContributionsCount.ToString()}, {nameof(HasSpecialUserReadmeEnabled)}={HasSpecialUserReadmeEnabled.ToString()}, {nameof(Username)}={Username}, {nameof(Name)}={Name}, {nameof(Description)}={Description}, {nameof(Organization)}={Organization}, {nameof(Location)}={Location}, {nameof(ProfileImageUrl)}={ProfileImageUrl}, {nameof(RepositoryCount)}={RepositoryCount.ToString()}, {nameof(FollowerCount)}={FollowerCount.ToString()}, {nameof(FollowingCount)}={FollowingCount.ToString()}, {nameof(PrimaryProfileUrl)}={PrimaryProfileUrl}, {nameof(StarsGivenCount)}={StarsGivenCount.ToString()}}}"; + } + } +} diff --git a/GithubNet/Models/Userprofiles/LightUserprofile.cs b/GithubNet/Models/Userprofiles/LightUserprofile.cs new file mode 100644 index 0000000..24a3bac --- /dev/null +++ b/GithubNet/Models/Userprofiles/LightUserprofile.cs @@ -0,0 +1,50 @@ +using System; +using System.Linq; + +namespace GithubNet.Models.Userprofiles +{ + public class LightUserprofile + { + public LightUserprofile(string username, string name, string description, string organization, string location, string profileImageUrl, int repositoryCount, int followerCount, int followingCount, string primaryProfileUrl, int starsGivenCount) + { + Username = username; + Name = name; + Description = description; + Organization = organization; + Location = location; + ProfileImageUrl = profileImageUrl; + RepositoryCount = repositoryCount; + FollowerCount = followerCount; + FollowingCount = followingCount; + PrimaryProfileUrl = primaryProfileUrl; + StarsGivenCount = starsGivenCount; + } + + public string Username { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Organization { get; set; } + + public string Location { get; set; } + + public string ProfileImageUrl { get; set; } + + public int RepositoryCount { get; set; } + + public int FollowerCount { get; set; } + + public int FollowingCount { get; set; } + + public string PrimaryProfileUrl { get; set; } + + public int StarsGivenCount { get; set; } + + public override string ToString() + { + return $"{{{nameof(Username)}={Username}, {nameof(Name)}={Name}, {nameof(Description)}={Description}, {nameof(Organization)}={Organization}, {nameof(Location)}={Location}, {nameof(ProfileImageUrl)}={ProfileImageUrl}, {nameof(RepositoryCount)}={RepositoryCount.ToString()}, {nameof(FollowerCount)}={FollowerCount.ToString()}, {nameof(FollowingCount)}={FollowingCount.ToString()}, {nameof(PrimaryProfileUrl)}={PrimaryProfileUrl}, {nameof(StarsGivenCount)}={StarsGivenCount.ToString()}}}"; + } + } +} diff --git a/GithubNetDemo/GithubNetDemo.csproj b/GithubNetDemo/GithubNetDemo.csproj index e043c0f..f67a9e5 100644 --- a/GithubNetDemo/GithubNetDemo.csproj +++ b/GithubNetDemo/GithubNetDemo.csproj @@ -3,6 +3,8 @@ Exe net7.0 + Github(Data)Net is a simple C# library, using HtmlAgilityPack to retrieve several things from GitHub, things like trending repositories, profiles of users, the repositories of users and related information. + New example code enable enable diff --git a/GithubNetDemo/Program.cs b/GithubNetDemo/Program.cs index d120681..30a384e 100644 --- a/GithubNetDemo/Program.cs +++ b/GithubNetDemo/Program.cs @@ -1,28 +1,101 @@ -using GithubNet; -using GithubNet.Models; +using GithubNet.Managers; +using GithubNet.Models.Extra; +using GithubNet.Models.Repositories; +using GithubNet.Models.Userprofiles; namespace GithubNetDemo { internal class Program { - private static async Task Main(string[] args) + private static void Main(string[] args) { GithubNetClient client = new(); - Repository tinygrad = await client.GetRepositoryInfoAsync("https://github.com/liebki/GithubNet"); - Console.WriteLine(tinygrad.ToString()); + IEnumerable repos = client.GetFullRepositories("fffaraz"); + foreach (FullRepository it in repos) + { + Console.WriteLine(it); + Console.WriteLine("\n"); + } + + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + - List testEntries = await client.GetTrendItemsAsync(); - foreach (TrendItem entry in testEntries) + + IEnumerable TrendRepositoriesUrlList = client.GetUrlsOfTrendingRepositories(); + foreach (string it in TrendRepositoriesUrlList) { - Console.WriteLine(); - Console.WriteLine("------------------------------------------"); - Console.WriteLine(entry.ToString()); - Console.WriteLine("------------------------------------------"); - Console.WriteLine(); + Console.WriteLine(it); + Console.WriteLine("\n"); } - Console.WriteLine(client.GetTopicUrlFromTopicName(string.Empty)); + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + + + + IEnumerable TrendRepositoriesList = client.GetAllTrendingRepositories(); + foreach (TrendRepository it in TrendRepositoriesList) + { + Console.WriteLine(it); + Console.WriteLine("\n"); + } + + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + + + + (LightUserprofile Userprofile, IEnumerable UserRepositories, TopValues topValues) UserWithRepositories = client.GetLightUserprofileWithRepositories("liebki"); + + Console.WriteLine(UserWithRepositories.Userprofile.ToString()); + Console.WriteLine("\n"); + + Console.WriteLine(UserWithRepositories.topValues.ToString()); + Console.WriteLine("\n"); + + foreach (UserRepository it in UserWithRepositories.UserRepositories) + { + Console.WriteLine(it); + Console.WriteLine("\n"); + } + + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + + + + FullUserprofile fullUserprofile = client.GetFullUserprofile("liebki"); + Console.Write(fullUserprofile.ToString()); + + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + + + + FullRepository fullRepository = client.GetFullRepository("https://github.com/bgstaal/multipleWindow3dScene"); + Console.Write(fullRepository.ToString()); + + + Console.WriteLine("\n"); + Console.WriteLine("\n"); + Console.WriteLine("\n"); + + + + LightUserprofile lightUserprofile = client.GetLightUserprofile("bgstaal"); + Console.WriteLine(lightUserprofile.ToString()); } } } \ No newline at end of file diff --git a/README.md b/README.md index ff29792..791215f 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,102 @@ -# GithubNet +# GithubNet + ## Introduction -GithubNet is a C# library that allows you to retrieve trending GitHub repositories and their information. Currently, it provides functionality to fetch the trending repositories and their details. The library is built using .NET Core 7 and will be available as a NuGet package. +Github(Data)Net is a simple C# library, using HtmlAgilityPack to retrieve several things from GitHub, things like trending repositories, profiles of users, the repositories of users and related information. -## Technologies +## Features ⭐ -- C# -- .NET Core 7 -- [HtmlAgilityPack](https://www.nuget.org/packages/HtmlAgilityPack) -- [CssSelectors.Core.HtmlAgilityPack](https://www.nuget.org/packages/CssSelectors.Core.HtmlAgilityPack) +- **Trending Repositories:** Retrieve trending repositories. +- **Repository Details:** Obtain detailed information about repositories. +- **User Profiles:** Access full and light GitHub user profiles. +- **User Repositories:** Retrieve repositories associated with a GitHub user. +- **Topic URL:** Obtain the GitHub URL for a specific topic. -## Features +## Usage 🔧 -Both the `Repository` and `TrendItem` classes inherit from a base class named `ItemBase`. The `ItemBase` class includes the following common properties: -- `User` (string): The username or owner of the repository. -- `RespositoryLink` (string): The URL or link to the repository. -- `RespositoryName` (string): The name of the repository. -- `Description` (string): A brief description or summary of the repository. -- `TotalStars` (integer): Indicates the total number of stars or favorites received by the repository. -- `TotalForks` (integer): Represents the total number of forks or copies of the repository. -- `HasProjectUrl` (boolean): Indicates whether the repository has a project URL. -- `ProjectUrl` (string): The URL or link to the project associated with the repository, if available. -- `HasTopics` (boolean): Indicates whether the repository has topics associated with it. -- `Topics` (string[]): An array of strings representing the topics or tags associated with the repository. +### General -The `Repository` class includes additional properties specific to repositories: +The GithubNetClient needs to be used to access all functionalities: -- `OpenIssuesNumber` (integer): Represents the number of open issues in the repository. -- `OpenPullRequestsNumber` (integer): Indicates the number of open pull requests in the repository. -- `TotalCommitsNumber` (integer): Represents the total number of commits made in the repository. -- `TotalContributorsNumber` (integer): Indicates the total number of contributors to the repository. +```csharp +GithubNetClient client = new(); +``` -The `TrendItem` class includes additional properties specific to trend items: -- `Programminglanguage` (string): The main programming language associated with the trend item. -- `HasDetails` (boolean): Indicates whether it's an intense crawl, where each repository is individually crawled. -- `IsArchived` (boolean): Indicates whether the repository associated with the trend item is archived. -- `LastCommitTime` (string): The timestamp or time of the last commit made to the repository. -- `LastCommitUrl` (string): The URL or link to the last commit made in the repository. +### Trending Repositories +```csharp +IEnumerable GetAllTrendingRepositories(string customQuery = "https://github.com/trending"); +IEnumerable GetUrlsOfTrendingRepositories(string customQuery = "https://github.com/trending"); +IEnumerable GetFullTrendingRepositories(string customQuery = "https://github.com/trending"); +``` -## Usage -The following methods can be used: +### User Profiles -- `GetTrendItemsAsync`: This method retrieves a list of `TrendItem` objects representing trending repositories. It returns the list asynchronously and can optionally load all available details if the `loadTrendItemDetails` parameter is set to `true`. +```csharp +FullUserprofile GetFullUserprofile(string Username); +LightUserprofile GetLightUserprofile(string Username); +``` -- `GetTrendItemDetailsAsync`: This method fetches additional details for a specific `TrendItem` object. It is used when you need more information about a particular repository in the list of trending repositories obtained from `GetTrendItemsAsync`. -- `GetTopicUrlFromTopicName`: This method allows you to obtain the GitHub URL for a given topic. It takes the topic name as input and returns the corresponding URL. +### Full Repositories -- `GetRepositoryInfoAsync`: This method retrieves detailed information about a specific GitHub repository. It provides details such as the number of contributors, the number of open issues, and more. The method returns the repository information asynchronously. +```csharp +IEnumerable GetFullRepositories(string Username); +FullRepository GetFullRepository(string RepositoryUrl); +``` -## Example +### LightUserprofile, UserRepositories and TopValues of User -For a demonstration of the library's functionality, refer to the included `GithubNetDemo` project. It showcases the console output of the crawled data using the `GetTrendItemsAsync` method. +```csharp +(LightUserprofile Userprofile, IEnumerable UserRepositories, TopValues topValues) GetLightUserprofileWithRepositories(string Username); +``` -## License +### Utils -GithubNet is licensed under the GNU General Public License v3.0. +```csharp +string GetTopicUrlFromTopicName(string topicName); +``` -You can read the full license details of the GNU General Public License v3.0 [here](https://choosealicense.com/licenses/gpl-3.0/). +## Types 🔖 + +- **FullUserprofile:** Represents a comprehensive user profile with additional information such as last year's contributions count and whether the user has a special readme enabled. This type is based on `LightUserprofile`. + +- **LightUserprofile:** Represents a lightweight user profile with essential information like username, name, description, etc. + +- **UserRepository:** Represents a user's repository with additional details like whether it's a fork, license text, and last update information. This type is based on `RepositoryBase`. + +- **TrendRepository:** Represents a trending repository with information about stars received today, main language, total stars, total forks, etc. This type is based on `RepositoryBase`. -## Roadmap +- **RepositoryBase:** Base class for repositories, containing shared information like main language, total stars, total forks, username, repository name, and description. -The roadmap for future development includes the following planned features: +- **FullRepository:** Represents a comprehensive repository with additional details such as project URL, open issues count, open pull requests count, total commits count, etc. This type is based on `RepositoryBase`. -- Reduce the redundant code in the `GithubNetManager` class! +- **TopValues:** Contains top values such as the top language, total stars count, and total forks count across user repositories. -- `GetUserInfoAsync`: Develop a method to fetch information about a GitHub user. This method will allow users to retrieve details such as the user's name, bio, location, profile picture, number of followers, number of repositories, and other relevant information. -- `GetAllTopicsAsync`: Create a method to fetch all available topics from the GitHub Topics page (https://github.com/topics). This method will provide a list of topics along with their corresponding URLs and other relevant information. +## Example ✍🏻 -- `GetAllCollectionsAsync`: Implement a method to retrieve information about the GitHub Collections page (https://github.com/collections). This method will allow users to obtain a list of collections, including their titles, descriptions, URLs, and other relevant details. +For a demonstration of the library's functionality, refer to the included `GithubNetDemo` project. + + +## License 📜 + +GithubNet is licensed under the GNU General Public License v3.0. + +You can read the full license details of the GNU General Public License v3.0 [here](https://choosealicense.com/licenses/gpl-3.0/). -## Disclaimer +## Disclaimer ⚠️ -Please read the full disclaimer in the DISCLAIMER.md file before using this project. The author (liebki) of the project and the project itself are not endorsed by Microsoft and do not reflect the views or opinions of Microsoft or any individuals officially involved with the project. \ No newline at end of file +Please read the full disclaimer in the DISCLAIMER.md file before using this project. +The author (liebki) of the project and the project itself are not endorsed by Microsoft and do not reflect the views or opinions of Microsoft or any individuals officially involved with the project. +The author of this library is not responsible for any incorrect or inappropriate usage. Please ensure that you use this library in accordance with its intended purpose and guidelines. \ No newline at end of file