Loading app/Config/Routes.php +8 −0 Original line number Diff line number Diff line Loading @@ -194,6 +194,14 @@ $routes->group('@(:podcastHandle)', static function ($routes): void { $routes->get('feed', 'FeedController/$1'); }); // audio routes $routes->head('audio/@(:podcastHandle)/(:slug)', 'EpisodeController::audio/$1/$2', [ 'as' => 'episode-audio', ],); $routes->get('audio/@(:podcastHandle)/(:slug)', 'EpisodeController::audio/$1/$2', [ 'as' => 'episode-audio', ],); // Other pages $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', Loading app/Controllers/EpisodeController.php +80 −0 Original line number Diff line number Diff line Loading @@ -19,12 +19,14 @@ use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use Config\Services; use Modules\Analytics\AnalyticsTrait; use Modules\Fediverse\Objects\OrderedCollectionObject; use Modules\Fediverse\Objects\OrderedCollectionPage; use Modules\PremiumPodcasts\Models\SubscriptionModel; use SimpleXMLElement; class EpisodeController extends BaseController Loading Loading @@ -329,4 +331,82 @@ class EpisodeController extends BaseController ->setHeader('Access-Control-Allow-Origin', '*') ->setBody($collection->toJSON()); } public function audio(): RedirectResponse | ResponseInterface { // check if episode is premium? $subscription = null; // check if podcast is already unlocked before any token validation if ($this->episode->is_premium && ($subscription = service('premium_podcasts')->subscription( $this->episode->podcast->handle )) === null) { // look for token as GET parameter if (($token = $this->request->getGet('token')) === null) { return $this->response->setStatusCode(401) ->setJSON([ 'errors' => [ 'status' => 401, 'title' => 'Unauthorized', 'detail' => 'Episode is premium, you must provide a token to unlock it.', ], ]); } // check if there's a valid subscription for the provided token if (($subscription = (new SubscriptionModel())->validateSubscription( $this->episode->podcast->handle, $token )) === null) { return $this->response->setStatusCode(401, 'Invalid token!') ->setJSON([ 'errors' => [ 'status' => 401, 'title' => 'Unauthorized', 'detail' => 'Invalid token!', ], ]); } } $session = Services::session(); $session->start(); $serviceName = ''; if ($this->request->getGet('_from')) { $serviceName = $this->request->getGet('_from'); } elseif ($session->get('embed_domain') !== null) { $serviceName = $session->get('embed_domain'); } elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') { $serviceName = parse_url((string) $session->get('referer'), PHP_URL_HOST); } $audioFileSize = $this->episode->audio->file_size; $audioFileHeaderSize = $this->episode->audio->header_size; $audioDuration = $this->episode->audio->duration; // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics // - if audio is less than or equal to 60s, then take the audio file_size // - if audio is more than 60s, then take the audio file_header_size + 60s $bytesThreshold = $audioDuration <= 60 ? $audioFileSize : $audioFileHeaderSize + (int) floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60); helper('analytics'); podcast_hit( $this->episode->podcast_id, $this->episode->id, $bytesThreshold, $audioFileSize, $audioDuration, $this->episode->published_at->getTimestamp(), $serviceName, $subscription !== null ? $subscription->id : null ); $analyticsConfig = config('Analytics'); return redirect()->to($analyticsConfig->getAudioUrl($this->episode, $this->request->getGet())); } } app/Controllers/FeedController.php +1 −2 Original line number Diff line number Diff line Loading @@ -3,14 +3,13 @@ declare(strict_types=1); /** * @copyright 2020 Ad Aures * @copyright 2022 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ namespace App\Controllers; use App\Entities\Podcast; use App\Models\EpisodeModel; use App\Models\PodcastModel; use CodeIgniter\Controller; Loading app/Entities/Episode.php +6 −23 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ use RuntimeException; * @property string $title * @property int $audio_id * @property Audio $audio * @property string $audio_analytics_url * @property string $audio_url * @property string $audio_web_url * @property string $audio_opengraph_url * @property string|null $description Holds text only description, striped of any markdown or html special characters Loading Loading @@ -93,7 +93,7 @@ class Episode extends Entity protected ?Audio $audio = null; protected string $audio_analytics_url; protected string $audio_url; protected string $audio_web_url; Loading Loading @@ -335,36 +335,19 @@ class Episode extends Entity return $this->chapters; } public function getAudioAnalyticsUrl(): string public function getAudioUrl(): string { helper('analytics'); return generate_episode_analytics_url( $this->podcast_id, $this->id, $this->getPodcast() ->handle, $this->attributes['slug'], $this->getAudio() ->file_extension, $this->getAudio() ->duration, $this->getAudio() ->file_size, $this->getAudio() ->header_size, $this->published_at, ); return url_to('episode-audio', $this->getPodcast()->handle, $this->slug); } public function getAudioWebUrl(): string { return $this->getAudioAnalyticsUrl() . '?_from=-+Website+-'; return $this->getAudioUrl() . '?_from=-+Website+-'; } public function getAudioOpengraphUrl(): string { return $this->getAudioAnalyticsUrl() . '?_from=-+Open+Graph+-'; return $this->getAudioUrl() . '?_from=-+Open+Graph+-'; } /** Loading app/Helpers/rss_helper.php +1 −1 Original line number Diff line number Diff line Loading @@ -286,7 +286,7 @@ if (! function_exists('get_rss_feed')) { $enclosure->addAttribute( 'url', $episode->audio_analytics_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams), $episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams), ); $enclosure->addAttribute('length', (string) $episode->audio->file_size); $enclosure->addAttribute('type', $episode->audio->file_mimetype); Loading Loading
app/Config/Routes.php +8 −0 Original line number Diff line number Diff line Loading @@ -194,6 +194,14 @@ $routes->group('@(:podcastHandle)', static function ($routes): void { $routes->get('feed', 'FeedController/$1'); }); // audio routes $routes->head('audio/@(:podcastHandle)/(:slug)', 'EpisodeController::audio/$1/$2', [ 'as' => 'episode-audio', ],); $routes->get('audio/@(:podcastHandle)/(:slug)', 'EpisodeController::audio/$1/$2', [ 'as' => 'episode-audio', ],); // Other pages $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', Loading
app/Controllers/EpisodeController.php +80 −0 Original line number Diff line number Diff line Loading @@ -19,12 +19,14 @@ use App\Models\PodcastModel; use App\Models\PostModel; use CodeIgniter\Database\BaseBuilder; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Response; use CodeIgniter\HTTP\ResponseInterface; use Config\Services; use Modules\Analytics\AnalyticsTrait; use Modules\Fediverse\Objects\OrderedCollectionObject; use Modules\Fediverse\Objects\OrderedCollectionPage; use Modules\PremiumPodcasts\Models\SubscriptionModel; use SimpleXMLElement; class EpisodeController extends BaseController Loading Loading @@ -329,4 +331,82 @@ class EpisodeController extends BaseController ->setHeader('Access-Control-Allow-Origin', '*') ->setBody($collection->toJSON()); } public function audio(): RedirectResponse | ResponseInterface { // check if episode is premium? $subscription = null; // check if podcast is already unlocked before any token validation if ($this->episode->is_premium && ($subscription = service('premium_podcasts')->subscription( $this->episode->podcast->handle )) === null) { // look for token as GET parameter if (($token = $this->request->getGet('token')) === null) { return $this->response->setStatusCode(401) ->setJSON([ 'errors' => [ 'status' => 401, 'title' => 'Unauthorized', 'detail' => 'Episode is premium, you must provide a token to unlock it.', ], ]); } // check if there's a valid subscription for the provided token if (($subscription = (new SubscriptionModel())->validateSubscription( $this->episode->podcast->handle, $token )) === null) { return $this->response->setStatusCode(401, 'Invalid token!') ->setJSON([ 'errors' => [ 'status' => 401, 'title' => 'Unauthorized', 'detail' => 'Invalid token!', ], ]); } } $session = Services::session(); $session->start(); $serviceName = ''; if ($this->request->getGet('_from')) { $serviceName = $this->request->getGet('_from'); } elseif ($session->get('embed_domain') !== null) { $serviceName = $session->get('embed_domain'); } elseif ($session->get('referer') !== null && $session->get('referer') !== '- Direct -') { $serviceName = parse_url((string) $session->get('referer'), PHP_URL_HOST); } $audioFileSize = $this->episode->audio->file_size; $audioFileHeaderSize = $this->episode->audio->header_size; $audioDuration = $this->episode->audio->duration; // bytes_threshold: number of bytes that must be downloaded for an episode to be counted in download analytics // - if audio is less than or equal to 60s, then take the audio file_size // - if audio is more than 60s, then take the audio file_header_size + 60s $bytesThreshold = $audioDuration <= 60 ? $audioFileSize : $audioFileHeaderSize + (int) floor((($audioFileSize - $audioFileHeaderSize) / $audioDuration) * 60); helper('analytics'); podcast_hit( $this->episode->podcast_id, $this->episode->id, $bytesThreshold, $audioFileSize, $audioDuration, $this->episode->published_at->getTimestamp(), $serviceName, $subscription !== null ? $subscription->id : null ); $analyticsConfig = config('Analytics'); return redirect()->to($analyticsConfig->getAudioUrl($this->episode, $this->request->getGet())); } }
app/Controllers/FeedController.php +1 −2 Original line number Diff line number Diff line Loading @@ -3,14 +3,13 @@ declare(strict_types=1); /** * @copyright 2020 Ad Aures * @copyright 2022 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ namespace App\Controllers; use App\Entities\Podcast; use App\Models\EpisodeModel; use App\Models\PodcastModel; use CodeIgniter\Controller; Loading
app/Entities/Episode.php +6 −23 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ use RuntimeException; * @property string $title * @property int $audio_id * @property Audio $audio * @property string $audio_analytics_url * @property string $audio_url * @property string $audio_web_url * @property string $audio_opengraph_url * @property string|null $description Holds text only description, striped of any markdown or html special characters Loading Loading @@ -93,7 +93,7 @@ class Episode extends Entity protected ?Audio $audio = null; protected string $audio_analytics_url; protected string $audio_url; protected string $audio_web_url; Loading Loading @@ -335,36 +335,19 @@ class Episode extends Entity return $this->chapters; } public function getAudioAnalyticsUrl(): string public function getAudioUrl(): string { helper('analytics'); return generate_episode_analytics_url( $this->podcast_id, $this->id, $this->getPodcast() ->handle, $this->attributes['slug'], $this->getAudio() ->file_extension, $this->getAudio() ->duration, $this->getAudio() ->file_size, $this->getAudio() ->header_size, $this->published_at, ); return url_to('episode-audio', $this->getPodcast()->handle, $this->slug); } public function getAudioWebUrl(): string { return $this->getAudioAnalyticsUrl() . '?_from=-+Website+-'; return $this->getAudioUrl() . '?_from=-+Website+-'; } public function getAudioOpengraphUrl(): string { return $this->getAudioAnalyticsUrl() . '?_from=-+Open+Graph+-'; return $this->getAudioUrl() . '?_from=-+Open+Graph+-'; } /** Loading
app/Helpers/rss_helper.php +1 −1 Original line number Diff line number Diff line Loading @@ -286,7 +286,7 @@ if (! function_exists('get_rss_feed')) { $enclosure->addAttribute( 'url', $episode->audio_analytics_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams), $episode->audio_url . ($enclosureParams === '' ? '' : '?' . $enclosureParams), ); $enclosure->addAttribute('length', (string) $episode->audio->file_size); $enclosure->addAttribute('type', $episode->audio->file_mimetype); Loading