From aa720022eebafeb43366906521240a66e79eb97f Mon Sep 17 00:00:00 2001 From: Athlon1600 Date: Fri, 3 Nov 2023 20:16:11 -0500 Subject: [PATCH] added types --- src/Browser.php | 14 +++++-- src/DownloadOptions.php | 2 +- src/Exception/TooManyRequestsException.php | 4 +- src/Models/InitialPlayerResponse.php | 27 +++++-------- src/Models/JsonObject.php | 2 +- src/Models/VideoInfo.php | 17 +++----- src/Models/YouTubeCaption.php | 15 +++++++ src/Models/YouTubeConfigData.php | 32 +++++---------- src/PlayerVideoInfoMapper.php | 32 --------------- src/Responses/HttpResponse.php | 12 +++--- src/Responses/PlayerApiResponse.php | 11 ++++- src/Responses/WatchVideoPage.php | 37 +++-------------- src/Utils/ITagUtils.php | 8 ++-- src/Utils/SerializationUtils.php | 6 +-- src/Utils/Utils.php | 47 +++++++++++++++------- src/VideoInfoMapper.php | 41 +++++++++++++++++++ src/YouTubeDownloader.php | 46 +++++++++++++++++++-- src/YouTubeStreamer.php | 29 ++++++++----- tests/YouTubePageTest.php | 2 - tests/YouTubeTest.php | 2 +- tests/YouTubeUrlTest.php | 2 - 21 files changed, 216 insertions(+), 172 deletions(-) create mode 100644 src/Models/YouTubeCaption.php delete mode 100644 src/PlayerVideoInfoMapper.php create mode 100644 src/VideoInfoMapper.php diff --git a/src/Browser.php b/src/Browser.php index 61cad85..c1f1887 100644 --- a/src/Browser.php +++ b/src/Browser.php @@ -3,11 +3,12 @@ namespace YouTube; use Curl\BrowserClient; +use Curl\Response; use YouTube\Utils\Utils; class Browser extends BrowserClient { - public function setUserAgent($agent): void + public function setUserAgent(string $agent): void { $this->headers['User-Agent'] = $agent; } @@ -17,13 +18,18 @@ public function getUserAgent(): ?string return Utils::arrayGet($this->headers, 'User-Agent'); } - public function followRedirects($enabled): self + public function followRedirects(bool $enabled): self { $this->options[CURLOPT_FOLLOWLOCATION] = $enabled ? 1 : 0; return $this; } - public function cachedGet($url) + /** + * Return some special response that lets caller know if cache miss or hit + * @param string $url + * @return Response + */ + public function cachedGet(string $url): Response { $cache_path = sprintf('%s/%s', static::getStorageDirectory(), $this->getCacheKey($url)); @@ -44,7 +50,7 @@ public function cachedGet($url) return $response; } - protected function getCacheKey($url): string + protected function getCacheKey(string $url): string { return md5($url) . '_v4'; } diff --git a/src/DownloadOptions.php b/src/DownloadOptions.php index 656ea23..be50f76 100644 --- a/src/DownloadOptions.php +++ b/src/DownloadOptions.php @@ -99,7 +99,7 @@ protected function getLowToHighAudioFormats(): array /** @var StreamFormat $a */ /** @var StreamFormat $b */ - return $a->contentLength - $b->contentLength; + return intval($a->contentLength) - intval($b->contentLength); }); return $copy; diff --git a/src/Exception/TooManyRequestsException.php b/src/Exception/TooManyRequestsException.php index b9aeebd..61e6930 100644 --- a/src/Exception/TooManyRequestsException.php +++ b/src/Exception/TooManyRequestsException.php @@ -6,7 +6,7 @@ class TooManyRequestsException extends YouTubeException { - protected $page; + protected WatchVideoPage $page; public function __construct(WatchVideoPage $page) { @@ -18,7 +18,7 @@ public function __construct(WatchVideoPage $page) /** * @return WatchVideoPage */ - public function getPage() + public function getPage(): WatchVideoPage { return $this->page; } diff --git a/src/Models/InitialPlayerResponse.php b/src/Models/InitialPlayerResponse.php index fca9c1a..9abc15f 100644 --- a/src/Models/InitialPlayerResponse.php +++ b/src/Models/InitialPlayerResponse.php @@ -9,32 +9,23 @@ * JSON data that appears inside /watch?v= page [ytInitialPlayerResponse=] * @package YouTube\Models */ -class InitialPlayerResponse +class InitialPlayerResponse extends JsonObject { - private $ytInitialPlayerResponse; + //public array $videoDetails; + //public array $microformat; - public function __construct($ytInitialPlayerResponse) + public function isPlayabilityStatusOkay(): bool { - $this->ytInitialPlayerResponse = $ytInitialPlayerResponse; + return $this->deepGet('playabilityStatus.status') == 'OK'; } - public function all() + public function getVideoDetails(): ?array { - return $this->ytInitialPlayerResponse; + return $this->deepGet('videoDetails'); } - protected function query($key) + public function getCaptionTracks(): array { - return Utils::arrayGet($this->ytInitialPlayerResponse, $key); - } - - public function isPlayabilityStatusOkay() - { - return $this->query('playabilityStatus.status') == 'OK'; - } - - public function getVideoDetails() - { - return $this->query('videoDetails'); + return (array)$this->deepGet("captions.playerCaptionsTracklistRenderer.captionTracks"); } } \ No newline at end of file diff --git a/src/Models/JsonObject.php b/src/Models/JsonObject.php index 13c3681..75f9dbd 100644 --- a/src/Models/JsonObject.php +++ b/src/Models/JsonObject.php @@ -33,6 +33,6 @@ public function deepGet(string $key, $default = null) public function toArray(): array { - return get_object_vars($this); + return $this->_data; } } \ No newline at end of file diff --git a/src/Models/VideoInfo.php b/src/Models/VideoInfo.php index f79b375..36f74db 100644 --- a/src/Models/VideoInfo.php +++ b/src/Models/VideoInfo.php @@ -2,7 +2,10 @@ namespace YouTube\Models; -class VideoInfo extends AbstractModel +/** + * General class for holding video info. Not all fields required + */ +class VideoInfo { // uniquely identifies this video public ?string $id; @@ -12,23 +15,15 @@ class VideoInfo extends AbstractModel public ?string $title = null; public ?string $description = null; - public ?string $uploadDate; + public ?\DateTime $uploadDate; - // accessible by public - public ?string $pageUrl; + public ?string $category; public ?int $viewCount; public ?int $commentCount; public ?int $likeCount; public ?int $dislikeCount; - /** - * @var VideoThumbnail[] - */ - public array $thumbnails; - - public ?string $thumbnail; - // in seconds public ?int $durationSeconds; diff --git a/src/Models/YouTubeCaption.php b/src/Models/YouTubeCaption.php new file mode 100644 index 0000000..89ed9a5 --- /dev/null +++ b/src/Models/YouTubeCaption.php @@ -0,0 +1,15 @@ +data = $data; - } - - protected function query($key) - { - return Utils::arrayGet($this->data, $key); - } - - public function getGoogleVisitorId() + public function getGoogleVisitorId(): ?string { - return $this->query('VISITOR_DATA'); + return $this->deepGet('VISITOR_DATA'); } - public function getClientName() + public function getClientName(): ?string { - return $this->query('INNERTUBE_CONTEXT_CLIENT_NAME'); + return $this->deepGet('INNERTUBE_CONTEXT_CLIENT_NAME'); } - public function getClientVersion() + public function getClientVersion(): ?string { - return $this->query('INNERTUBE_CONTEXT_CLIENT_VERSION'); + return $this->deepGet('INNERTUBE_CONTEXT_CLIENT_VERSION'); } - public function getApiKey() + public function getApiKey(): ?string { - return $this->query('INNERTUBE_API_KEY'); + return $this->deepGet('INNERTUBE_API_KEY'); } } \ No newline at end of file diff --git a/src/PlayerVideoInfoMapper.php b/src/PlayerVideoInfoMapper.php deleted file mode 100644 index 0639dc4..0000000 --- a/src/PlayerVideoInfoMapper.php +++ /dev/null @@ -1,32 +0,0 @@ -getJson(), "videoDetails"); - - $result = new VideoInfo([]); - - if ($videoDetails) { - $result->id = Utils::arrayGet($videoDetails, 'videoId'); - $result->title = Utils::arrayGet($videoDetails, 'title'); - $result->description = Utils::arrayGet($videoDetails, 'shortDescription'); - - $result->channelId = Utils::arrayGet($videoDetails, 'channelId'); - - $result->durationSeconds = Utils::arrayGet($videoDetails, 'lengthSeconds'); - - $result->viewCount = Utils::arrayGet($videoDetails, 'viewCount'); - } - - return $result; - } -} \ No newline at end of file diff --git a/src/Responses/HttpResponse.php b/src/Responses/HttpResponse.php index 1db26e7..2bce990 100644 --- a/src/Responses/HttpResponse.php +++ b/src/Responses/HttpResponse.php @@ -9,10 +9,10 @@ abstract class HttpResponse /** * @var Response */ - private $response; + private Response $response; // Will become null if response contents cannot be decoded from JSON - private $json; + private ?array $json; public function __construct(Response $response) { @@ -20,7 +20,7 @@ public function __construct(Response $response) $this->json = json_decode($response->body, true); } - public function getResponse() + public function getResponse(): Response { return $this->response; } @@ -28,7 +28,7 @@ public function getResponse() /** * @return string|null */ - public function getResponseBody() + public function getResponseBody(): ?string { return $this->response->body; } @@ -36,12 +36,12 @@ public function getResponseBody() /** * @return array|null */ - public function getJson() + public function getJson(): ?array { return $this->json; } - public function isStatusOkay() + public function isStatusOkay(): bool { return $this->getResponse()->status == 200; } diff --git a/src/Responses/PlayerApiResponse.php b/src/Responses/PlayerApiResponse.php index e3e2b91..90cec8a 100644 --- a/src/Responses/PlayerApiResponse.php +++ b/src/Responses/PlayerApiResponse.php @@ -4,14 +4,21 @@ use YouTube\Utils\Utils; +/** + * Response from: /youtubei/v1/player + */ class PlayerApiResponse extends HttpResponse { - protected function query($key) + /** + * @param string $key + * @return array|mixed|null + */ + protected function query(string $key) { return Utils::arrayGet($this->getJson(), $key); } - public function getAllFormats() + public function getAllFormats(): array { $formats = $this->query('streamingData.formats'); diff --git a/src/Responses/WatchVideoPage.php b/src/Responses/WatchVideoPage.php index 02cb958..b6c1d45 100644 --- a/src/Responses/WatchVideoPage.php +++ b/src/Responses/WatchVideoPage.php @@ -5,10 +5,12 @@ use YouTube\Models\InitialPlayerResponse; use YouTube\Models\VideoInfo; use YouTube\Models\YouTubeConfigData; +use YouTube\VideoInfoMapper; use YouTube\Utils\Utils; class WatchVideoPage extends HttpResponse { + const REGEX_YTCFG = '/ytcfg\.set\s*\(\s*({.+?})\s*\)\s*;/'; const REGEX_INITIAL_PLAYER_RESPONSE = '/getResponseBody(), $matches)) { + if (preg_match(self::REGEX_YTCFG, $this->getResponseBody(), $matches)) { $data = json_decode($matches[1], true); return new YouTubeConfigData($data); } @@ -80,6 +82,7 @@ protected function getInitialData(): ?array } /** + * Parse whatever info we can just from this page without making any additional requests * @return VideoInfo */ public function getVideoInfo(): ?VideoInfo @@ -87,37 +90,9 @@ public function getVideoInfo(): ?VideoInfo $playerResponse = $this->getPlayerResponse(); if ($playerResponse) { - $playerResponse = $playerResponse->all(); + return VideoInfoMapper::fromInitialPlayerResponse($playerResponse); } - $thumbnails = Utils::arrayGet($playerResponse, 'videoDetails.thumbnail.thumbnails', []); - - $thumbnail_url = null; - $thumb_max_width = 0; - - foreach ($thumbnails as $thumbnail) { - - if ($thumbnail['width'] > $thumb_max_width) { - $thumbnail_url = $thumbnail['url']; - $thumb_max_width = $thumbnail['width']; - } - } - - $data = array( - 'id' => Utils::arrayGet($playerResponse, 'videoDetails.videoId'), - 'channelId' => Utils::arrayGet($playerResponse, 'videoDetails.channelId'), - 'channelTitle' => Utils::arrayGet($playerResponse, 'videoDetails.author'), - 'title' => Utils::arrayGet($playerResponse, 'videoDetails.title'), - 'description' => Utils::arrayGet($playerResponse, 'videoDetails.shortDescription'), - 'pageUrl' => $this->getResponse()->info->url, - 'uploadDate' => Utils::arrayGet($playerResponse, 'microformat.playerMicroformatRenderer.uploadDate'), - 'viewCount' => Utils::arrayGet($playerResponse, 'videoDetails.viewCount'), - 'thumbnail' => $thumbnail_url, - 'duration' => Utils::arrayGet($playerResponse, 'videoDetails.lengthSeconds'), - 'keywords' => Utils::arrayGet($playerResponse, 'videoDetails.keywords', []), - 'regionsAllowed' => Utils::arrayGet($playerResponse, 'microformat.playerMicroformatRenderer.availableCountries', []) - ); - - return new VideoInfo($data); + return null; } } \ No newline at end of file diff --git a/src/Utils/ITagUtils.php b/src/Utils/ITagUtils.php index da8fedd..f94eedb 100644 --- a/src/Utils/ITagUtils.php +++ b/src/Utils/ITagUtils.php @@ -4,7 +4,7 @@ class ITagUtils { - public static function downloadFormats() + public static function downloadFormats(): array { $data = file_get_contents("https://raw.githubusercontent.com/ytdl-org/youtube-dl/master/youtube_dl/extractor/youtube.py"); @@ -28,7 +28,7 @@ public static function downloadFormats() return array(); } - public static function transformFormats($formats) + public static function transformFormats(array $formats): array { $results = []; @@ -58,7 +58,7 @@ public static function transformFormats($formats) return $results; } - public static function parseItagInfo($itag) + public static function parseItagInfo(int $itag): string { if (array_key_exists($itag, static::$itag_detailed)) { return static::$itag_detailed[$itag]; @@ -68,7 +68,7 @@ public static function parseItagInfo($itag) } // itag info does not change frequently, that is why we cache it here as a plain static array - protected static $itag_detailed = array( + protected static array $itag_detailed = array( 5 => 'flv, video, 240p, audio', 6 => 'flv, video, 270p, audio', 13 => '3gp, video, audio', diff --git a/src/Utils/SerializationUtils.php b/src/Utils/SerializationUtils.php index 42dd8ce..84b6a5a 100644 --- a/src/Utils/SerializationUtils.php +++ b/src/Utils/SerializationUtils.php @@ -7,14 +7,14 @@ class SerializationUtils { - public static function optionsToArray(DownloadOptions $downloadOptions) + public static function optionsToArray(DownloadOptions $downloadOptions): array { return array_map(function (StreamFormat $link) { return $link->toArray(); }, $downloadOptions->getAllFormats()); } - public static function optionsFromArray($array) + public static function optionsFromArray(array $array): DownloadOptions { $links = array(); @@ -25,7 +25,7 @@ public static function optionsFromArray($array) return new DownloadOptions($links); } - public static function optionsFromFile($path) + public static function optionsFromFile(string $path): ?DownloadOptions { $contents = @file_get_contents($path); diff --git a/src/Utils/Utils.php b/src/Utils/Utils.php index 51ffd45..57a9e63 100644 --- a/src/Utils/Utils.php +++ b/src/Utils/Utils.php @@ -6,10 +6,10 @@ class Utils { /** * Extract youtube video_id from any piece of text - * @param $str - * @return mixed|string|null + * @param string $str + * @return string|null */ - public static function extractVideoId($str) + public static function extractVideoId(string $str): ?string { if (strlen($str) === 11) { return $str; @@ -26,10 +26,10 @@ public static function extractVideoId($str) * Will parse either channel ID or username from custom URL. * Will not parse legacy usernames as those cannot be queried via YouTube API * https://support.google.com/youtube/answer/6180214?hl=en - * @param $url - * @return mixed|null + * @param string $url + * @return string|null */ - public static function extractChannel($url) + public static function extractChannel(string $url): ?string { if (strpos($url, 'UC') === 0 && strlen($url) == 24) { return $url; @@ -46,7 +46,13 @@ public static function extractChannel($url) return null; } - public static function arrayGet($array, $key, $default = null) + /** + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed|null + */ + public static function arrayGet($array, string $key, $default = null) { foreach (explode('.', $key) as $segment) { @@ -61,23 +67,34 @@ public static function arrayGet($array, $key, $default = null) return $array; } - public static function arrayFilterReset($array, $callback) + public static function arrayFilterReset(array $array, callable $callback): array { return array_values(array_filter($array, $callback)); } + public static function jsonStringOrFail(string $json): array + { + return json_decode($json, true, 512, JSON_THROW_ON_ERROR); + } + + // str_contains not available in early PHP + public static function stringContains(string $haystack, string $needle): bool + { + return strpos($haystack, $needle) !== false; + } + /** - * @param $string - * @return mixed + * @param string $string + * @return array */ - public static function parseQueryString($string) + public static function parseQueryString(string $string): array { - $result = null; + $result = []; parse_str($string, $result); return $result; } - public static function relativeToAbsoluteUrl($url, $domain) + public static function relativeToAbsoluteUrl(string $url, string $domain): string { $scheme = parse_url($url, PHP_URL_SCHEME); $scheme = $scheme ? $scheme : 'http'; @@ -93,7 +110,7 @@ public static function relativeToAbsoluteUrl($url, $domain) return $url; } - public static function getInputValueByName($html, $name) + public static function getInputValueByName(string $html, string $name): ?string { if (preg_match("/name=(['\"]){$name}\\1[^>]+value=(['\"])(.*?)\\2/is", $html, $matches)) { return $matches[3]; @@ -102,7 +119,7 @@ public static function getInputValueByName($html, $name) return null; } - public static function sendJson($data, $status = 200) + public static function sendJson(array $data, int $status = 200): void { http_response_code($status); header('Content-Type: application/json'); diff --git a/src/VideoInfoMapper.php b/src/VideoInfoMapper.php new file mode 100644 index 0000000..c3f3990 --- /dev/null +++ b/src/VideoInfoMapper.php @@ -0,0 +1,41 @@ +getVideoDetails(); + + $result = new VideoInfo(); + + $result->id = Utils::arrayGet($videoDetails, 'videoId'); + $result->title = Utils::arrayGet($videoDetails, 'title'); + $result->description = Utils::arrayGet($videoDetails, 'shortDescription'); + + $result->channelId = Utils::arrayGet($videoDetails, 'channelId'); + $result->channelTitle = Utils::arrayGet($videoDetails, 'author'); + + $microformat = Utils::arrayGet($initialPlayerResponse->toArray(), 'microformat'); + + $date = Utils::arrayGet($microformat, 'playerMicroformatRenderer.uploadDate'); + + $result->uploadDate = new \DateTime($date); + + $result->category = Utils::arrayGet($microformat, 'playerMicroformatRenderer.category'); + + $result->durationSeconds = Utils::arrayGet($videoDetails, 'lengthSeconds'); + $result->viewCount = Utils::arrayGet($videoDetails, 'viewCount'); + + $result->keywords = Utils::arrayGet($videoDetails, 'keywords', []); + $result->regionsAllowed = Utils::arrayGet($microformat, 'playerMicroformatRenderer.availableCountries', []); + + return $result; + } +} \ No newline at end of file diff --git a/src/YouTubeDownloader.php b/src/YouTubeDownloader.php index 2c35377..44a7320 100644 --- a/src/YouTubeDownloader.php +++ b/src/YouTubeDownloader.php @@ -6,6 +6,7 @@ use YouTube\Exception\VideoNotFoundException; use YouTube\Exception\YouTubeException; use YouTube\Models\VideoInfo; +use YouTube\Models\YouTubeCaption; use YouTube\Models\YouTubeConfigData; use YouTube\Responses\PlayerApiResponse; use YouTube\Responses\VideoPlayerJs; @@ -21,6 +22,7 @@ function __construct() $this->client = new Browser(); } + // Specify network options to be used in all network requests public function getBrowser(): Browser { return $this->client; @@ -51,8 +53,7 @@ public function getSearchSuggestions(string $query): array public function getVideoInfo(string $videoId): ?VideoInfo { $page = $this->getPage($videoId); - // TODO - return null; + return $page->getVideoInfo(); } public function getPage(string $url): WatchVideoPage @@ -142,8 +143,47 @@ public function getDownloadLinks(string $video_id, array $extra = []): DownloadO $links = SignatureLinkParser::parseLinks($player_response, $player); // since we already have that information anyways... - $info = PlayerVideoInfoMapper::map($player_response); + $info = VideoInfoMapper::fromInitialPlayerResponse($page->getPlayerResponse()); return new DownloadOptions($links, $info); } + + /** + * @param string $videoId + * @return YouTubeCaption[] + */ + public function getCaptions(string $videoId): array + { + $pageResponse = $this->getPage($videoId); + $data = $pageResponse->getPlayerResponse(); + + return array_map(function ($item) { + + $temp = new YouTubeCaption(); + $temp->name = Utils::arrayGet($item, "name.simpleText"); + $temp->baseUrl = Utils::arrayGet($item, "baseUrl"); + $temp->languageCode = Utils::arrayGet($item, "languageCode"); + $vss = Utils::arrayGet($item, "vssId"); + $temp->isAutomatic = Utils::arrayGet($item, "kind") === "asr" || strpos($vss, "a.") !== false; + return $temp; + + }, $data->getCaptionTracks()); + } + + public function getThumbnails(string $videoId): array + { + $videoId = Utils::extractVideoId($videoId); + + if ($videoId) { + return [ + 'default' => "https://img.youtube.com/vi/{$videoId}/default.jpg", + 'medium' => "https://img.youtube.com/vi/{$videoId}/mqdefault.jpg", + 'high' => "https://img.youtube.com/vi/{$videoId}/hqdefault.jpg", + 'standard' => "https://img.youtube.com/vi/{$videoId}/sddefault.jpg", + 'maxres' => "https://img.youtube.com/vi/{$videoId}/maxresdefault.jpg", + ]; + } + + return []; + } } diff --git a/src/YouTubeStreamer.php b/src/YouTubeStreamer.php index 8c74afa..494cbf8 100644 --- a/src/YouTubeStreamer.php +++ b/src/YouTubeStreamer.php @@ -5,14 +5,14 @@ class YouTubeStreamer { // 4096 - protected $buffer_size = 256 * 1024; + protected int $buffer_size = 256 * 1024; - protected $headers = array(); - protected $headers_sent = false; + protected array $headers = array(); + protected bool $headers_sent = false; - protected $debug = false; + protected bool $debug = false; - protected function sendHeader($header) + protected function sendHeader(string $header): void { if ($this->debug) { var_dump($header); @@ -21,7 +21,12 @@ protected function sendHeader($header) } } - public function headerCallback($ch, $data) + /** + * @param \CurlHandle|resource $ch + * @param string $data + * @return int + */ + public function headerCallback($ch, string $data): int { // this should be first line if (preg_match('/HTTP\/[\d.]+\s*(\d+)/', $data, $matches)) { @@ -48,14 +53,19 @@ public function headerCallback($ch, $data) return strlen($data); } - public function bodyCallback($ch, $data) + /** + * @param \CurlHandle|resource $ch + * @param string $data + * @return int + */ + public function bodyCallback($ch, string $data): int { echo $data; flush(); return strlen($data); } - public function stream($url) + public function stream(string $url): void { $ch = curl_init(); @@ -101,8 +111,5 @@ public function stream($url) $error = ($ret === false) ? sprintf('curl error: %s, num: %s', curl_error($ch), curl_errno($ch)) : null; curl_close($ch); - - // if we are still here by now, then all must be okay - return true; } } diff --git a/tests/YouTubePageTest.php b/tests/YouTubePageTest.php index a6f3217..d291d2e 100644 --- a/tests/YouTubePageTest.php +++ b/tests/YouTubePageTest.php @@ -28,8 +28,6 @@ public function test_get_video_info() $this->assertNotEmpty($info->title); $this->assertNotEmpty($info->channelTitle); $this->assertNotEmpty($info->description); - - $this->assertNotEmpty($info->pageUrl); $this->assertNotEmpty($info->uploadDate); $this->assertNotEmpty($info->thumbnail); } diff --git a/tests/YouTubeTest.php b/tests/YouTubeTest.php index ef19509..d60b1cb 100644 --- a/tests/YouTubeTest.php +++ b/tests/YouTubeTest.php @@ -18,7 +18,7 @@ public function test_browser_cookies() // list cookies $list = $browser->get('https://httpbin.org/cookies'); - $this->assertContains('random_value', $list->body); + $this->assertStringContainsString('random_value', $list->body); } public function test_get_download_links() diff --git a/tests/YouTubeUrlTest.php b/tests/YouTubeUrlTest.php index fb8243c..13b1625 100644 --- a/tests/YouTubeUrlTest.php +++ b/tests/YouTubeUrlTest.php @@ -34,8 +34,6 @@ public function test_channel_parsing() '' => null ]; - $this->assertNull(Utils::extractChannel(null)); - foreach ($tests as $url => $expected) { $this->assertEquals($expected, Utils::extractChannel($url)); }