Skip to content

Commit

Permalink
add Anime Saturn provider #53
Browse files Browse the repository at this point in the history
  • Loading branch information
insomniachi committed Aug 30, 2023
1 parent 8b180b1 commit 40ebde0
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 36 deletions.
2 changes: 1 addition & 1 deletion Totoro.Core/Contracts/IVideoStreamResolverFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Totoro.Core.Contracts;
public interface IVideoStreamResolverFactory
{
IVideoStreamModelResolver CreateAnimDLResolver(string providerType, string baseUrl);
IVideoStreamModelResolver CreateGogoAnimDLResolver(string providerType, string baseUrlSub, string baseUrlDub);
IVideoStreamModelResolver CreateSubDubResolver(string providerType, string baseUrlSub, string baseUrlDub);
Task<IVideoStreamModelResolver> CreateDebridStreamResolver(string magnet);
IVideoStreamModelResolver CreateMonoTorrentStreamResolver(IEnumerable<Element> parsedResults, string magnet);
IVideoStreamModelResolver CreateMonoTorrentStreamResolver(Torrent torrent);
Expand Down
1 change: 1 addition & 0 deletions Totoro.Core/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public static IServiceCollection AddPlugins(this IServiceCollection services)
PluginFactory<AnimeProvider>.Instance.LoadPlugin(new Plugins.Anime.YugenAnime.Plugin());
PluginFactory<AnimeProvider>.Instance.LoadPlugin(new Plugins.Anime.GogoAnime.Plugin());
PluginFactory<AnimeProvider>.Instance.LoadPlugin(new Plugins.Anime.Marin.Plugin());
PluginFactory<AnimeProvider>.Instance.LoadPlugin(new Plugins.Anime.AnimeSaturn.Plugin());

// torrents
PluginFactory<ITorrentTracker>.Instance.LoadPlugin(new Plugins.Torrents.Nya.Plugin());
Expand Down
56 changes: 28 additions & 28 deletions Totoro.Core/Services/StreamResolvers/AnimDLVideoStreamResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,59 +48,59 @@ public async Task<VideoStreamsForEpisodeModel> ResolveEpisode(int episode, Strea
return new VideoStreamsForEpisodeModel(results.First());
}

private async Task<List<VideoStreamsForEpisode>> GetStreams(int episode, StreamType streamType)
public async Task<int> GetNumberOfEpisodes(StreamType streamType)
{
try
{
var url = _baseUrlSub;
if (_settings.DefaultProviderType == "gogo-anime")
{
url = streamType is StreamType.EnglishDubbed ? _baseUrlDub : _baseUrlSub;
}

return _provider?.StreamProvider switch
var url = GetUrlForStreamType(streamType);
var count = _provider?.StreamProvider switch
{
IMultiLanguageAnimeStreamProvider mp => await mp.GetStreams(url, episode..episode, streamType).ToListAsync(),
IAnimeStreamProvider sp => await sp.GetStreams(url, episode..episode).ToListAsync(),
_ => new List<VideoStreamsForEpisode>()
IMultiLanguageAnimeStreamProvider mp => await mp.GetNumberOfStreams(url, streamType),
IAnimeStreamProvider sp => await sp.GetNumberOfStreams(url),
_ => 0
};

return count;
}
catch (Exception ex)
{
this.Log().Fatal(ex);
return new List<VideoStreamsForEpisode>();
return 0;
}
}

public async Task<int> GetNumberOfEpisodes(StreamType streamType)
public async Task<EpisodeModelCollection> ResolveAllEpisodes(StreamType streamType)
{
return EpisodeModelCollection.FromEpisodeCount(await GetNumberOfEpisodes(streamType));
}

private async Task<List<VideoStreamsForEpisode>> GetStreams(int episode, StreamType streamType)
{
try
{
var url = _baseUrlSub;
if (_settings.DefaultProviderType == "gogo-anime")
{
url = streamType is StreamType.EnglishDubbed ? _baseUrlDub : _baseUrlSub;
}

var count = _provider?.StreamProvider switch
var url = GetUrlForStreamType(streamType);
return _provider?.StreamProvider switch
{
IMultiLanguageAnimeStreamProvider mp => await mp.GetNumberOfStreams(url, streamType),
IAnimeStreamProvider sp => await sp.GetNumberOfStreams(url),
_ => 0
IMultiLanguageAnimeStreamProvider mp => await mp.GetStreams(url, episode..episode, streamType).ToListAsync(),
IAnimeStreamProvider sp => await sp.GetStreams(url, episode..episode).ToListAsync(),
_ => new List<VideoStreamsForEpisode>()
};

return count;
}
catch (Exception ex)
{
this.Log().Fatal(ex);
return 0;
return new List<VideoStreamsForEpisode>();
}
}

public async Task<EpisodeModelCollection> ResolveAllEpisodes(StreamType streamType)
private string GetUrlForStreamType(StreamType streamType)
{
return EpisodeModelCollection.FromEpisodeCount(await GetNumberOfEpisodes(streamType));
return _settings.DefaultProviderType switch
{
"gogo-anime" => streamType is StreamType.EnglishDubbed ? _baseUrlDub : _baseUrlSub,
"anime-saturn" => streamType is StreamType.ItalianDubbed ? _baseUrlDub : _baseUrlSub,
_ => _baseUrlSub
};
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public IVideoStreamModelResolver CreateAnimDLResolver(string providerType, strin
return new AnimDLVideoStreamResolver(_providerFactory.CreatePlugin(providerType), _settings, baseUrl);
}

public IVideoStreamModelResolver CreateGogoAnimDLResolver(string providerType, string baseUrlSub, string baseUrlDub)
public IVideoStreamModelResolver CreateSubDubResolver(string providerType, string baseUrlSub, string baseUrlDub)
{
return new AnimDLVideoStreamResolver(_providerFactory.CreatePlugin(providerType), _settings, baseUrlSub, baseUrlDub);
}
Expand Down
1 change: 1 addition & 0 deletions Totoro.Core/Totoro.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<ProjectReference Include="..\Totoro.Plugins.Anime.YugenAnime\Totoro.Plugins.Anime.YugenAnime.csproj" />
<ProjectReference Include="..\Totoro.Plugins.Anime.GogoAnime\Totoro.Plugins.Anime.GogoAnime.csproj" />
<ProjectReference Include="..\Totoro.Plugins.Anime.Marin\Totoro.Plugins.Anime.Marin.csproj" />
<ProjectReference Include="..\Totoro.Plugins.Anime.AnimeSaturn\Totoro.Plugins.Anime.AnimeSaturn.csproj" />
</ItemGroup>

<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
13 changes: 8 additions & 5 deletions Totoro.Core/ViewModels/WatchViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public partial class WatchViewModel : NavigatableViewModel
private readonly IVideoStreamResolverFactory _videoStreamResolverFactory;
private readonly IMyAnimeListService _myAnimeListService;
private readonly List<IMediaEventListener> _mediaEventListeners;

private readonly string[] _subDubProviders = new [] { "gogo-anime", "anime-saturn" };

private PluginOptions _providerOptions;
private bool _isCrunchyroll;
private IEnumerable<EpisodeModel> _episodeMetadata;
Expand Down Expand Up @@ -103,7 +104,9 @@ public WatchViewModel(IPluginFactory<AnimeProvider> providerFactory,
if (hasSubDub)
{
await CreateAnimDLResolver(x.Sub.Url, x.Dub.Url);
SubStreams = new List<StreamType>() { StreamType.EnglishSubbed, StreamType.EnglishDubbed };
SubStreams = ProviderType is "gogo"
? new List<StreamType>() { StreamType.EnglishSubbed, StreamType.EnglishDubbed }
: new List<StreamType>() { StreamType.ItalianSubbed, StreamType.ItalianDubbed };
SelectedAudioStream = _settings.PreferSubs ? SubStreams.First() : SubStreams.Last();
}
else
Expand Down Expand Up @@ -176,7 +179,7 @@ public WatchViewModel(IPluginFactory<AnimeProvider> providerFactory,
.WhereNotNull()
.Do(stream =>
{
if (ProviderType != "gogo-anime")
if (!_subDubProviders.Contains(ProviderType))
{
var selectedAudioStream = SelectedAudioStream;
SubStreams = stream.StreamTypes;
Expand Down Expand Up @@ -447,7 +450,7 @@ private void OnSubmitTimeStamps()
{
return (results[0], null);
}
else if (results.Count == 2 && ProviderType is "gogo-anime") // gogo anime has separate listing for sub/dub
else if (results.Count == 2 && _subDubProviders.Contains(ProviderType)) // separate listing for sub/dub
{
return (results[0], results[1]);
}
Expand Down Expand Up @@ -644,7 +647,7 @@ private async Task CreateAnimDLResolver(string url)

private async Task CreateAnimDLResolver(string sub, string dub)
{
_videoStreamResolver = _videoStreamResolverFactory.CreateGogoAnimDLResolver(ProviderType, sub, dub);
_videoStreamResolver = _videoStreamResolverFactory.CreateSubDubResolver(ProviderType, sub, dub);
EpisodeModels = await _videoStreamResolver.ResolveAllEpisodes(SelectedAudioStream.Value);
}

Expand Down
34 changes: 34 additions & 0 deletions Totoro.Plugins.Anime.AnimeSaturn.Tests/CatalogTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Xunit.Abstractions;

namespace Totoro.Plugins.Anime.AnimeSaturn.Tests;

[ExcludeFromCodeCoverage]
public class CatalogTests
{
private readonly ITestOutputHelper _output;
private readonly JsonSerializerOptions _searializerOption = new() { WriteIndented = true };

public CatalogTests(ITestOutputHelper output)
{
_output = output;
}

[Theory]
[InlineData("hyouka")]
public async Task Search(string query)
{
// arrange
var sut = new Catalog();

// act
var result = await sut.Search(query).ToListAsync();

Assert.NotEmpty(result);
foreach (var item in result)
{
_output.WriteLine(JsonSerializer.Serialize(item, item.GetType(), _searializerOption));
}
}
}
1 change: 1 addition & 0 deletions Totoro.Plugins.Anime.AnimeSaturn.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
58 changes: 58 additions & 0 deletions Totoro.Plugins.Anime.AnimeSaturn.Tests/StreamProviderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Flurl;
using Xunit.Abstractions;

namespace Totoro.Plugins.Anime.AnimeSaturn.Tests;

[ExcludeFromCodeCoverage]
public class StreamProviderTests
{
public const string Hyouka = "hyouka";

private readonly ITestOutputHelper _output;
private readonly JsonSerializerOptions _searializerOption = new() { WriteIndented = true };
private readonly Dictionary<string, string> _urlMap = new()
{
{ Hyouka, Url.Combine(Config.Url, "/anime/Hyouka-aaaaaa") }
};
private readonly bool _allEpisodes = false;

public StreamProviderTests(ITestOutputHelper output)
{
_output = output;
}

[Theory]
[InlineData(Hyouka, 22)]
public async Task GetNumberOfEpisodes(string key, int expected)
{
// arrange
var url = _urlMap[key];
var sut = new StreamProvider();

// act
var actual = await sut.GetNumberOfStreams(url);

// assert
Assert.Equal(expected, actual);
}

[Theory]
[InlineData(Hyouka)]
public async Task GetStreams(string key)
{
// arrange
var url = _urlMap[key];
var sut = new StreamProvider();

// act
var result = await sut.GetStreams(url, _allEpisodes ? Range.All : 1..1).ToListAsync();

Assert.NotEmpty(result);
foreach (var item in result)
{
_output.WriteLine(JsonSerializer.Serialize(item, item.GetType(), _searializerOption));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>x64</Platforms>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Totoro.Plugins.Anime.AnimeSaturn\Totoro.Plugins.Anime.AnimeSaturn.csproj" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions Totoro.Plugins.Anime.AnimeSaturn/Catalog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Totoro.Plugins.Anime.Contracts;
using Totoro.Plugins.Contracts.Optional;

namespace Totoro.Plugins.Anime.AnimeSaturn;

internal class Catalog : IAnimeCatalog
{
class SearchResult : ICatalogItem, IHaveImage
{
required public string Title { get; init; }
required public string Url { get; init; }
required public string Image { get; init; }
}

public async IAsyncEnumerable<ICatalogItem> Search(string query)
{
var response = await Config.Url
.AppendPathSegment("/index.php")
.SetQueryParams(new
{
search = 1,
key = query
})
.GetStringAsync();

foreach (var item in JsonNode.Parse(response)?.AsArray() ?? new JsonArray())
{
var title = item!["name"]!.ToString();
var image = item!["image"]!.ToString();
var link = Url.Combine(Config.Url, "anime", item!["link"]!.ToString());

yield return new SearchResult
{
Title = title,
Image = image,
Url = link
};
}
}
}
12 changes: 12 additions & 0 deletions Totoro.Plugins.Anime.AnimeSaturn/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Totoro.Plugins.Anime.AnimeSaturn;

internal class Config
{
internal static string Url { get; set; } = "https://www.animesaturn.tv/";
}
Loading

0 comments on commit 40ebde0

Please sign in to comment.