diff --git a/.gitignore b/.gitignore index 4ce6fdde..e86e769a 100644 --- a/.gitignore +++ b/.gitignore @@ -337,4 +337,5 @@ ASALocalRun/ .localhistory/ # BeatPulse healthcheck temp database -healthchecksdb \ No newline at end of file +healthchecksdb +/Kyoo/TheTVDB-Credentials.json diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index 3b63fab5..188eb7d5 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -40,7 +40,7 @@ namespace Kyoo.InternalAPI return null; } - public void Scan(string folderPath) + public async void Scan(string folderPath) { string[] files = Directory.GetFiles(folderPath); @@ -50,7 +50,7 @@ namespace Kyoo.InternalAPI { Debug.WriteLine("&Should insert this: " + file); - string patern = @".*\\(?.+?) S(?\d+)E(?\d+)"; + string patern = config.GetValue("regex"); Regex regex = new Regex(patern, RegexOptions.IgnoreCase); Match match = regex.Match(file); @@ -63,7 +63,10 @@ namespace Kyoo.InternalAPI if(!libraryManager.IsShowRegistered(ShowPath)) { - Show show = metadataProvider.GetShowFromName(ShowTitle); + Show show = await metadataProvider.GetShowFromName(ShowTitle); + + Debug.WriteLine("&Show Name: " + show.Title + " Overview: " + show.Overview); + //long showID = libraryManager.RegisterShow(show); } } } diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index 2792d34c..258c896e 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -27,7 +27,7 @@ namespace Kyoo.InternalAPI string createStatement = @"CREATE TABLE shows( id INTEGER PRIMARY KEY UNIQUE, - uri TEXT UNIQUE, + slug TEXT UNIQUE, title TEXT, aliases TEXT, path TEXT, @@ -94,7 +94,7 @@ namespace Kyoo.InternalAPI CREATE TABLE libraries( id INTEGER PRIMARY KEY UNIQUE, - uri TEXT UNIQUE, + slug TEXT UNIQUE, name TEXT ); CREATE TABLE librariesLinks( @@ -106,7 +106,7 @@ namespace Kyoo.InternalAPI CREATE TABLE studios( id INTEGER PRIMARY KEY UNIQUE, - uri TEXT UNIQUE, + slug TEXT UNIQUE, name TEXT ); CREATE TABLE studiosLinks( @@ -118,7 +118,7 @@ namespace Kyoo.InternalAPI CREATE TABLE people( id INTEGER PRIMARY KEY UNIQUE, - uri TEXT UNIQUE, + slug TEXT UNIQUE, name TEXT, imgPrimary TEXT, externalIDs TEXT @@ -134,7 +134,7 @@ namespace Kyoo.InternalAPI CREATE TABLE genres( id INTEGER PRIMARY KEY UNIQUE, - uri TEXT UNIQUE, + slug TEXT UNIQUE, name TEXT ); CREATE TABLE genresLinks( diff --git a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs index 13a308b6..6c78d3b0 100644 --- a/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs +++ b/Kyoo/InternalAPI/MetadataProvider/IMetadataProvider.cs @@ -1,9 +1,12 @@ using Kyoo.Models; +using System.Threading.Tasks; namespace Kyoo.InternalAPI { public interface IMetadataProvider { - Show GetShowFromName(string showName); + Task GetShowFromID(string externalIDs); + + Task GetShowFromName(string showName); } } diff --git a/Kyoo/InternalAPI/MetadataProvider/Implementations/ProviderTheTvDB.cs b/Kyoo/InternalAPI/MetadataProvider/Implementations/ProviderTheTvDB.cs index b53920d9..110709da 100644 --- a/Kyoo/InternalAPI/MetadataProvider/Implementations/ProviderTheTvDB.cs +++ b/Kyoo/InternalAPI/MetadataProvider/Implementations/ProviderTheTvDB.cs @@ -1,12 +1,164 @@ using Kyoo.Models; +using Newtonsoft.Json; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Web; namespace Kyoo.InternalAPI.MetadataProvider { - public class ProviderTheTvDB : IMetadataProvider + public class ProviderTheTvDB : ProviderHelper, IMetadataProvider { - public Show GetShowFromName(string showName) + public override string Provider => "TvDB"; + + private struct DataTvDB { - throw new System.NotImplementedException(); + public string seriesName; + public string overview; + public string slug; + public string network; + public string status; + public int id; + public string firstAired; + public string banner; + public string[] aliases; + } + + + async Task Authentificate() + { + WebRequest request = WebRequest.Create("https://api.thetvdb.com/login"); + request.Method = "POST"; + request.Timeout = 12000; + request.ContentType = "application/json"; + + string json = "{ \"apikey\": \"IM2OXA8UHUIU0GH6\" }"; + byte[] bytes = Encoding.ASCII.GetBytes(json); + + request.ContentLength = bytes.Length; + + using (Stream stream = request.GetRequestStream()) + { + stream.Write(bytes, 0, bytes.Length); + } + + HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync(); + if(response.StatusCode == HttpStatusCode.OK) + { + Stream stream = response.GetResponseStream(); + using (StreamReader reader = new StreamReader(stream)) + { + string content = await reader.ReadToEndAsync(); + stream.Close(); + response.Close(); + + var obj = new { Token = "" }; + return JsonConvert.DeserializeAnonymousType(content, obj).Token; + } + } + else + Debug.WriteLine("&Couldn't authentificate in TheTvDB API.\nError status: " + response.StatusCode + " Message: " + response.StatusDescription); + + return null; + } + + + public async Task GetShowFromName(string showName) + { + string token = await Authentificate(); + Debug.WriteLine("&Sucess, token = " + token); + + if (token != null) + { + WebRequest request = WebRequest.Create("https://api.thetvdb.com/search/series?name=" + HttpUtility.HtmlEncode(showName)); + request.Method = "GET"; + request.Timeout = 12000; + request.ContentType = "application/json"; + request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token); + + HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync(); + + if (response.StatusCode == HttpStatusCode.OK) + { + Stream stream = response.GetResponseStream(); + using (StreamReader reader = new StreamReader(stream)) + { + string content = await reader.ReadToEndAsync(); + stream.Close(); + response.Close(); + + var model = new { data = new DataTvDB[0] }; + DataTvDB data = JsonConvert.DeserializeAnonymousType(content, model).data[0]; + + long? startYear = null; + if (!long.TryParse(data.firstAired?.Substring(4), out long year)) + startYear = year; + + Show show = new Show(-1, ToSlug(showName), data.seriesName, data.aliases?.ToList(), data.overview, null, startYear, null, null, null, null, null, null, string.Format("{0}={1}|", Provider, data.id)); + return await GetShowFromID(show.ExternalIDs) ?? show; + } + } + else + { + Debug.WriteLine("&TheTvDB Provider couldn't work for this show: " + showName + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription); + response.Close(); + } + } + + return new Show() { Slug = ToSlug(showName), Title = showName }; + } + + public async Task GetShowFromID(string externalIDs) + { + string id = GetId(externalIDs); + + if (id == null) + return null; + + string token = await Authentificate(); + + if (token == null) + return null; + + WebRequest request = WebRequest.Create("https://api.thetvdb.com/search/series/" + id); + request.Method = "GET"; + request.Timeout = 12000; + request.ContentType = "application/json"; + request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token); + + HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync(); + + if (response.StatusCode == HttpStatusCode.OK) + { + Stream stream = response.GetResponseStream(); + using (StreamReader reader = new StreamReader(stream)) + { + string content = await reader.ReadToEndAsync(); + stream.Close(); + response.Close(); + + var model = new { data = new DataTvDB[0] }; + DataTvDB data = JsonConvert.DeserializeAnonymousType(content, model).data[0]; + + long? startYear = null; + if (!long.TryParse(data.firstAired?.Substring(4), out long year)) + startYear = year; + + Show show = new Show(-1, ToSlug(showName), data.seriesName, data.aliases?.ToList(), data.overview, null, startYear, null, null, null, null, null, null, string.Format("TvDB={0}|", data.id)); + return await GetShowFromID(show.ExternalIDs) ?? show; + } + } + else + { + Debug.WriteLine("&TheTvDB Provider couldn't work for the show with the id: " + id + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription); + response.Close(); + return null; + } } } } diff --git a/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs b/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs new file mode 100644 index 00000000..188fcd7a --- /dev/null +++ b/Kyoo/InternalAPI/MetadataProvider/ProviderHelper.cs @@ -0,0 +1,48 @@ +using System.Text; +using System.Text.RegularExpressions; + +namespace Kyoo.InternalAPI.MetadataProvider +{ + public abstract class ProviderHelper + { + public abstract string Provider { get; } + + public string GetId(string externalIDs) + { + if (externalIDs.Contains(Provider)) + { + int startIndex = externalIDs.IndexOf(Provider) + Provider.Length; + return externalIDs.Substring(startIndex, externalIDs.IndexOf('|', startIndex) - startIndex); + } + else + return null; + } + + public string ToSlug(string showTitle) + { + if (showTitle == null) + return null; + + //First to lower case + showTitle = showTitle.ToLowerInvariant(); + + //Remove all accents + //var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(showTitle); + //showTitle = Encoding.ASCII.GetString(bytes); + + //Replace spaces + showTitle = Regex.Replace(showTitle, @"\s", "-", RegexOptions.Compiled); + + //Remove invalid chars + showTitle = Regex.Replace(showTitle, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled); + + //Trim dashes from end + showTitle = showTitle.Trim('-', '_'); + + //Replace double occurences of - or \_ + showTitle = Regex.Replace(showTitle, @"([-_]){2,}", "$1", RegexOptions.Compiled); + + return showTitle; + } + } +} diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj index fb62b937..be41c200 100644 --- a/Kyoo/Kyoo.csproj +++ b/Kyoo/Kyoo.csproj @@ -17,6 +17,7 @@ + diff --git a/Kyoo/Models/Show.cs b/Kyoo/Models/Show.cs index bfa2d0d0..cab131cb 100644 --- a/Kyoo/Models/Show.cs +++ b/Kyoo/Models/Show.cs @@ -5,9 +5,9 @@ namespace Kyoo.Models { public class Show { - public readonly long id; + public readonly long id = -1; - public string Uri; + public string Slug; public string Title; public List Aliases; public string Overview; @@ -24,11 +24,12 @@ namespace Kyoo.Models public string ExternalIDs; + public Show() { } - public Show(long id, string uri, string title, List aliases, string overview, Status? status, long? startYear, long? endYear, string imgPrimary, string imgThumb, string imgBanner, string imgLogo, string imgBackdrop, string externalIDs) + public Show(long id, string slug, string title, List aliases, string overview, Status? status, long? startYear, long? endYear, string imgPrimary, string imgThumb, string imgBanner, string imgLogo, string imgBackdrop, string externalIDs) { this.id = id; - Uri = uri; + Slug = slug; Title = title; Aliases = aliases; Overview = overview; @@ -46,7 +47,7 @@ namespace Kyoo.Models public static Show FromReader(System.Data.SQLite.SQLiteDataReader reader) { return new Show((long)reader["id"], - reader["uri"] as string, + reader["slug"] as string, reader["title"] as string, (reader["aliases"] as string)?.Split('|').ToList() ?? null, reader["overview"] as string, diff --git a/Kyoo/config.json b/Kyoo/config.json index 714f8dfa..1b3356a0 100644 --- a/Kyoo/config.json +++ b/Kyoo/config.json @@ -2,5 +2,6 @@ "databasePath": "C://Projects/database.db", "libraryPaths": [ "D:\\Videos" - ] + ], + "regex": ".*\\\\(?.+?) S(?\\d+)E(?\\d+)" }