mirror of
https://github.com/mxpv/podsync.git
synced 2024-05-11 05:55:04 +00:00
Add Patreon integration
This commit is contained in:
@@ -23,6 +23,7 @@ namespace Podsync.Controllers
|
||||
}
|
||||
|
||||
// Main video download endpoint, don't forget to reflect any changes in LinkService.Download
|
||||
[HttpGet]
|
||||
[Route("{feedId}/{videoId}/")]
|
||||
public async Task<IActionResult> Download(string feedId, string videoId)
|
||||
{
|
||||
@@ -31,7 +32,7 @@ namespace Podsync.Controllers
|
||||
var url = _linkService.Make(new LinkInfo
|
||||
{
|
||||
Provider = metadata.Provider,
|
||||
LinkType = metadata.LinkType,
|
||||
LinkType = LinkType.Video,
|
||||
Id = videoId
|
||||
});
|
||||
|
||||
|
@@ -50,6 +50,12 @@ namespace Podsync.Controllers
|
||||
PageSize = request.PageSize ?? DefaultPageSize
|
||||
};
|
||||
|
||||
if (!User.EnablePatreonFeatures())
|
||||
{
|
||||
feed.Quality = ResolveType.VideoHigh;
|
||||
feed.PageSize = DefaultPageSize;
|
||||
}
|
||||
|
||||
return _storageService.Save(feed);
|
||||
}
|
||||
|
||||
|
46
src/Podsync/Helpers/UserExtensions.cs
Normal file
46
src/Podsync/Helpers/UserExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Podsync.Services.Patreon;
|
||||
|
||||
namespace Podsync.Helpers
|
||||
{
|
||||
public static class UserExtensions
|
||||
{
|
||||
private const string OwnerId = "2822191";
|
||||
|
||||
public static bool EnablePatreonFeatures(this ClaimsPrincipal user)
|
||||
{
|
||||
if (!user.Identity.IsAuthenticated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.GetClaim(ClaimTypes.NameIdentifier) == OwnerId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int MinAmountCents = 100;
|
||||
|
||||
int amount;
|
||||
if (int.TryParse(user.GetClaim(PatreonConstants.AmountDonated), out amount))
|
||||
{
|
||||
return amount >= MinAmountCents;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetName(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
return claimsPrincipal.GetClaim(ClaimTypes.Name)
|
||||
?? claimsPrincipal.GetClaim(ClaimTypes.Email)
|
||||
?? "noname :(";
|
||||
}
|
||||
|
||||
private static string GetClaim(this ClaimsPrincipal claimsPrincipal, string type)
|
||||
{
|
||||
return claimsPrincipal.Claims.FirstOrDefault(x => x.Type == type)?.Value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,11 +21,11 @@ namespace Podsync.Services.Builder
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override Task<Rss> Query(FeedMetadata feed)
|
||||
public override Task<Rss> Query(string feedId, FeedMetadata feed)
|
||||
{
|
||||
if (feed.Provider == Provider.YouTube)
|
||||
{
|
||||
return _youTubeBuilder.Query(feed);
|
||||
return _youTubeBuilder.Query(feedId, feed);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("Not supported provider");
|
||||
|
@@ -8,6 +8,6 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
Task<Rss> Query(string feedId);
|
||||
|
||||
Task<Rss> Query(FeedMetadata feed);
|
||||
Task<Rss> Query(string feedId, FeedMetadata feed);
|
||||
}
|
||||
}
|
@@ -20,9 +20,9 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
var metadata = await _storageService.Load(feedId);
|
||||
|
||||
return await Query(metadata);
|
||||
return await Query(feedId, metadata);
|
||||
}
|
||||
|
||||
public abstract Task<Rss> Query(FeedMetadata metadata);
|
||||
public abstract Task<Rss> Query(string feedId, FeedMetadata metadata);
|
||||
}
|
||||
}
|
@@ -24,7 +24,7 @@ namespace Podsync.Services.Builder
|
||||
|
||||
public override Provider Provider { get; } = Provider.YouTube;
|
||||
|
||||
public override async Task<Rss> Query(FeedMetadata metadata)
|
||||
public override async Task<Rss> Query(string feedId, FeedMetadata metadata)
|
||||
{
|
||||
if (metadata.Provider != Provider.YouTube)
|
||||
{
|
||||
@@ -57,7 +57,7 @@ namespace Podsync.Services.Builder
|
||||
// Get video descriptions
|
||||
var videos = await _youTube.GetVideos(new VideoQuery { Id = string.Join(",", ids) });
|
||||
|
||||
channel.Items = videos.Select(x => MakeItem(x, metadata));
|
||||
channel.Items = videos.Select(x => MakeItem(x, feedId, metadata));
|
||||
|
||||
var rss = new Rss
|
||||
{
|
||||
@@ -103,7 +103,7 @@ namespace Podsync.Services.Builder
|
||||
};
|
||||
}
|
||||
|
||||
private Item MakeItem(Video video, FeedMetadata feed)
|
||||
private Item MakeItem(Video video, string feedId, FeedMetadata feed)
|
||||
{
|
||||
return new Item
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace Podsync.Services.Builder
|
||||
{
|
||||
Length = video.Size,
|
||||
MediaType = SelectMediaType(feed.Quality),
|
||||
Url = _linkService.Download(feed.Id, video.VideoId)
|
||||
Url = _linkService.Download(feedId, video.VideoId)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
17
src/Podsync/Services/Patreon/Contracts/Pledge.cs
Normal file
17
src/Podsync/Services/Patreon/Contracts/Pledge.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Podsync.Services.Patreon.Contracts
|
||||
{
|
||||
public class Pledge
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
public DateTime DeclinedSince { get; set; }
|
||||
|
||||
public int AmountCents { get; set; }
|
||||
|
||||
public int PledgeCapCents { get; set; }
|
||||
}
|
||||
}
|
23
src/Podsync/Services/Patreon/Contracts/User.cs
Normal file
23
src/Podsync/Services/Patreon/Contracts/User.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Podsync.Services.Patreon.Contracts
|
||||
{
|
||||
public class User
|
||||
{
|
||||
public User()
|
||||
{
|
||||
Pledges = Enumerable.Empty<Pledge>();
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
public IEnumerable<Pledge> Pledges { get; set; }
|
||||
}
|
||||
}
|
10
src/Podsync/Services/Patreon/IPatreonApi.cs
Normal file
10
src/Podsync/Services/Patreon/IPatreonApi.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Podsync.Services.Patreon
|
||||
{
|
||||
public interface IPatreonApi : IDisposable
|
||||
{
|
||||
Task<dynamic> FetchProfile(Tokens tokens);
|
||||
}
|
||||
}
|
46
src/Podsync/Services/Patreon/PatreonApi.cs
Normal file
46
src/Podsync/Services/Patreon/PatreonApi.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Podsync.Services.Patreon
|
||||
{
|
||||
public sealed class PatreonApi : IPatreonApi
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public PatreonApi()
|
||||
{
|
||||
_client = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri("https://api.patreon.com/oauth2/api/")
|
||||
};
|
||||
|
||||
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer");
|
||||
}
|
||||
|
||||
public Task<dynamic> FetchProfile(Tokens tokens)
|
||||
{
|
||||
return Query("current_user", tokens);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client.Dispose();
|
||||
}
|
||||
|
||||
private async Task<dynamic> Query(string path, Tokens tokens)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, path);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
|
||||
|
||||
var response = await _client.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return JObject.Parse(json);
|
||||
}
|
||||
}
|
||||
}
|
13
src/Podsync/Services/Patreon/PatreonConstants.cs
Normal file
13
src/Podsync/Services/Patreon/PatreonConstants.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Podsync.Services.Patreon
|
||||
{
|
||||
public static class PatreonConstants
|
||||
{
|
||||
public const string AuthenticationScheme = "Patreon";
|
||||
|
||||
public const string AuthorizationEndpoint = "https://www.patreon.com/oauth2/authorize";
|
||||
|
||||
public const string TokenEndpoint = "https://api.patreon.com/oauth2/token";
|
||||
|
||||
public const string AmountDonated = "Patreon/" + nameof(AmountDonated);
|
||||
}
|
||||
}
|
72
src/Podsync/Services/Patreon/PatreonExtensions.cs
Normal file
72
src/Podsync/Services/Patreon/PatreonExtensions.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Podsync.Services.Patreon.Contracts;
|
||||
|
||||
namespace Podsync.Services.Patreon
|
||||
{
|
||||
public static class PatreonExtensions
|
||||
{
|
||||
public static async Task<User> FetchUserAndPledges(this IPatreonApi api, Tokens tokens)
|
||||
{
|
||||
var resp = await api.FetchProfile(tokens);
|
||||
|
||||
dynamic userAttrs = resp.data.attributes;
|
||||
|
||||
var user = new User
|
||||
{
|
||||
Id = resp.data.id,
|
||||
Email = userAttrs.email,
|
||||
Name = userAttrs.first_name ?? userAttrs.full_name,
|
||||
Url = userAttrs.url,
|
||||
Pledges = ParsePledges(resp)
|
||||
};
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static IEnumerable<Pledge> ParsePledges(dynamic resp)
|
||||
{
|
||||
dynamic pledges = resp.data.relationships.pledges.data;
|
||||
|
||||
foreach (var pledge in pledges)
|
||||
{
|
||||
var id = pledge.id;
|
||||
var type = pledge.type;
|
||||
|
||||
foreach (var include in resp.included)
|
||||
{
|
||||
if (include.id == id && include.type == type)
|
||||
{
|
||||
dynamic attrs = include.attributes;
|
||||
|
||||
yield return new Pledge
|
||||
{
|
||||
Id = include.id,
|
||||
CreatedAt = ParseDate(attrs.created_at),
|
||||
DeclinedSince = ParseDate(attrs.declined_since),
|
||||
AmountCents = attrs.amount_cents,
|
||||
PledgeCapCents = attrs.pledge_cap_cents
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime ParseDate(object obj)
|
||||
{
|
||||
var date = obj?.ToString();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(date))
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
|
||||
var dateTime = DateTime.Parse(date);
|
||||
|
||||
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
}
|
9
src/Podsync/Services/Patreon/Tokens.cs
Normal file
9
src/Podsync/Services/Patreon/Tokens.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Podsync.Services.Patreon
|
||||
{
|
||||
public struct Tokens
|
||||
{
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
public string RefreshToken { get; set; }
|
||||
}
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
namespace Podsync.Services
|
||||
using Podsync.Services.Patreon;
|
||||
|
||||
namespace Podsync.Services
|
||||
{
|
||||
public class PodsyncConfiguration
|
||||
{
|
||||
@@ -7,5 +9,11 @@
|
||||
public string RedisConnectionString { get; set; }
|
||||
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
public string PatreonClientId { get; set; }
|
||||
|
||||
public string PatreonSecret { get; set; }
|
||||
|
||||
public Tokens CreatorTokens { get; set; }
|
||||
}
|
||||
}
|
@@ -1,11 +1,18 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Authentication;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Podsync.Services;
|
||||
using Podsync.Services.Builder;
|
||||
using Podsync.Services.Links;
|
||||
using Podsync.Services.Patreon;
|
||||
using Podsync.Services.Resolver;
|
||||
using Podsync.Services.Storage;
|
||||
using Podsync.Services.Videos.YouTube;
|
||||
@@ -43,8 +50,12 @@ namespace Podsync
|
||||
services.AddSingleton<IResolverService, YtdlWrapper>();
|
||||
services.AddSingleton<IStorageService, RedisStorage>();
|
||||
services.AddSingleton<IRssBuilder, CompositeRssBuilder>();
|
||||
services.AddSingleton<IPatreonApi, PatreonApi>();
|
||||
|
||||
// Add framework services.
|
||||
// Add authentication services
|
||||
services.AddAuthentication(config => config.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// Add framework services
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
@@ -62,6 +73,77 @@ namespace Podsync
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseCookieAuthentication(new CookieAuthenticationOptions
|
||||
{
|
||||
AutomaticAuthenticate = true,
|
||||
AutomaticChallenge = true,
|
||||
LoginPath = new PathString("/login"),
|
||||
LogoutPath = new PathString("/logout")
|
||||
});
|
||||
|
||||
// Patreon authentication
|
||||
app.UseOAuthAuthentication(new OAuthOptions
|
||||
{
|
||||
AuthenticationScheme = PatreonConstants.AuthenticationScheme,
|
||||
|
||||
ClientId = Configuration[$"Podsync:{nameof(PodsyncConfiguration.PatreonClientId)}"],
|
||||
ClientSecret = Configuration[$"Podsync:{nameof(PodsyncConfiguration.PatreonSecret)}"],
|
||||
|
||||
CallbackPath = new PathString("/oauth-patreon"),
|
||||
|
||||
AuthorizationEndpoint = PatreonConstants.AuthorizationEndpoint,
|
||||
TokenEndpoint = PatreonConstants.TokenEndpoint,
|
||||
|
||||
SaveTokens = true,
|
||||
|
||||
Scope = { "users", "pledges-to-me", "my-campaign" },
|
||||
|
||||
Events = new OAuthEvents
|
||||
{
|
||||
OnCreatingTicket = async context =>
|
||||
{
|
||||
var patreonApi = app.ApplicationServices.GetService<IPatreonApi>();
|
||||
|
||||
var tokens = new Tokens
|
||||
{
|
||||
AccessToken = context.AccessToken,
|
||||
RefreshToken = context.RefreshToken
|
||||
};
|
||||
|
||||
var user = await patreonApi.FetchUserAndPledges(tokens);
|
||||
|
||||
context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
|
||||
context.Identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
|
||||
context.Identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
|
||||
context.Identity.AddClaim(new Claim(ClaimTypes.Uri, user.Url));
|
||||
|
||||
var amountCents = user.Pledges.Sum(x => x.AmountCents);
|
||||
context.Identity.AddClaim(new Claim(PatreonConstants.AmountDonated, amountCents.ToString()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.Map("/login", builder =>
|
||||
{
|
||||
builder.Run(async context =>
|
||||
{
|
||||
// Return a challenge to invoke the Patreon authentication scheme
|
||||
await context.Authentication.ChallengeAsync(PatreonConstants.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
|
||||
});
|
||||
});
|
||||
|
||||
app.Map("/logout", builder =>
|
||||
{
|
||||
builder.Run(async context =>
|
||||
{
|
||||
// Sign the user out of the authentication middleware (i.e. it will clear the Auth cookie)
|
||||
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
// Redirect the user to the home page after signing out
|
||||
context.Response.Redirect("/");
|
||||
});
|
||||
});
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
|
||||
|
@@ -1,4 +1,8 @@
|
||||
|
||||
@using Podsync.Helpers
|
||||
@{
|
||||
var enableFeatures = User.EnablePatreonFeatures();
|
||||
}
|
||||
|
||||
<div class="title">
|
||||
<h1>Podsync</h1>
|
||||
|
||||
@@ -8,16 +12,22 @@
|
||||
</h2>
|
||||
|
||||
<div class="login-block">
|
||||
<h5>Login to unlock Patreon features <i class="fa fa-question-circle" aria-hidden="true"></i></h5>
|
||||
<a href="#">
|
||||
<i class="fa fa-facebook" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i class="fa fa-twitter" aria-hidden="true"></i>
|
||||
</a>
|
||||
<a href="#">
|
||||
<i class="fa fa-google-plus" aria-hidden="true"></i>
|
||||
</a>
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
<p>
|
||||
Yo, <i class="fa fa-user-circle" aria-hidden="true"></i> @User.GetName() ( <a href="~/logout"><i class="fa fa-sign-out" aria-hidden="true"></i>logout</a>)
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h5>
|
||||
<a href="~/login">
|
||||
Login with Patreon to unlock features
|
||||
<i class="fa fa-question-circle master-tooltip" aria-hidden="true" title="We have added advanced features for those who supported development. Login with your Patreon account to unlock them.">
|
||||
</i>
|
||||
</a>
|
||||
</h5>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,11 +44,22 @@
|
||||
|
||||
<div class="controls">
|
||||
<p>
|
||||
<i id="control-icon" class="fa fa-question-circle" aria-hidden="true"></i>
|
||||
<span id="control-panel" class="locked">
|
||||
@if (enableFeatures)
|
||||
{
|
||||
<i class="fa fa-wrench" aria-hidden="true"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fa fa-question-circle master-tooltip"
|
||||
aria-hidden="true"
|
||||
title="This features are available for patrons only. You may support us and unlock this features"></i>
|
||||
}
|
||||
|
||||
<span id="control-panel" class="@(!enableFeatures ? "locked" : "")">
|
||||
Selected format <a class="selected-option" id="video-format">video</a> <a id="audio-format">audio</a>,
|
||||
quality <a class="selected-option" id="best-quality">best</a> <a id="worst-quality">worst</a>,
|
||||
</span>
|
||||
|
||||
<span class="locked" style="text-decoration: line-through">
|
||||
<i class="fa fa-question-circle master-tooltip" title="Still under development. We will keep you updated via Patreon's news feed" aria-hidden="true"></i>
|
||||
episode count <a class="selected-option">50</a> <a>100</a> <a>150</a>
|
||||
|
@@ -48,8 +48,7 @@
|
||||
<script src="~/js/site.min.js" asp-append-version="true">
|
||||
</script>
|
||||
</environment>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
|
@@ -10,6 +10,9 @@
|
||||
|
||||
"Podsync": {
|
||||
"YouTubeApiKey": "",
|
||||
"RedisConnectionString": "localhost"
|
||||
"RedisConnectionString": "localhost",
|
||||
"BaseUrl": "",
|
||||
"PatreonClientId": "",
|
||||
"PatreonSecret": ""
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
"dependencies": {
|
||||
"Google.Apis.YouTube.v3": "1.16.0.582",
|
||||
"Hashids.net": "1.2.2",
|
||||
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
|
||||
"Microsoft.AspNetCore.Authentication.OAuth": "1.0.0",
|
||||
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
|
||||
"Microsoft.AspNetCore.Mvc": "1.0.0",
|
||||
"Microsoft.AspNetCore.Razor.Tools": {
|
||||
|
@@ -64,12 +64,13 @@ a {
|
||||
}
|
||||
|
||||
.login-block {
|
||||
color: #EB8C11;
|
||||
font-size: 1.3em;
|
||||
color: #e18712;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.login-block a {
|
||||
margin-right: 0.5em;
|
||||
font-size: 1.2em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Main block + input */
|
||||
|
BIN
src/Podsync/wwwroot/img/patreon_logo.png
Normal file
BIN
src/Podsync/wwwroot/img/patreon_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
@@ -77,23 +77,6 @@ $(function () {
|
||||
Control panel
|
||||
*/
|
||||
|
||||
function lockControls(lock) {
|
||||
if (lock) {
|
||||
$('#control-panel').addClass('locked');
|
||||
$('#control-icon')
|
||||
.removeClass('fa-wrench')
|
||||
.addClass('fa-question-circle master-tooltip')
|
||||
.attr('title', 'This features are available for patrons only (please, login with your Patreon account). You may support us and unlock this features');
|
||||
} else {
|
||||
$('#control-panel')
|
||||
.removeClass('locked');
|
||||
$('#control-icon')
|
||||
.removeClass('fa-question-circle master-tooltip')
|
||||
.addClass('fa-wrench')
|
||||
.removeAttr('title');
|
||||
}
|
||||
}
|
||||
|
||||
function isLocked() {
|
||||
return $('#control-panel').hasClass('locked');
|
||||
}
|
||||
@@ -118,6 +101,17 @@ $(function () {
|
||||
$('#best-quality, #worst-quality').toggleClass('selected-option');
|
||||
}
|
||||
|
||||
function getQuality() {
|
||||
var isAudio = $('#audio-format').hasClass('selected-option');
|
||||
var isWorst = $('#worst-quality').hasClass('selected-option');
|
||||
|
||||
if (isAudio) {
|
||||
return isWorst ? 'AudioLow' : 'AudioHigh';
|
||||
} else {
|
||||
return isWorst ? 'VideoLow' : 'VideoHigh';
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
|
||||
function closeModal() {
|
||||
@@ -164,7 +158,7 @@ $(function () {
|
||||
|
||||
$('#get-link').click(function(e) {
|
||||
var url = $('#url-input').val();
|
||||
createFeed({ url: url, quality: 'VideoHigh' }, displayLink);
|
||||
createFeed({ url: url, quality: getQuality() }, displayLink);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
@@ -192,6 +186,4 @@ $(function () {
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
lockControls(true);
|
||||
});
|
24
test/Podsync.Tests/Services/Patreon/PatreonApiTests.cs
Normal file
24
test/Podsync.Tests/Services/Patreon/PatreonApiTests.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Threading.Tasks;
|
||||
using Podsync.Services.Patreon;
|
||||
using Xunit;
|
||||
|
||||
namespace Podsync.Tests.Services.Patreon
|
||||
{
|
||||
public class PatreonApiTests : TestBase
|
||||
{
|
||||
private readonly IPatreonApi _api = new PatreonApi();
|
||||
|
||||
private Tokens Tokens => Configuration.CreatorTokens;
|
||||
|
||||
[Fact]
|
||||
public async Task FetchProfileTest()
|
||||
{
|
||||
var user = await _api.FetchUserAndPledges(Tokens);
|
||||
|
||||
Assert.Equal("2822191", user.Id);
|
||||
Assert.Equal("pavlenko.maksym@gmail.com", user.Email);
|
||||
Assert.Equal("https://www.patreon.com/podsync", user.Url);
|
||||
Assert.Equal("Max", user.Name);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user