Skip to content

Commit

Permalink
Merge commit '1044860ca81ff80d3a52b9bd513d786141023c09' into public/8.2
Browse files Browse the repository at this point in the history
  • Loading branch information
mherger committed Jun 22, 2021
2 parents b638f4e + 1044860 commit c5d095c
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 185 deletions.
23 changes: 23 additions & 0 deletions Slim/Plugin/Podcast/GPodder.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package Slim::Plugin::Podcast::GPodder;

# Logitech Media Server Copyright 2005-2021 Logitech.

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.

__PACKAGE__->Slim::Plugin::Podcast::Plugin::registerProvider('GPodder', {
title => 'title',
feed => 'url',
image => ['scaled_logo_url', 'logo_url'],
});

# just use defaults
sub getItems { [ { } ] }

sub getSearchParams {
return ('https://gpodder.net/search.json?q=' . $_[2]);
}


1;
11 changes: 11 additions & 0 deletions Slim/Plugin/Podcast/HTML/EN/plugins/Podcast/settings/basic.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,15 @@
</select>
[% END %]

[% IF newsHandler %]
[% WRAPPER settingSection %]
[% WRAPPER settingGroup title="PLUGIN_PODCAST_NEWSINCE" desc="PLUGIN_PODCAST_WHATSNEW_DESC" %]
<input type="text" class="stdedit" name="pref_newSince" id="newSince" value="[% prefs.pref_newSince %]" size="2">
[% END %]
[% WRAPPER settingGroup title="PLUGIN_PODCAST_MAXNEW" %]
<input type="text" class="stdedit" name="pref_maxNew" id="maxNew" value="[% prefs.pref_maxNew %]" size="2">
[% END %]
[% END %]
[% END %]

[% PROCESS settings/footer.html %]
126 changes: 103 additions & 23 deletions Slim/Plugin/Podcast/Plugin.pm
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use strict;
use base qw(Slim::Plugin::OPMLBased);

use XML::Simple;
use JSON::XS::VersionOneAndTwo;
use Encode qw(encode);

use Slim::Plugin::Podcast::Parser;
use Slim::Utils::Cache;
Expand All @@ -19,7 +21,6 @@ use Slim::Utils::Strings qw(string cstring);
use Slim::Utils::Timers;

use Slim::Plugin::Podcast::ProtocolHandler;
use Slim::Plugin::Podcast::Provider;

