添加项目文件。

This commit is contained in:
2024-02-18 14:29:07 +08:00
parent 270ee8a82e
commit 51f3d4c12f
17 changed files with 482 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
using System.Net.Http.Json;
using System.Text.Json;
using LeetCodeSharp.Fetcher.Models;
namespace LeetCodeSharp.Fetcher;
internal class Fetcher
{
private const string ProblemsUrl = "https://leetcode.cn/api/problems/algorithms/";
private const string GraphQlUrl = "https://leetcode.cn/graphql";
private readonly HttpClient _httpClient = new();
private readonly JsonSerializerOptions _serializerOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
public async Task<Problems> GetProblems()
{
var problems =
await _httpClient.GetFromJsonAsync<Problems>(ProblemsUrl, _serializerOptions)
?? throw new Exception("Failed to get problems.");
return problems;
}
public async Task<Problem> GetProblem(uint questionId)
{
var problems = await GetProblems();
var targetProblem = problems.StatStatusPairs.First(p =>
{
if (uint.TryParse(p.Stat.FrontendQuestionId, out var id))
{
return id == questionId;
}
return false;
});
if (targetProblem.PaidOnly)
{
throw new Exception("Target problem is paid only.");
}
if (targetProblem.Stat.QuestionTitleSlug is null)
{
throw new Exception("Failed to get problem title.");
}
var query = new Query(targetProblem.Stat.QuestionTitleSlug);
var response = await _httpClient.PostAsJsonAsync(
GraphQlUrl, query, _serializerOptions);
response.EnsureSuccessStatusCode();
var rawProblem = await JsonSerializer.DeserializeAsync<RawProblem>(
await response.Content.ReadAsStreamAsync(), _serializerOptions)
?? throw new Exception("Failed to get raw problem.");
var returnType = rawProblem.Data.Question.MetaData.Replace("\"", "");
return new Problem
{
Title = targetProblem.Stat.QuestionTitle
?? throw new Exception("Failed to get question title"),
TitleSlug = targetProblem.Stat.QuestionTitleSlug
?? throw new Exception("Failed to get question title slug"),
Content = rawProblem.Data.Question.Content,
QuestionId = questionId,
ReturnType = returnType,
CodeDefinition = JsonSerializer.Deserialize<List<CodeDefinition>>(
rawProblem.Data.Question.CodeDefinition, _serializerOptions)
?? throw new Exception("Failed to get code definition.")
};
}
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace LeetCodeSharp.Fetcher.Models;
internal class CodeDefinition
{
public required string Value { get; set; }
public required string Text { get; set; }
[JsonPropertyName("defaultCode")]
public required string DefaultCode { get; set; }
}

View File

@@ -0,0 +1,79 @@
using System.Text;
namespace LeetCodeSharp.Fetcher.Models;
internal class Problem
{
private const string Template = """
/**
* [__PROBLEM_ID__] __PROBLEM_TITLE__
*/
__EXTRA_USE__
namespace LeetCodeSharp.Problems
{
// Submission codes start here
__PROBLEM_CODE__
// Submission codes end here
}
""";
public required string Title { get; set; }
public required string TitleSlug { get; set; }
public required string Content { get; set; }
public required List<CodeDefinition> CodeDefinition { get; set; }
public required uint QuestionId { get; set; }
public required string ReturnType { get; set; }
public string GetFilename()
{
return $"p{QuestionId}_{TitleSlug.Replace('-', '_')}.cs";
}
public string GetFileContent()
{
var code = CodeDefinition.FirstOrDefault(c => c.Value == "csharp")
?? throw new Exception("Target question has no C# version.");
string template;
if (code.DefaultCode.Contains("public class ListNode")
|| code.DefaultCode.Contains("public class Point")
|| code.DefaultCode.Contains("public class TreeNode")
|| code.DefaultCode.Contains("public class Node"))
{
template = Template.Replace("__EXTRA_USE__", "using LeetCodeSharp.Utils;");
}
else
{
template = Template.Replace("__EXTRA_USE__", string.Empty);
}
return template.Replace("__PROBLEM_ID__", Title)
.Replace("__PROBLEM_TITLE__", QuestionId.ToString())
.Replace("__PROBLEM_CODE__", code.DefaultCode);
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("Title:").Append(Title).Append("\r\n");
builder.Append("Title slug: ").Append(TitleSlug).Append("\r\n");
builder.Append("Code definitions: \r\n");
foreach (var definition in CodeDefinition)
{
builder.Append('\t').Append(definition.Value).Append("\r\n");
}
builder.Append("Return type: ").Append(ReturnType).Append("\r\n");
return builder.ToString();
}
}

View File

@@ -0,0 +1,6 @@
namespace LeetCodeSharp.Fetcher.Models;
internal class Problems
{
public required List<StatWithStatus> StatStatusPairs { get; set; }
}

View File

@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
namespace LeetCodeSharp.Fetcher.Models;
internal class Query
{
[JsonPropertyName("operationName")]
public string OperationName { get; init; }
public Dictionary<string, string> Variables { get; } = [];
[JsonPropertyName("query")]
public string QueryString { get; init; }
public Query(string title)
{
OperationName = "questionData";
Variables.Add("titleSlug", title);
QueryString = """
query questionData($titleSlug: String!) {
question(titleSlug: $titleSlug) {
content
stats
codeDefinition
sampleTestCase
metaData
}
}
""";
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
namespace LeetCodeSharp.Fetcher.Models;
internal class Question
{
public required string Content { get; set; }
public required string Stats { get; set; }
[JsonPropertyName("codeDefinition")]
public required string CodeDefinition { get; set; }
[JsonPropertyName("sampleTestCase")]
public required string SampleTestCase { get; set; }
[JsonPropertyName("metaData")]
public required string MetaData { get; set; }
}
internal class Data
{
public required Question Question { get; set; }
}
internal class RawProblem
{
public required Data Data { get; set; }
}

View File

@@ -0,0 +1,35 @@
using System.Text.Json.Serialization;
namespace LeetCodeSharp.Fetcher.Models;
internal class Stat
{
public required uint QuestionId { get; set; }
[JsonPropertyName("question__article_slug")]
public string? QuestionArticleSlug { get; set; }
[JsonPropertyName("question__title")]
public string? QuestionTitle { get; set; }
[JsonPropertyName("question__title_slug")]
public string? QuestionTitleSlug { get; set; }
[JsonPropertyName("question__hide")]
public bool QuestionHide { get; set; }
public required uint TotalAcs { get; set; }
public required uint TotalSubmitted { get; set; }
public required string FrontendQuestionId { get; set; }
public required bool IsNewQuestion { get; set; }
}
internal class StatWithStatus
{
public required Stat Stat { get; set; }
public required bool PaidOnly { get; set; }
}

View File

@@ -0,0 +1,18 @@
using LeetCodeSharp.Fetcher;
var directories = Directory.EnumerateDirectories(Environment.CurrentDirectory);
if (!directories.Any(d => d.Contains("Problems")))
{
Console.WriteLine("Please run in correct directory!");
return;
}
var problemDirectory = Path.Combine(Environment.CurrentDirectory, "Problems");
var fetcher = new Fetcher();
var problem = await fetcher.GetProblem(uint.Parse(args[0]));
var problemFilename = Path.Combine(problemDirectory, problem.GetFilename());
await using var writer = new StreamWriter(problemFilename);
await writer.WriteAsync(problem.GetFileContent());