Files
WSiPBookDownloader/Program.cs

212 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.Net;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
namespace WSiPBookDownloader;
internal class BookMissingException : Exception
{
public uint BookId { get; }
public BookMissingException(uint bookId)
: base($"Book with ID {bookId} does not exist.")
{
BookId = bookId;
}
}
internal class Program
{
private static readonly HttpClient httpClient = new
(
new HttpClientHandler
{
AllowAutoRedirect = false,
}
)
{
BaseAddress = new Uri("https://appwsipnet.eduranga.pl/"),
};
private static readonly Lock _consoleLock = new();
private static void Log(string message, ConsoleColor? color = null)
{
lock (_consoleLock)
{
if (color.HasValue)
Console.ForegroundColor = color.Value;
Console.WriteLine(message);
Console.ResetColor();
}
}
static async Task Main(string[] args)
{
var onlyCovers = args.Any(arg => arg.Equals("--only-covers", StringComparison.OrdinalIgnoreCase));
var outputDirectory = Directory.GetCurrentDirectory();
var outputDirectoryIndex = Array.FindIndex(args, arg => arg.Equals("--output-dir", StringComparison.OrdinalIgnoreCase));
if (outputDirectoryIndex > -1)
{
if (outputDirectoryIndex + 1 >= args.Length)
{
Log("--output-dir requires a value.", ConsoleColor.DarkRed);
return;
}
outputDirectory = args[outputDirectoryIndex + 1];
if (!Directory.Exists(outputDirectory))
{
Log($"Output directory \"{outputDirectory}\" does not exist.", ConsoleColor.DarkRed);
return;
}
}
var positionalArgs = args
.Except(
[
"--only-covers",
"--output-dir",
outputDirectory
], StringComparer.OrdinalIgnoreCase)
.ToArray();
var startBookId = int.Parse(positionalArgs[0]);
var endBookId = positionalArgs.Length > 1
? int.Parse(positionalArgs[1])
: startBookId;
var bookIds = Enumerable.Range(startBookId, endBookId - startBookId + 1).Select(bookId => (uint)bookId);
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = onlyCovers ? 100 : 10 };
Log($"Output directory: \"{outputDirectory}\"");
await Parallel.ForEachAsync(bookIds, parallelOptions, async (bookId, cancellationToken) =>
{
var outputPath = Path.Combine(outputDirectory, $"{bookId}.pdf");
if (File.Exists(outputPath))
{
Log($"[!] Book with ID {bookId} already downloaded, skipping...", ConsoleColor.Yellow);
return;
}
Log($"[?] Starting download of book with ID {bookId}", ConsoleColor.Cyan);
var jpgDir = Path.Combine(Path.GetTempPath(), "WSiPBookDownloader", bookId.ToString());
try
{
if (onlyCovers)
{
await DownloadBookCoverJpg(bookId, outputDirectory, cancellationToken);
Log($"[✓] Downloaded book cover {bookId}", ConsoleColor.DarkGreen);
return;
}
await DownloadBookJpg(bookId, jpgDir, cancellationToken);
BuildPdf(bookId, jpgDir, outputPath);
Log($"[✓] Downloaded book {bookId}", ConsoleColor.DarkGreen);
}
catch (Exception ex)
{
Log($"[×] Failed to download book {bookId}:\n {ex.Message}", ConsoleColor.DarkRed);
} finally
{
if (Directory.Exists(jpgDir))
Directory.Delete(jpgDir, recursive: true);
}
});
}
private static async Task DownloadBookCoverJpg(uint bookId, string outputDir, CancellationToken cancellationToken = default)
{
var outputPath = Path.Combine(outputDir, $"{bookId}.jpg");
if (File.Exists(outputPath))
{
Debug.WriteLine($"Cover for book {bookId} already exists, skipping...");
return;
}
var response = await httpClient.GetAsync($"e-podreczniki/podglad/{bookId}/files/mobile/1.jpg", cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Found)
throw new BookMissingException(bookId);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsByteArrayAsync(cancellationToken);
Directory.CreateDirectory(outputDir);
await File.WriteAllBytesAsync(outputPath, content, cancellationToken);
Debug.WriteLine($"Downloaded cover for book {bookId}");
}
private static async Task DownloadBookJpg(uint bookId, string outputDir, CancellationToken cancellationToken = default)
{
var currentPage = 1;
while (true)
{
var outputPath = Path.Combine(outputDir, $"{currentPage}.jpg");
if (File.Exists(outputPath))
{
Debug.WriteLine($"Page {currentPage} of book {bookId} already exists, skipping...");
currentPage++;
continue;
}
var response = await httpClient.GetAsync($"e-podreczniki/podglad/{bookId}/files/mobile/{currentPage}.jpg", cancellationToken);
if (
(response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.NotFound) &&
currentPage > 1
)
{
Debug.WriteLine($"No more pages for book {bookId}, stopping...");
break;
} else if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Found)
throw new BookMissingException(bookId);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsByteArrayAsync(cancellationToken);
Directory.CreateDirectory(outputDir);
await File.WriteAllBytesAsync(outputPath, content, cancellationToken);
Debug.WriteLine($"Downloaded page {currentPage} of book {bookId}");
currentPage++;
}
}
private static void BuildPdf(uint bookId, string jpgDir, string outputPath)
{
var pages = Directory.GetFiles(jpgDir, "*.jpg")
.OrderBy(file => int.Parse(Path.GetFileNameWithoutExtension(file)))
.ToList();
using var document = new PdfDocument();
document.Options.FlateEncodeMode = PdfFlateEncodeMode.BestCompression;
document.Options.UseFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never;
foreach (var pagePath in pages)
{
using var stream = new MemoryStream(File.ReadAllBytes(pagePath));
using var image = XImage.FromStream(stream);
var pdfPage = document.AddPage();
pdfPage.Width = XUnit.FromPoint(image.PointWidth);
pdfPage.Height = XUnit.FromPoint(image.PointHeight);
using var gfx = XGraphics.FromPdfPage(pdfPage);
gfx.DrawImage(image, 0, 0, image.PointWidth, image.PointHeight);
Debug.WriteLine($"Added page {Path.GetFileNameWithoutExtension(pagePath)} to PDF for book {bookId}");
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
document.Save(outputPath);
}
}