initial commit
This commit is contained in:
BIN
.vs/LetterBoxdUnwatchedGenerator/DesignTimeBuild/.dtbcache.v2
Normal file
BIN
.vs/LetterBoxdUnwatchedGenerator/DesignTimeBuild/.dtbcache.v2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.vs/LetterBoxdUnwatchedGenerator/v17/.suo
Normal file
BIN
.vs/LetterBoxdUnwatchedGenerator/v17/.suo
Normal file
Binary file not shown.
25
LetterBoxdUnwatchedGenerator.sln
Normal file
25
LetterBoxdUnwatchedGenerator.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.6.33723.286
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LetterBoxdUnwatchedGenerator", "LetterBoxdUnwatchedGenerator\LetterBoxdUnwatchedGenerator.csproj", "{AA177F53-376A-40C9-B372-BA5FD25FEAD7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AA177F53-376A-40C9-B372-BA5FD25FEAD7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F57C2260-910C-480D-A2FF-22FF5A6EEDE7}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
91
LetterBoxdUnwatchedGenerator/CommandLineSplit.cs
Normal file
91
LetterBoxdUnwatchedGenerator/CommandLineSplit.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
internal static class CommandLineSplitExt
|
||||
{
|
||||
|
||||
//public static IEnumerable<string> SplitCommandLine(this string commandLine, char splitChar = ' ')
|
||||
//{
|
||||
// bool inQuotes = false;
|
||||
// bool isEscaping = false;
|
||||
|
||||
// StringBuilder currentArg = new StringBuilder();
|
||||
|
||||
// foreach (char c in commandLine)
|
||||
// {
|
||||
// if (c == '\\' && !isEscaping) { isEscaping = true; continue; }
|
||||
// if (c == '\"' && !isEscaping) inQuotes = !inQuotes;
|
||||
// isEscaping = false;
|
||||
|
||||
// if (!inQuotes && (splitChar == ' ' ? Char.IsWhiteSpace(c) : c == splitChar))
|
||||
// {
|
||||
// if (currentArg.Length > 0)
|
||||
// {
|
||||
// yield return currentArg.ToString();
|
||||
// currentArg.Clear();
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// currentArg.Append(c);
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (currentArg.Length > 0)
|
||||
// {
|
||||
// yield return currentArg.ToString();
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
public static IEnumerable<string> SplitCommandLine(this string commandLine, char splitChar = ' ')
|
||||
{
|
||||
bool inQuotes = false;
|
||||
bool isEscaping = false;
|
||||
|
||||
return commandLine.Split(c =>
|
||||
{
|
||||
if (c == '\\' && !isEscaping) { isEscaping = true; return false; }
|
||||
if (c == '\"' && !isEscaping) inQuotes = !inQuotes;
|
||||
isEscaping = false;
|
||||
|
||||
return !inQuotes && (splitChar == ' ' ? Char.IsWhiteSpace(c) : c == splitChar); //Char.IsWhiteSpace(c)/*c == ' '*/;
|
||||
}).Select(arg => arg.Trim().TrimMatchingQuotes('\"').Replace("\\\"", "\""));
|
||||
}
|
||||
|
||||
public static IEnumerable<string> Split(this string str, Func<char, bool> controller)
|
||||
{
|
||||
int nextPiece = 0;
|
||||
|
||||
for (int c = 0; c < str.Length; c++)
|
||||
{
|
||||
if (controller(str[c]))
|
||||
{
|
||||
yield return str.Substring(nextPiece, c - nextPiece);
|
||||
nextPiece = c + 1;
|
||||
}
|
||||
}
|
||||
|
||||
yield return str.Substring(nextPiece);
|
||||
}
|
||||
|
||||
public static string TrimMatchingQuotes(this string input, char quote)
|
||||
{
|
||||
if ((input.Length >= 2) &&
|
||||
(input[0] == quote) && (input[input.Length - 1] == quote))
|
||||
return input.Substring(1, input.Length - 2);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
public static string JoinCommandLine(this IEnumerable<string> arguments, char joinChar = ' ')
|
||||
{
|
||||
return string.Join(joinChar, arguments.Select(a => a.Contains(joinChar) ? $"\"{a}\"" : a));
|
||||
}
|
||||
}
|
||||
}
|
||||
8
LetterBoxdUnwatchedGenerator/IMovie.cs
Normal file
8
LetterBoxdUnwatchedGenerator/IMovie.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
public interface IMovie
|
||||
{
|
||||
public string Title { get; }
|
||||
public int Year { get; }
|
||||
}
|
||||
}
|
||||
25
LetterBoxdUnwatchedGenerator/LetterBoxdConnection.cs
Normal file
25
LetterBoxdUnwatchedGenerator/LetterBoxdConnection.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
public class LetterBoxdConnection
|
||||
{
|
||||
const string baseUrl = "https://letterboxd.com/api/v0/";
|
||||
HttpClient httpClient;
|
||||
|
||||
public LetterBoxdConnection()
|
||||
{
|
||||
//Authentication
|
||||
httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
//add url form encoded content
|
||||
var results = httpClient.PostAsync(baseUrl + "auth/token", new StringContent("grant_type=password&username=USERNAME&password=PASSWORD", MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded"))); //, Encoding.UTF8, "application/x-www-form-urlencoded"));
|
||||
|
||||
results.Result.EnsureSuccessStatusCode();
|
||||
var bearer = results.Result.Content.ReadAsStringAsync().Result;
|
||||
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
37
LetterBoxdUnwatchedGenerator/LetterBoxdWatchedMovie.cs
Normal file
37
LetterBoxdUnwatchedGenerator/LetterBoxdWatchedMovie.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
public class LetterBoxdWatchedMovie : IMovie
|
||||
{
|
||||
public DateTime Date { get; }
|
||||
public string Title { get; }
|
||||
public int Year { get; }
|
||||
public string LetterboxdUri { get; }
|
||||
|
||||
public LetterBoxdWatchedMovie(DateTime date, string title, int year, string letterboxdUri)
|
||||
{
|
||||
Date = date;
|
||||
Title = title;
|
||||
Year = year;
|
||||
LetterboxdUri = letterboxdUri;
|
||||
}
|
||||
|
||||
public static IEnumerable<LetterBoxdWatchedMovie> FromFile(string path)
|
||||
{
|
||||
var lines = File.ReadAllLines(path);
|
||||
|
||||
if (lines.Length < 1 || !lines[0].StartsWith("Date,Name,Year,Letterboxd URI")) return Enumerable.Empty<LetterBoxdWatchedMovie>();
|
||||
|
||||
return lines.Skip(1).Select(line =>
|
||||
{
|
||||
var lineParts = line.SplitCommandLine(',');
|
||||
|
||||
var date = !string.IsNullOrEmpty(lineParts.First()) && DateTime.TryParse(lineParts.First(), out DateTime dateOut) ? dateOut : DateTime.MinValue;
|
||||
var title = lineParts.Skip(1).First();
|
||||
var year = !string.IsNullOrEmpty(lineParts.Skip(2).First()) && int.TryParse(lineParts.Skip(2).First(), out int yearOut) ? yearOut : 0;
|
||||
var letterboxdUri = lineParts.Skip(3).First();
|
||||
|
||||
return new LetterBoxdWatchedMovie(date, title, year, letterboxdUri);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
58
LetterBoxdUnwatchedGenerator/LocalMovie.cs
Normal file
58
LetterBoxdUnwatchedGenerator/LocalMovie.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
public class LocalMovie : IMovie
|
||||
{
|
||||
public string Title { get; }
|
||||
public int Year { get; }
|
||||
public string? Resolution { get; }
|
||||
public string Directory { get; }
|
||||
|
||||
public LocalMovie(string title, int year, string? resolution, string directory)
|
||||
{
|
||||
Title = title;
|
||||
Year = year;
|
||||
Resolution = resolution;
|
||||
Directory = directory;
|
||||
}
|
||||
public static LocalMovie FromDirectory(string directoryName, string directoryFullName)
|
||||
{
|
||||
string title;
|
||||
int? year;
|
||||
string? resolution;
|
||||
|
||||
var yearIndex = directoryName.LastIndexOf('(');
|
||||
var resolutionIndex = directoryName.LastIndexOf('[');
|
||||
if (yearIndex == -1)
|
||||
{
|
||||
year = null;
|
||||
if (resolutionIndex == -1)
|
||||
{
|
||||
resolution = null;
|
||||
title = directoryName;
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = directoryName.Substring(resolutionIndex + 1, directoryName.LastIndexOf(']') - resolutionIndex - 1);
|
||||
title = directoryName[..(resolutionIndex - 1)].Trim();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (int.TryParse(directoryName.AsSpan(yearIndex + 1, directoryName.LastIndexOf(')') - yearIndex - 1), out int yearConverted)) year = yearConverted;
|
||||
else year = null;
|
||||
if (resolutionIndex == -1)
|
||||
{
|
||||
resolution = null;
|
||||
title = directoryName[..(yearIndex - 1)].Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
resolution = directoryName.Substring(resolutionIndex + 1, directoryName.LastIndexOf(']') - resolutionIndex - 1);
|
||||
title = directoryName[..(yearIndex - 1)].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return new LocalMovie(title, year ?? 0, resolution, directoryFullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
LetterBoxdUnwatchedGenerator/Program.cs
Normal file
39
LetterBoxdUnwatchedGenerator/Program.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Text;
|
||||
|
||||
namespace LetterBoxdUnwatchedGenerator
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
DirectoryInfo di = new DirectoryInfo(@"A:\Video\Movies\All");
|
||||
var localMovies = di.EnumerateDirectories().Select(di => LocalMovie.FromDirectory(di.Name, di.FullName)).ToList();
|
||||
|
||||
var watchedMovies = LetterBoxdWatchedMovie.FromFile(@"A:\homes\@DH-ARNEHOME\0\arne-1103\Documents\Inactive\2023-07-01 Letterboxd download\letterboxd-thearne4-2023-07-01-10-32-utc\watched.csv");
|
||||
|
||||
var unwatchedMovies = localMovies.Where(lm => !watchedMovies.Any(wm => lm.Title.Replace("-", "").Replace("(", "").Replace(")", "").Replace(" ", "").ToLowerInvariant().EndsWith(wm.Title.Replace("\\", "").Replace("/", "").Replace(":", "").Replace("\"", "").Replace("?", "").Replace("<", "").Replace(">", "").Replace("|", "").Replace("-", "").Replace(" ", "").ToLowerInvariant()) && wm.Year == lm.Year));
|
||||
|
||||
WriteToCsv(@"A:\homes\@DH-ARNEHOME\0\arne-1103\Documents\Inactive\2023-07-01 Letterboxd download\local-unwatched.csv", unwatchedMovies);
|
||||
|
||||
//unwatchedMovies.ForEach(um => Console.WriteLine(um.Title));
|
||||
|
||||
Console.WriteLine("-------------");
|
||||
Console.WriteLine($"Total: {unwatchedMovies.Count()}");
|
||||
Console.ReadKey();
|
||||
}
|
||||
static void WriteToCsv(string path, IEnumerable<IMovie> unwatchedMovies)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine("Title,Year");
|
||||
foreach (IMovie movie in unwatchedMovies)
|
||||
{
|
||||
var title = movie.Title.Contains(',') ? $"\"{movie.Title}\"" : movie.Title;
|
||||
var year = movie.Year == 0 ? "" : movie.Year.ToString();
|
||||
|
||||
sb.AppendLine($"{title},{year}");
|
||||
}
|
||||
|
||||
File.WriteAllText(path, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user