From 13a750f296b0dd39dec1b1e7ef168253ca32b06a Mon Sep 17 00:00:00 2001 From: Celestora Date: Wed, 23 Mar 2022 21:31:21 +0200 Subject: [PATCH] Draft some playlist-related API methods --- VKAPI/Handlers/Audio.php | 294 +++++++++++++++++++++++++++-- Web/Models/Entities/Playlist.php | 27 +++ Web/Models/Repositories/Audios.php | 2 +- 3 files changed, 303 insertions(+), 20 deletions(-) diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php index 3012c8f8..a596a466 100644 --- a/VKAPI/Handlers/Audio.php +++ b/VKAPI/Handlers/Audio.php @@ -2,6 +2,7 @@ namespace openvk\VKAPI\Handlers; use Chandler\Database\DatabaseConnection; use openvk\Web\Models\Entities\Audio as AEntity; +use openvk\Web\Models\Entities\Playlist; use openvk\Web\Models\Repositories\Audios; use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\Repositories\Util\EntityStream; @@ -61,28 +62,35 @@ final class Audio extends VKAPIRequestHandler } } + private function audioFromAnyId(string $id): ?AEntity + { + $descriptor = explode("_", $id); + if(sizeof($descriptor) === 1) { + if(ctype_digit($descriptor[0])) { + $audio = (new Audios)->get((int) $descriptor[0]); + } else { + $aid = base64_decode($descriptor[0], true); + if(!$aid) + $this->fail(8, "Invalid audio $id"); + + $audio = (new Audios)->get((int) $aid); + } + } else if(sizeof($descriptor) === 2) { + $audio = (new Audios)->getByOwnerAndVID((int) $descriptor[0], (int) $descriptor[1]); + } else { + $this->fail(8, "Invalid audio $id"); + } + + return $audio; + } + function getById(string $audios, ?string $hash = NULL, int $need_user = 0): object { $this->requireUser(); $audioIds = array_unique(explode(",", $audios)); if(sizeof($audioIds) === 1) { - $descriptor = explode("_", $audioIds[0]); - if(sizeof($descriptor) === 1) { - if(ctype_digit($descriptor[0])) { - $audio = (new Audios)->get((int) $descriptor[0]); - } else { - $aid = base64_decode($descriptor[0], true); - if(!$aid) - $this->fail(8, "Invalid audio $audioIds[0]"); - - $audio = (new Audios)->get((int) $aid); - } - } else if(sizeof($descriptor) === 2) { - $audio = (new Audios)->getByOwnerAndVID((int) $descriptor[0], (int) $descriptor[1]); - } else { - $this->fail(8, "Invalid audio $audioIds[0]"); - } + $audio = $this->audioFromAnyId($audioIds[0]); return (object) [ "count" => 1, @@ -104,6 +112,18 @@ final class Audio extends VKAPIRequestHandler ]; } + function isLagtrain(string $audio_id): int + { + $this->requireUser(); + + $audio = $this->audioFromAnyId($audio_id); + if(!$audio) + $this->fail(0404, "Audio not found"); + + # Possible information disclosure risks are acceptable :D + return (int) (strpos($audio->getName(), "Lagtrain") !== false); + } + // TODO stub function getRecommendations(): object { @@ -149,6 +169,8 @@ final class Audio extends VKAPIRequestHandler function getCount(int $owner_id, int $uploaded_only = 0): int { + $this->requireUser(); + if($owner_id < 0) { $owner_id *= -1; $group = (new Clubs)->get($owner_id); @@ -177,9 +199,6 @@ final class Audio extends VKAPIRequestHandler { $this->requireUser(); - if($album_id != 0) - $this->fail(10, "album_id is not supported"); - $shuffleSeed = NULL; $shuffleSeedStr = NULL; if($shuffle == 1) { @@ -200,6 +219,28 @@ final class Audio extends VKAPIRequestHandler } } + if($album_id != 0) { + $album = (new Audios)->getPlaylist($album_id); + if(!$album) + $this->fail(0404, "album_id invalid"); + else if(!$album->canBeViewedBy($this->getUser())) + $this->fail(600, "Can't open this album for reading"); + + $songs = []; + $list = $album->getAudios($offset, $count, $shuffleSeed); + foreach($list as $song) + $songs[] = $this->toSafeAudioStruct($song, $hash, $need_user == 1); + + $response = (object) [ + "count" => sizeof($songs), + "items" => $songs, + ]; + if(!is_null($shuffleSeed)) + $response->shuffle_seed = $shuffleSeedStr; + + return $response; + } + if(!is_null($audio_ids)) { $audio_ids = explode(",", $audio_ids); if(!$audio_ids) @@ -503,4 +544,219 @@ final class Audio extends VKAPIRequestHandler return $this->getById($vid, $hash)->items[0]; } + + function getAlbums(?int $owner_id = NULL, int $offset = 0, int $count = 50, int $drop_private = 1): object + { + $this->requireUser(); + + $owner_id ??= $this->getUser()->getId(); + $playlists = []; + foreach((new Audios)->getPlaylistsByEntityId($owner_id, $offset, $count) as $playlist) { + if(!$playlist->canBeViewedBy($this->getUser())) { + if($drop_private == 1) + continue; + + $playlists[] = NULL; + continue; + } + + $playlists[] = $playlist->toVkApiStructure($this->getUser()); + } + + return (object) [ + "count" => sizeof($playlists), + "items" => $playlists, + ]; + } + + function searchAlbums(string $query, int $offset = 0, int $limit = 25, int $drop_private = 0): object + { + $this->requireUser(); + + $playlists = []; + $search = (new Audios)->searchPlaylists($query)->offsetLimit($offset, $limit); + foreach($search as $playlist) { + if(!$playlist->canBeViewedBy($this->getUser())) { + if($drop_private == 0) + $playlists[] = NULL; + + continue; + } + + $playlists[] = $playlist->toVkApiStruct($this->getUser()); + } + + return (object) [ + "count" => sizeof($playlists), + "items" => $playlists, + ]; + } + + function addAlbum(string $title, ?string $description = NULL, int $group_id = 0): int + { + $this->requireUser(); + + $group = NULL; + if($group_id != 0) { + $group = (new Clubs)->get($group_id); + if(!$group_id) + $this->fail(0404, "Invalid group_id"); + else if(!$group->canBeModifiedBy($this->getUser())) + $this->fail(600, "Insufficient rights to this group"); + } + + $album = new Playlist; + $album->setName($title); + if(!is_null($group)) + $album->setOwner($group_id * -1); + else + $album->setOwner($this->getUser()->getId()); + + if(!is_null($description)) + $album->setDescription($description); + + $album->save(); + + return $album->getId(); + } + + function editAlbum(int $album_id, ?string $title = NULL, ?string $description = NULL): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($album_id); + if(!$album) + $this->fail(0404, "Album not found"); + else if(!$album->canBeModifiedBy($this->getUser())) + $this->fail(600, "Insufficient rights to this album"); + + if(!is_null($title)) + $album->setName($title); + + if(!is_null($description)) + $album->setDescription($description); + + return (int) !(!$title && !$description); + } + + function deleteAlbum(int $album_id): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($album_id); + if(!$album) + $this->fail(0404, "Album not found"); + else if(!$album->canBeModifiedBy($this->getUser())) + $this->fail(600, "Insufficient rights to this album"); + + $album->delete(); + + return 1; + } + + function moveToAlbum(int $album_id, string $audio_ids): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($album_id); + if(!$album) + $this->fail(0404, "Album not found"); + else if(!$album->canBeModifiedBy($this->getUser())) + $this->fail(600, "Insufficient rights to this album"); + + $audios = []; + $audio_ids = array_unique(explode(",", $audio_ids)); + if(sizeof($audio_ids) < 1 || sizeof($audio_ids) > 1000) + $this->fail(8, "audio_ids must contain at least 1 audio and at most 1000"); + + foreach($audio_ids as $audio_id) { + $audio = $this->audioFromAnyId($audio_id); + if(!$audio) + continue; + else if($audio->canBeViewedBy($this->getUser())) + continue; + + $audios[] = $audio; + } + + if(sizeof($audios) < 1) + return 0; + + $res = 1; + try { + foreach ($audios as $audio) + $res = min($res, $album->add($audio)); + } catch(\OutOfBoundsException $ex) { + return 0; + } + + return $res; + } + + function removeFromAlbum(int $album_id, string $audio_ids): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($album_id); + if(!$album) + $this->fail(0404, "Album not found"); + else if(!$album->canBeModifiedBy($this->getUser())) + $this->fail(600, "Insufficient rights to this album"); + + $audios = []; + $audio_ids = array_unique(explode(",", $audio_ids)); + if(sizeof($audio_ids) < 1 || sizeof($audio_ids) > 1000) + $this->fail(8, "audio_ids must contain at least 1 audio and at most 1000"); + + foreach($audio_ids as $audio_id) { + $audio = $this->audioFromAnyId($audio_id); + if(!$audio) + continue; + else if($audio->canBeViewedBy($this->getUser())) + continue; + + $audios[] = $audio; + } + + if(sizeof($audios) < 1) + return 0; + + foreach($audios as $audio) + $album->remove($audio); + + return 1; + } + + function copyToAlbum(int $album_id, string $audio_ids): int + { + return $this->moveToAlbum($album_id, $audio_ids); + } + + function bookmarkAlbum(int $id): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($id); + if(!$album) + $this->fail(0404, "Not found"); + + if(!$album->canBeViewedBy($this->getUser())) + $this->fail(600, "Access error"); + + return (int) $album->bookmark($this->getUser()); + } + + function unBookmarkAlbum(int $id): int + { + $this->requireUser(); + + $album = (new Audios)->getPlaylist($id); + if(!$album) + $this->fail(0404, "Not found"); + + if(!$album->canBeViewedBy($this->getUser())) + $this->fail(600, "Access error"); + + return (int) $album->unbookmark($this->getUser()); + } } diff --git a/Web/Models/Entities/Playlist.php b/Web/Models/Entities/Playlist.php index 5c439166..95025fef 100644 --- a/Web/Models/Entities/Playlist.php +++ b/Web/Models/Entities/Playlist.php @@ -15,6 +15,7 @@ class Playlist extends MediaCollection protected $relTableName = "playlist_relations"; protected $entityTableName = "audios"; protected $entityClassName = 'openvk\Web\Models\Entities\Audio'; + protected $allowDuplicates = false; private $importTable; @@ -33,6 +34,11 @@ class Playlist extends MediaCollection return NULL; } + function getLength(): int + { + return $this->getRecord()->length; + } + function getAudios(int $offset = 0, ?int $limit = NULL, ?int $shuffleSeed = NULL): \Traversable { if(!$shuffleSeed) @@ -114,6 +120,27 @@ class Playlist extends MediaCollection return $count > 0; } + function toVkApiStruct(?User $user = NULL): object + { + $oid = $this->getOwner()->getId(); + if($this->getOwner() instanceof Club) + $oid *= -1; + + return (object) [ + "id" => $this->getId(), + "owner_id" => $oid, + "title" => $this->getTitle(), + "description" => $this->getDescription(), + "size" => $this->size(), + "length" => $this->getLength(), + "created" => $this->getCreationTime()->relative(), + "modified" => $this->getCreationTime()->relative(), + "accessible" => $this->canBeViewedBy($user), + "editable" => $this->canBeModifiedBy($user), + "bookmarked" => $this->isBookmarkedBy($user), + ]; + } + function setLength(): void { throw new \LogicException("Can't set length of playlist manually"); diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php index 614e9512..dfeb8978 100644 --- a/Web/Models/Repositories/Audios.php +++ b/Web/Models/Repositories/Audios.php @@ -41,7 +41,7 @@ class Audios return new Audio($audio); } - private function getPlaylist(int $id): ?Playlist + function getPlaylist(int $id): ?Playlist { $playlist = $this->playlists->get($id); if(!$playlist)