my $log = Slim::Utils::Log->addLogCategory({
'category' => 'plugin.podcast',
Expand All @@ -32,10 +33,14 @@ tie my %recentlyPlayed, 'Tie::Cache::LRU', 50;
my $prefs = preferences('plugin.podcast');
my $cache;

my %providers = ();

$prefs->init({
feeds => [],
skipSecs => 15,
recent => [],
newSince => 7,
maxNew => 7,
});

# migrate old prefs across
Expand Down Expand Up @@ -72,6 +77,9 @@ sub initPlugin {
func => \&trackInfoMenu,
) );

require Slim::Plugin::Podcast::PodcastIndex;
require Slim::Plugin::Podcast::GPodder;

# create wrapped pseudo-tracks for recently played to have title during scanUrl
foreach my $item (@{$prefs->get('recent')}) {
my $track = Slim::Schema->updateOrCreate( {
Expand All @@ -85,9 +93,6 @@ sub initPlugin {

%recentlyPlayed = map { $_->{url} => $_ } reverse @{$prefs->get('recent')};

# initialize all feed providers
Slim::Plugin::Podcast::Provider::init;

Slim::Control::Request::addDispatch(
[ 'podcastinfo', 'items', '_index', '_quantity' ],
[ 0, 1, 1, \&showInfo ]
Expand Down Expand Up @@ -132,31 +137,19 @@ sub updateRecentlyPlayed {
};
}

sub unwrapUrl {
return shift =~ m|^podcast://([^{]+)(?:{from=(\d+)}$)?|;
}

sub wrapUrl {
my ($url, $from) = @_;

return 'podcast://' . $url . (defined $from ? "{from=$from}" : '');
}

sub handleFeed {
my ($client, $cb, $params, $args) = @_;

my $items = [];
my $provider = Slim::Plugin::Podcast::Provider::getCurrent;
my $provider = getProviderByName();

# populate provider's custom menu
foreach my $item (@{$provider->{menu}}) {
push @$items, {
name => $item->{title} || cstring($client, 'PLUGIN_PODCAST_SEARCH'),
type => $item->{type} || 'search',
image => 'html/images/search.png',
url => $item->{handler} || \&Slim::Plugin::Podcast::Provider::defaultHandler,
passthrough => [ { provider => $provider, query => $item->{query} } ],
};
foreach my $item (@{$provider->getItems($client)}) {
$item->{title} ||= cstring($client, 'PLUGIN_PODCAST_SEARCH');
$item->{type} ||= 'search',
$item->{url} ||= \&searchHandler unless $item->{enclosure};
$item->{passthrough} ||= [ { provider => $provider, item => $item } ],
push @$items, $item;
}

# then add recently played
Expand Down Expand Up @@ -258,6 +251,82 @@ sub recentHandler {
$cb->({ items => \@menu });
}

sub searchHandler {
my ($client, $cb, $args, $passthrough) = @_;

my $provider = $passthrough->{provider};
my $search = encode('utf-8', $args->{search});
my ($url, $headers) = $provider->getSearchParams($client, $passthrough->{item}, $search);

Slim::Networking::SimpleAsyncHTTP->new(
sub {
my $response = shift;
my $result = eval { from_json( $response->content ) };
$result = $result->{$provider->{result}} if $provider->{result};

$log->error($@) if $@;
main::DEBUGLOG && $log->is_debug && warn Data::Dump::dump($result);

my $items = [];
foreach my $feed (@$result) {
next unless $feed->{$provider->{feed}};

# find the image by order of preference
my ($image) = grep { $feed->{$_} } @{$provider->{image}};

push @$items, {
name => $feed->{$provider->{title}},
url => $feed->{$provider->{feed}},
image => $feed->{$image},
parser => 'Slim::Plugin::Podcast::Parser',
}
}

$cb->({
items => $items,
actions => {
info => {
command => ['podcastinfo', 'items'],
variables => [ 'url', 'url', 'name', 'name' ],
},
}
});
},
sub {
$log->error("Search failed $_[1]");
$cb->({ items => [{
type => 'text',
name => cstring($client, 'PLUGIN_PODCAST_SEARCH_FAILED'),
}] });
},
{
cache => 1,
expires => 86400,
}
)->get($url, @$headers);
}

sub registerProvider {
my ($class, $name, $provider, $force) = @_;

if (!$providers{$name} || $force) {
$providers{$name} = bless $provider, $class;
}
else {
$log->warn(sprintf('Podcast aggregator %s is already registered!', $name));
}
}

sub getProviders {
my @list = keys %providers;
return \@list;
}

sub getProviderByName {
my $provider = shift || $providers{$prefs->get('provider')};
return $provider || $providers{(keys %providers)[0]};
}

sub getDisplayName {
return 'PLUGIN_PODCAST';
}
Expand Down Expand Up @@ -402,4 +471,15 @@ sub delShow {
$request->setStatusDone();
}

sub unwrapUrl {
return shift =~ m|^podcast://([^{]+)(?:{from=(\d+)}$)?|;
}

sub wrapUrl {
my ($url, $from) = @_;

return 'podcast://' . $url . (defined $from ? "{from=$from}" : '');
}


1;
121 changes: 121 additions & 0 deletions Slim/Plugin/Podcast/PodcastIndex.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package Slim::Plugin::Podcast::PodcastIndex;

# Logitech Media Server Copyright 2005-2021 Logitech.

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2.

use strict;

use JSON::XS::VersionOneAndTwo;
use Digest::SHA1 qw(sha1_hex);
use MIME::Base64;
use URI::Escape;

use Slim::Utils::Prefs;
use Slim::Utils::Log;
use Slim::Utils::Strings qw(string cstring);

my $prefs = preferences('plugin.podcast');
my $log = logger('plugin.podcast');

$prefs->init( { podcastindex => {
k => 'NTVhNTMzODM0MzU1NDM0NTQ1NTRlNDI1MTU5NTk1MzRhNDYzNzRkNA==',
s => 'ODQzNjc1MDc3NmQ2NDQ4MzQ3OTc4NDczNzc1MzE3MTdlNTM3YzQzNTI2ODU1NWE0MzIyNjE2ZTU0MjMyOTdhN2U2ZTQyNWU0ODQ0MjM0NTU=',
} } );

__PACKAGE__->Slim::Plugin::Podcast::Plugin::registerProvider('PodcastIndex', {
result => 'feeds',
feed => 'url',
title => 'title',
image => ['artwork', 'image'],
});

# add a new episode menu to defaults
sub getItems {
my ($class, $client);
return [ { }, {
title => cstring($client, 'PLUGIN_PODCAST_WHATSNEW', $prefs->get('newSince')),
image => 'plugins/Podcast/html/images/podcastindex.png',
type => 'link',
url => \&newsHandler,
} ];
}

sub getSearchParams {
my ($class, $client, $item, $search) = @_;
return ('https://api.podcastindex.org/api/1.0/search/byterm?q=' . $search, getHeaders());
}

sub newsHandler {
my ($client, $cb, $args, $passthrough) = @_;

my $provider = $passthrough->{provider};
my $headers = getHeaders();
my @feeds = @{$prefs->get('feeds')};
my $count = scalar @feeds;

return $cb->(undef) unless $count;

my $items = [];

$log->info("about to get updates for $count podcast feeds");

foreach my $feed (@feeds) {
my $url = 'https://api.podcastindex.org/api/1.0/episodes/byfeedurl?url=' . uri_escape($feed->{value});
$url .= '&since=-' . $prefs->get('newSince')*3600*24 . '&max=' . $prefs->get('maxNew');
Slim::Networking::SimpleAsyncHTTP->new(
sub {
my $response = shift;
my $result = eval { from_json( $response->content ) };

$log->warn("error parsing new episodes for $url", $@);
main::INFOLOG && $log->is_info && $log->info("found $result->{count} for $url");

foreach my $item (@{$result->{items}}) {
push @$items, {
name => $item->{title},
enclosure => { url => Slim::Plugin::Podcast::Plugin::wrapUrl($item->{enclosureUrl}) },
image => $item->{image} || $item->{feedImage},
date => $item->{datePublished},
type => 'audio',
};
}

unless (--$count) {
$items = [ sort { $a->{date} < $b->{date} } @$items ];
$cb->( { items => $items } );
}
},
sub {
$log->warn("can't get new episodes for $url ", shift->error);
unless (--$count) {
$items = [ sort { $a->{date} < $b->{date} } @$items ];
$cb->( { items => $items } );
}

},
{
cache => 1,
expires => 600,
timeout => 15,
},
)->get($url, @$headers);
}
}

sub getHeaders {
my $config = $prefs->get('podcastindex');
my $k = pack('H*', scalar(reverse(MIME::Base64::decode($config->{k}))));
my $s = pack('H*', scalar(reverse(MIME::Base64::decode($config->{s}))));
my $time = time;
my $headers = [
'X-Auth-Key', $k,
'X-Auth-Date', $time,
'Authorization', sha1_hex($k . $s . $time),
];
}


1;

0 comments on commit c5d095c

Please sign in to comment.