├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── docs ├── Album.md ├── Artist.md ├── Documentation.md ├── Duration.md ├── Episode.md ├── Filter.md ├── Location.md ├── Media.md ├── Movie.md ├── PlexApi.md ├── Season.md ├── Section.md ├── Show.md ├── Size.md ├── Tests.md └── Track.md ├── phpunit.xml ├── src └── jc21 │ ├── Collections │ └── ItemCollection.php │ ├── Iterators │ └── ItemIterator.php │ ├── Movies │ └── Movie.php │ ├── Music │ ├── Album.php │ ├── Artist.php │ └── Track.php │ ├── PlexApi.php │ ├── Section.php │ ├── TV │ ├── Episode.php │ ├── Season.php │ └── Show.php │ └── Util │ ├── Duration.php │ ├── Filter.php │ ├── Item.php │ ├── Location.php │ ├── Media.php │ └── Size.php └── tests ├── ItemList.php ├── PlexApiTest.php ├── SectionList.php ├── bootstrap.php └── refresh-library.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | vendor 4 | composer.lock 5 | *.env 6 | *.cache -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 1-clause BSD License (BSD-1-Clause) 2 | 3 | Copyright (c) 2015 jc21 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | THIS SOFTWARE IS PROVIDED BY jc21 “AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL jc21 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Plex API for PHP 2 | ================================================ 3 | 4 | This is a basic API wrapper for Plex. See the 5 | [documentation](docs/Documentation.md) for functionality. 6 | 7 | This doesn't use the Plex.tv API apart from 8 | signing in. 9 | 10 | XML data returned is interpreted into arrays. 11 | 12 | ### Installing via Composer 13 | 14 | ```bash 15 | # Install Composer 16 | curl -sS https://getcomposer.org/installer | php 17 | ``` 18 | 19 | Next, run the Composer command to install the latest stable version: 20 | 21 | ```bash 22 | composer.phar require jc21/plex-api 23 | ``` 24 | 25 | After installing, you need to require Composer's autoloader: 26 | 27 | ```php 28 | require 'vendor/autoload.php'; 29 | ``` 30 | 31 | ### Using 32 | 33 | ```php 34 | use jc21\PlexApi; 35 | 36 | $client = new PlexApi('192.168.0.10'); 37 | $client->setAuth('username', 'password'); 38 | $result = $client->getOnDeck(); 39 | print_r($result); 40 | ``` 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jc21/plex-api", 3 | "description": "PHP Api for Plex", 4 | "keywords": [ 5 | "plex", 6 | "api", 7 | "media" 8 | ], 9 | "homepage": "https://github.com/jc21/plex-api", 10 | "license": "BSD-1-Clause", 11 | "authors": [ 12 | { 13 | "name": "Jamie Curnow", 14 | "email": "jc@jc21.com" 15 | } 16 | ], 17 | "minimum-stability": "stable", 18 | "require": { 19 | "php": ">=7.4", 20 | "ext-curl": "*" 21 | }, 22 | "require-dev": { 23 | "symfony/dotenv": "6.2.x-dev", 24 | "phpunit/phpunit": "^9" 25 | }, 26 | "autoload": { 27 | "psr-0": { 28 | "jc21": "src" 29 | } 30 | }, 31 | "scripts": { 32 | "test": "php vendor/bin/phpunit", 33 | "sections": "php tests/SectionList.php", 34 | "items": "php tests/ItemList.php" 35 | } 36 | } -------------------------------------------------------------------------------- /docs/Album.md: -------------------------------------------------------------------------------- 1 | # Album 2 | 3 | The object to represent a music album 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | | :-------- | :---------------------- | :-------------------------------------------------- | 9 | | int | ratingKey | | 10 | | int | parentRatingKey | | 11 | | string | key | The key to get the details of the album | 12 | | string | parentKey | The link back to the artist | 13 | | string | guid | | 14 | | string | parentGuid | | 15 | | string | studio | The studio that produced the album | 16 | | string | type | The media type `album` | 17 | | string | title | The title of the album | 18 | | string | titleSort | The title used in sorting the album in the UI | 19 | | string | parentTitle | The name of the parent artist | 20 | | string | summary | | 21 | | string | rating | User rating | 22 | | int | index | | 23 | | int | viewCount | | 24 | | int | skipCount | | 25 | | int | year | The year the album was released | 26 | | DateTime | lastVeiwedAt | Date/time the album was last played | 27 | | DateTime | originallyAvailableAt | The date/time the album was released | 28 | | DateTime | addedAt | Date/time the album was added to the library | 29 | | DateTime | updatedAt | Date/time the album database entry was last changed | 30 | | string | thumb | URL to thumbnail | 31 | | string | parentThumb | URL to artist thumbnail | 32 | | int | loudnessAnalysisVersion | | 33 | | array | directory | | 34 | | array | genre | Genre's of music on the album | 35 | 36 | ## Function List 37 | 38 | | Visibility | Function (parameters,...): return | 39 | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------- | 40 | | public | __construct(): void
| 41 | | public | __get(string $var): mixed
Magic getter | 42 | | public | __set(string \$var, mixed $val): void
Magic setter | 43 | | public | getChildren(): ItemCollection:Track
Method to retrieve collection of tracks on this album | 44 | | public | addTrack(Track $a): void | 45 | | public static | fromLibrary(array $library): Album
Create a Album from the Plex API call return | 46 | -------------------------------------------------------------------------------- /docs/Artist.md: -------------------------------------------------------------------------------- 1 | # Artist 2 | 3 | The object to represent a music artist 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | | :-------- | :----------- | :-------------------------------------------------- | 9 | | int | ratingKey | | 10 | | string | key | The key to get the details of the artist | 11 | | string | guid | | 12 | | string | type | The media type `artist` | 13 | | string | title | The artist's name | 14 | | string | summary | | 15 | | int | index | | 16 | | int | viewCount | Number of times the artist details have been viewed | 17 | | int | skipCount | | 18 | | DateTime | lastViewedAt | Date/time somebody viewed this artist | 19 | | DateTime | addedAt | Date/time this artist was added to the database | 20 | | DateTime | updatedAt | Date/time this artist's database entry was updated | 21 | | string | thumb | URL to thumbnail image | 22 | | string | art | | 23 | | array | genre | Genre's of music the artist has performed in | 24 | | array | country | Country's the albums were recorded in | 25 | 26 | ## Function List 27 | | Visibility | Function (parameters,...): return | 28 | | :------------ | :----------------------------------------------------------------------------------------------------------------------------------------- | 29 | | public | __construct(): void
| 30 | | public | __get(string $var): mixed
Magic getter | 31 | | public | __set(string \$var, mixed $val): void
Magic setter | 32 | | public | getChildren(): ItemCollection:Album
Method to retrieve all albums written by this artist | 33 | | public | addAlbum(Album $a): void | 34 | | public static | fromLibrary(array $library): Artist
Create a Artist from the Plex API call return | 35 | -------------------------------------------------------------------------------- /docs/Documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ### Main Class 4 | 5 | [PlexApi](PlexApi.md) 6 | 7 | ### Collection Classes 8 | 9 | `ItemCollection` - enables collection functionality and implements the `\IteratorAggregate` interface
10 | `ItemIterator` - enables iterator functionality and implements the `\Iterator` interface 11 | 12 | ### Media Classes 13 | 14 | - Movie 15 | - [Movie](Movie.md)
16 | - TV 17 | - [Show](Show.md)
18 | - [Season](Season.md)
19 | - [Episode](Episode.md)
20 | - Music 21 | - [Artist](Artist.md)
22 | - [Album](Album.md)
23 | - [Track](Track.md)
24 | 25 | ### Utility Classes 26 | 27 | [Duration](Duration.md)
28 | [Filter](Filter.md)
29 | Item - only present for inheritance and `ItemCollection`
30 | [Location](Location.md)
31 | [Media](Media.md)
32 | [Size](Size.md)
33 | [Section](Section.md) 34 | 35 | ### Dev Testing 36 | 37 | [Tests](Tests.md) 38 | 39 |
40 | 41 | ## PlexApi 42 | 43 | > Plex API Class - Communicate with your Plex Media Server. 44 | 45 | **Note**: There is no formal documentation referencing the lifespan of a token. These appear to have a long shelf-life, but it is possible they rotate occasionally. 46 | 47 | ### Example 48 | ```php 49 | $client = new jc21\PlexApi('{Plex server IP}'); 50 | $client->setAuth('username', 'password'); 51 | $sections = $client->getLibrarySections(); 52 | ``` 53 | If you wish to store the authentication token for later usage: 54 | ```php 55 | $client = new jc21\PlexApi('{Plex server IP}'); 56 | $client->setAuth('username', 'password'); 57 | $token = $client->getToken(); 58 | //Store the $token for later 59 | ``` 60 | The token can be reused on new requests later: 61 | ```php 62 | $token = FakeModel::get('token'); 63 | $client = new jc21\PlexApi('{Plex server IP}'); 64 | $client->setToken($token); 65 | //Now able to make requests just as if you started with a username / password 66 | $sections = $client->getLibrarySections(); 67 | ``` 68 | 69 | If you know which library section contains the data content you want (e.g. all your public movies). The following will return an `ItemCollection` of all items in library section 1. I can then loop through that collection to output each item in JSON. 70 | 71 | ```php 72 | $client = new jc21\PlexApi('{Plex server IP}'); 73 | $movieLibrary = $client->getLibrarySectionContents(1, true); 74 | foreach($movieLibrary as $movie) { 75 | print json_encode($movie).PHP_EOL; 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/Duration.md: -------------------------------------------------------------------------------- 1 | # Duration 2 | 3 | The object is created to represent the duration of a media file. The duration can then be output in `H:i:s` format 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | duration | The duration in milliseconds | 10 | 11 | ## Function List 12 | 13 | | Visibility | Function (parameters,...): return | 14 | |:-----------|:---------| 15 | | public | __construct(int $duration): void
| 16 | | public | minutes(): string
The duration represented in minutes | 17 | | public | seconds(): string
The duration represented in seconds | 18 | | public | __toString(): string
Auto convert the object to a string and output in `H:i:s` format | 19 | | 20 | 21 | ## Examples 22 | 23 | ```php 24 | $dur = new Duration(20152000); 25 | print $dur->seconds().PHP_EOL; // 20152 26 | print $dur->minutes().PHP_EOL; // 335 27 | print (string) $dur; // 05:35:52 28 | ``` -------------------------------------------------------------------------------- /docs/Episode.md: -------------------------------------------------------------------------------- 1 | # Episode 2 | 3 | This class stores TV Episode data 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | ratingKey | An index | 10 | | int | parentRatingKey | The index for the parent season | 11 | | int | grandparentRatingKey | The index for the grandparent show | 12 | | string | key | A string that can be used to query the object directly | 13 | | string | parentKey | A string that can be used to query the parent season | 14 | | string | grandparentKey | A string to query the grandparent show | 15 | | string | guid | The GUID | 16 | | string | parentGuid | The parent season GUID | 17 | | string | grandparentGuid | The grandparent show GUID | 18 | | string | type | What type of media is this (`episode`) | 19 | | string | title | The title of the episode | 20 | | string | parentTitle | The title of the season | 21 | | string | grandparentTitle | The title of the show | 22 | | string | contentRating | The content rating of the episode (e.g. TV-G, TV-PG, etc) | 23 | | string | summary | The summary of the episode | 24 | | int | index | | 25 | | int | parentIndex | | 26 | | string | audienceRating | The audience rating | 27 | | string | audienceRatingImage | | 28 | | int | viewCount | The number of times this episode has been viewed | 29 | | string | thumb | The thumbnail image | 30 | | string | parentThumb | The parent season thumbnail image | 31 | | string | grandparentThumb | The grandparent show thumbnail image | 32 | | string | art | The background artwork | 33 | | string | grandparentArt | The shows artwork | 34 | | [Duration](Duration.md) | duration | The duration | 35 | | `DateTime` | lastViewedAt | The last date and time the episode was viewed | 36 | | `DateTime` | originallyAvailableAt | The original air date of the episode | 37 | | `DateTime` | addedAt | The date the episode was added to the library | 38 | | `DateTime` | updatedAt | The date the episode's entry in the library was updated | 39 | | [Media](Media.md) | media | The details of the media | 40 | 41 | ## Function List 42 | 43 | | Visibility | Function (parameters,...): return | 44 | |:-----------|:---------| 45 | | public | __construct(): void | 46 | | public | __get(string $var): mixed
Magic getter | 47 | | public | __set(string $var, mixed $val): void
Magic setter | 48 | | public static | fromLibrary(array $lib): Episode
Create an object from the array data from the PlexApi | -------------------------------------------------------------------------------- /docs/Filter.md: -------------------------------------------------------------------------------- 1 | # Filter 2 | 3 | This class is specifically intended to filter a library section, but could be used for other purposes. Objects are `immutable` as there are no publicly available properties or setter methods. **Filters matches case-insensitive results.** 4 | 5 | ## Function List 6 | 7 | | Visibility | Function (parameters,...): return | 8 | | :--------- | :-------------------------------- | 9 | | public | __construct(string $field, string $value, string $operator = `'='`): void | 10 | | public | __toString(): string
Convert the object to a string | 11 | 12 | ## Examples 13 | 14 | The `field` parameter in the constructor can accept, `title`, `studio`, `year`, `rating`, `contentRating`. Any other value will throw an `Exception` 15 | 16 | Searching for *George of the Jungle* 17 | 18 | ```php 19 | $filter1 = new Filter('title', 'George'); 20 | $filter2 = new Filter('year', 1997, ">="); 21 | $res = $api->filter(1, [$filter1, $filter2], true); 22 | // Returns any movie with George in the title and released in or after 1997 23 | ``` -------------------------------------------------------------------------------- /docs/Location.md: -------------------------------------------------------------------------------- 1 | # Location 2 | 3 | Object to store library location info 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | 10 | 11 | ## Function List 12 | 13 | | Visibility | Function (parameters,...): return | 14 | |:-----------|:---------| 15 | | public | __get(string $var): mixed
Magic getter | 16 | | public static | fromLibrary(array $location): Location
Create a library object from PlexApi call data | 17 | -------------------------------------------------------------------------------- /docs/Media.md: -------------------------------------------------------------------------------- 1 | # Media 2 | 3 | This object is created for each media file 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | id | | 10 | | [Duration](Duration.md) | duration | The duration of the media | 11 | | int | bitrate | The total bitrate | 12 | | int | width | The pixel width of the video | 13 | | int | height | The pixel heigh of the video | 14 | | float | aspectRatio | The aspect ratio (1.6, 1.77, 2.35, etc) | 15 | | int | audioChannels | The number of audio channels | 16 | | string | audioCodec | The audio codec used (mp3, aac, etc) | 17 | | string | videoCodec | The video codec used (hvec, h264, etc) | 18 | | string | videoResolution | The resolution (720, 1080, 4k, etc) | 19 | | string | videoFrameRate | The frame rate | 20 | | bool | optomizedForStreaming | | 21 | | string | audioProfile | The audio profile info | 22 | | bool | has64bitOffsets | | 23 | | string | videoProfile | The video profile info | 24 | | string | title | The title of the media | 25 | | [Size](Size.md) | size | The size of the file | 26 | | string | path | The path of the file | 27 | 28 | ## Function List 29 | 30 | | Visibility | Function (parameters,...): return | 31 | |:-----------|:---------| 32 | | public | __get(string $var): mixed
Magic getter | 33 | | public | __set(string $var, mixed$val): void
Magic setter | 34 | | public static | fromLibrary(array $lib): Media
Method to create Media object from Plex API call | -------------------------------------------------------------------------------- /docs/Movie.md: -------------------------------------------------------------------------------- 1 | # Movie 2 | 3 | The object to represent a movie 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | ratingKey | | 10 | | string | key | The key to get details of the movie | 11 | | string | guid | | 12 | | string | studio | The movie studio that created the movie | 13 | | string | type | The media type (`movie`) | 14 | | string | title | The title of the movie | 15 | | string | titleSort | The title used to sort the movie for display | 16 | | string | contentRating | The content rating of the movie (G, PG, PG-13, etc) | 17 | | string | summary | The summary of the movie | 18 | | string | audienceRating | The audience rating of the movie | 19 | | int | viewCount | The number of times this movie has been viewed | 20 | | `DateTime` | lastViewedAt | The last date and time this movie was watched | 21 | | int | year | The year the movie was released | 22 | | string | tagline | Any tagline for the movie | 23 | | string | thumb | Movie thumbnail | 24 | | string | art | Movie artwork | 25 | | [Duration](Duration.md) | duration | The duration of the movie | 26 | | `DateTime` | originallyAvilableAt | The date and time of movie was originally released | 27 | | `DateTime` | addedAt | The date and time the movie was added to the library | 28 | | `DateTime` | updatedAt | The date and time the movie entry in the library was updated | 29 | | string | audienceRatingImage | | 30 | | string | primaryExtraKey | | 31 | | array:string | genre | The genres the movie falls into | 32 | | array:string | director | The directors for the movie | 33 | | array:string | writer | The movie writers | 34 | | array:string | country | The counties the movie was filmed in | 35 | | array:string | role | The actor/actresses in the movie | 36 | | [Media](Media.md) | media | The media for the movie | 37 | 38 | ## Function List 39 | | Visibility | Function (parameters,...): return | 40 | |:-----------|:---------| 41 | | public | __construct(): void
| 42 | | public | __get(string $var): mixed
Magic getter | 43 | | public | __set(string $var, mixed $val): void
Magic setter | 44 | | public static | fromLibrary(array $library): Movie
Create a movie from the Plex API call return | 45 | 46 | ## Examples 47 | 48 | Simple example which can be duplicated by just passing in `true` as the second parameter to the `getLibrarySectionContents` method. 49 | 50 | ```php 51 | $res = $api->getLibrarySectionContents(1); 52 | foreach($res['Video'] as $m) { 53 | $movie = Movie::fromLibrary($m); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/PlexApi.md: -------------------------------------------------------------------------------- 1 | # PlexAPI 2 | 3 | ## Function List 4 | 5 | | Visibility | Function (parameters,...): return | 6 | | :--------------- | :---------------- | 7 | | public | __construct(string $host = `'127.0.0.1'`, mixed/int $port = 32400, bool $ssl = false) : void
Instantiate the class with your Host/Port | 8 | | public | getBaseInfo() : array\|bool
Get Plex Server basic info | 9 | | public | getAccount() : array\|bool
Get account info | 10 | | public | getLastCallStats() : array
Get last curl stats, for debugging purposes | 11 | | public | getLibrarySections() : array\|bool
Get Library Sections ie Movies, TV Shows etc | 12 | | public | getLibrarySectionContents(int $sectionKey, bool $returnCollection = `false`) : array\|ItemCollection\|bool
Get Library Section contents | 13 | | public | getMetadata(int $item) : array\|bool
Get Metadata for an Item | 14 | | public | getArtwork(Movie $item, string $tag) : string
Get binary data for artwork, can store as `jpg` at return | 15 | | public | getOnDeck(bool $returnCollection = `false`) : array\|ItemCollection\|bool
Get On Deck Info | 16 | | public | getRecentlyAdded(bool $returnCollection = `false`) : array\|ItemCollection\|bool
Get Recently Added | 17 | | public | getServers() : array\|bool
Get Servers | 18 | | public | getSessions() : array\|bool
Get Sessions from Plex | 19 | | public | getToken() : string/bool
Tests the set username and password and returns the auth token | 20 | | public | getTranscodeSessions() : array\|bool
Get Transcode Sessions from Plex | 21 | | public | refreshLibrarySection(int $sectionKey, bool $force = `false`) : null/bool
Refresh a Library Section. This makes Plex search for new and removed items from the Library paths. Doesn't return anything when successful. | 22 | | public | refreshMetadata(int $item, bool $force = `false`) : null/bool
Refresh a specific item. Doesn't return anything when successful. | 23 | | public | search(string $query, bool $returnCollection = `false`) : array\|ItemCollection\|bool
Search for Items | 24 | | public | filter(int $sectionKey, array $filters, bool $returnCollection = `false`): array\|ItemCollection\|bool
Filter a library section using common syntax | 25 | | public | setAuth(string $username, string $password) : void
Credentials for logging into Plex.tv. Username can also be an email address. | 26 | | public | setToken(string $token) : void
A valid token for a logged in device, obtainable using setAuth(). | 27 | | public | setClientIdentifier(string $identifier) : void
setClientIdentifier | 28 | | public | setDevice(string $name) : void
setDevice | 29 | | public | setDeviceName(string $name) : void
setDeviceName | 30 | | public | setProductName(string $name) : void
setProductName | 31 | | public | setTimeout(int $timeout) : void
setTimeout | 32 | | protected | call(string $path, array $params=array(), string $method = `'GET'`, bool $isLoginCall = `false`) : array\|bool
Make an API Call or Login Call | 33 | | private | buildHttpQuery(array $query): string
Build http query string from array of `Filter` objects | 34 | | protected static | normalizeSimpleXML(mixed $obj, mixed $result) : void
normalizeSimpleXML | 35 | | protected static | xml2array(mixed $xml) : mixed
xml2array | 36 | | public static | array2collection(array $array): ItemCollection | 37 | | public static | array2object(array $array): Movie\|Show\|Season\|Episode\|Artist\|Album\|Track | 38 | -------------------------------------------------------------------------------- /docs/Season.md: -------------------------------------------------------------------------------- 1 | # Season 2 | 3 | This represents a single season within a show 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | ratingKey | The key index for the season | 10 | | int | parentRatingKey | The key index of the show | 11 | | string | key | The key for the season (used to get the details and children) | 12 | | string | parentKey | The key for the parent show | 13 | | string | guid | The guid for the season | 14 | | string | parentGuid | The GUID of the parent show | 15 | | string | type | The type of the media (`season`) | 16 | | string | title | The title of the season | 17 | | string | parentTitle | The title of the parent show | 18 | | string | summary | Th sumary of the season | 19 | | string | index | The index for the season | 20 | | int | parentYear | The year the parent show was released | 21 | | string | thumb | The thumbnail for this season | 22 | | string | art | The artwork for the season | 23 | | string | parentThumb | The thumbnail of the parent | 24 | | string | parentTheme | The theme of the parent | 25 | | int | leafCount | The number of episodes in this season | 26 | | int | viewedLeafCount | The number of times an episode in this season was viewed | 27 | | `DateTime` | addedAt | The date and time this season was added to the library | 28 | | `DateTime` | updatedAt | The date and time this season was last updated in the library | 29 | | `ItemCollection`:`Episode`(Episode.md) | episodes | An array to store all the episodes in this season | 30 | 31 | ## Function List 32 | 33 | | Visibility | Function (parameters,...): return | 34 | |:-----------|:---------| 35 | | public | __construct(): void
| 36 | | public | __get(string $var): mixed
Magic getter | 37 | | public | __set(string $var, mixed $val): void
Magic setter | 38 | | public | getChildren(): ItemCollection:Episodes
Method to get all episodes within this season | 39 | | public | addEpisode(Episode $episode): void
Method to add an episode to the season | 40 | | public static | fromLibrary(array $lib): Season
Method to create a season | 41 | -------------------------------------------------------------------------------- /docs/Section.md: -------------------------------------------------------------------------------- 1 | # Section 2 | 3 | This class is intended to represent a library 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | bool | allowSync | Does this library allow sync/download | 10 | | string | art | Library artwork | 11 | | string | composite | | 12 | | bool | filters | Are any filters assigned to the library | 13 | | bool | refreshing | Should we refresh the metadata in the library | 14 | | string | thumb | Library thumbnail | 15 | | int | key | Library index | 16 | | string | libraryType | What type of library is this (movie, show, photo, music) | 17 | | string | type | What type of library is this (movie, show, photo, music) | 18 | | string | title | Library title | 19 | | string | agent | Library agent | 20 | | string | scanner | Library scanner | 21 | | string | language | Language the library is in | 22 | | string | uuid | | 23 | | `DateTime` | createdAt | Date and time the library was created | 24 | | `DateTime` | updatedAt | Date/time the library was last updated | 25 | | `DateTime` | scannedAt | Date/time the library was last scanned | 26 | | int | content | | 27 | | int | directory | | 28 | | int | contentChangedAt | | 29 | | bool | hidden | Is the library hidden | 30 | | [Location](Location.md) | location | Location data for the library | 31 | 32 | ## Function List 33 | 34 | | Visibility | Function (parameters,...): return | 35 | |:-----------|:---------| 36 | | public | __construct(): void
| 37 | | public | __get(string $var): mixed
Magic getter | 38 | | public | __set(string $var, mixed $val): void
Magic getter | 39 | | public static | fromLibrary(array $lib): Section
Create a Section of PlexApi call | 40 | -------------------------------------------------------------------------------- /docs/Show.md: -------------------------------------------------------------------------------- 1 | # Show 2 | 3 | A TV Show 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | |:----------|:---------|:------------| 9 | | int | ratingKey | The key of the show | 10 | | string | key | The string to query the details of the show | 11 | | string | guid | That GUID | 12 | | string | studio | The studio that created the show | 13 | | string | type | The type of the media (`show`) | 14 | | string | title | The title of the show | 15 | | string | titleSort | The string that is used to actually sort the show for display | 16 | | int | episodeSort | An integer representing the way the episodes should be sorted | 17 | | string | contentRating | The content rating (TV-G, TV-PG, etc) | 18 | | string | summary | The summary of the whole show | 19 | | int | index | The index | 20 | | string | audienceRating | | 21 | | int | viewCount | The number of times all episodes have been viewed | 22 | | int | skipCount | The number of times an episode was skipped | 23 | | `DateTime` | lastViewedAt | The date the last episode was viewed | 24 | | `DateTime` | addedAt | The date the show was added to the library | 25 | | `DateTime` | updatedAt | The date the show entry in the library was last updated | 26 | | `DateTime` | originallyAvailableAt | The original air date of the first episode in the show | 27 | | int | year | The original air year of the show | 28 | | string | thumb | The show thumbnail | 29 | | string | art | The show artwork | 30 | | [Duration](Duration.md) | duration | The aired duration of each episode | 31 | | int | leafCount | The number of episodes | 32 | | int | viewedLeafCount | The number of viewed episodes | 33 | | int | childCount | The number of child seasons in the show | 34 | | string | audienceRatingImage | | 35 | | array:string | genre | The genres the show is in | 36 | | array:string | role | The actor/actresses in the show | 37 | | `ItemCollection`:`Season`(Season.md) | seasons | The array containing all the seasons | 38 | 39 | ## Function List 40 | 41 | | Visibility | Function (parameters,...): return | 42 | |:-----------|:---------| 43 | | public | __construct(): void
| 44 | | public | __get(string $var): mixed
Magic getter | 45 | | public | __set(string $var, mixed $val): void
Magic setter | 46 | | public | getChildren(): array:Season
Method to get the seasons | 47 | | public | addSeason(Season $season): bool
Method to add a season to the show | 48 | | public static | fromLibrary(array $lib): Show
Method to create a show from the Plex API call | 49 | -------------------------------------------------------------------------------- /docs/Size.md: -------------------------------------------------------------------------------- 1 | # Size 2 | 3 | This represents the size of a media file (in bytes) 4 | 5 | ## Property List 6 | | Data type | Property | Description | 7 | |:----------|:---------|:------------| 8 | | int | size | The size, in bytes, of whatever is being checked | 9 | 10 | ## Function List 11 | | Visibility | Function (parameters,...): return | 12 | |:-----------|:---------| 13 | | public | __construct(int $size): void
| 14 | | public | GB(): string
Convert the size to a GB formatted string | 15 | | public | MB(): string
Convert the size to a MB formatted string | 16 | | public | KB(): string
Convert the size to a KB formatted string | 17 | | public | bytes(): int
Return the size 18 | 19 | ## Examples 20 | 21 | ```php 22 | $size = new Size(1894113877); 23 | print $size->GB().PHP_EOL; // 1.764 gb 24 | print $size->MB().PHP_EOL; // 1,806.368 mb 25 | print $size->KB(); // 1,849,720.583 kb 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/Tests.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | ## Intro 4 | 5 | PHPUnit is added for development purposes to be able to perform unit tests and make sure that all functions operate as expected. To accomplish this an `.env` file needs to be created in the `/tests` folder with the following information: 6 | 7 | | **Env** | **Value** | **Description** | 8 | | ---------------------- | -------------- | -------------------------------------------------------------------------- | 9 | | **PLEX_HOST** | *ip\|hostname* | IP or host name of the Plex server | 10 | | **PLEX_PORT** | *port* | Port to connect to Plex server (optional, defaults 32400) | 11 | | **PLEX_USER** | *email* | Username to login to Plex.tv (only needed once) | 12 | | **PLEX_PASSWORD** | *password* | Password to login to Plex.tv (only needed once) | 13 | | **PLEX_SSL** | *0\|1* | Boolean to connect to Plex server over SSL | 14 | | **MOVIE_TESTS** | *0\|1* | Boolean to conduct tests on movie library (optional) | 15 | | **MOVIE_SECTION_KEY** | *int* | Integer of movie library (run `composer sections` to see list | 16 | | **MOVIE_ITEM_ID** | *int* | Integer key of a specific movie to pull metadata for | 17 | | **MOVIE_SEARCH_QUERY** | *string* | String query to search for in Movie library | 18 | | **MOVIE_FILTER_QUERY** | *string* | String query to filter for in Movie library (must be a 'title') | 19 | | **TV_TESTS** | *0\|1* | Boolean to conduct tests of TV library (optional) | 20 | | **TV_SECTION_KEY** | *int* | Integer of TV library (run `composer sections` to see list) | 21 | | **TV_ITEM_ID** | *int* | Integer key of a specific TV show, season, or episode to pull metadata for | 22 | | **TV_SEARCH_QUERY** | *string* | String query to search for in TV library | 23 | | **TV_FILTER_QUERY** | *string* | String query to filter for in TV library (must be 'title') | 24 | | **MUSIC_TESTS** | *0\|1* | Boolean to conduct tests of Music library (optional) | 25 | | **MUSIC_SECTION_KEY** | *int* | Integer of Music library (run `composer sections` to see list | 26 | | **MUSIC_SEARCH_QUERY** | *string* | String query to search for in Music library | 27 | | **MUSIC_FILTER_QUERY** | *string* | String query to filter for in Music library | 28 | 29 | The `PLEX_*` values are required. The username and password values can be deleted after the token is retrieved 30 | 31 | `*_TESTS` are optional, if they are not present, those tests will not be run. If they are present, then the similar ENV values are required. 32 | 33 | Once you have the PLEX_* values present you can run `composer sections` to retrieve the section keys for your libraries. Put in the section keys for the tests you want to run. After you've made the changes, run `composer test` to run php tests 34 | -------------------------------------------------------------------------------- /docs/Track.md: -------------------------------------------------------------------------------- 1 | # Track 2 | 3 | The object to represent a music track 4 | 5 | ## Property List 6 | 7 | | Data type | Property | Description | 8 | | :-------- | :------------------- | :------------------------------------------------ | 9 | | int | ratingKey | | 10 | | int | parentRatingKey | | 11 | | int | grandparentRatingKey | | 12 | | string | key | The key to get the track details | 13 | | string | parentKey | The key to get the album details | 14 | | string | grandparentKey | The key to get the artist details | 15 | | string | guid | | 16 | | string | parentGuid | | 17 | | string | grandparentGuid | | 18 | | string | type | The media type `track` | 19 | | string | title | The track title | 20 | | string | parentTitle | The album title | 21 | | string | grandparentTitle | The artist name | 22 | | string | parentStudio | The publishing album studio | 23 | | string | summary | | 24 | | int | index | | 25 | | int | parentIndex | | 26 | | int | ratingCount | | 27 | | int | parentYear | The release year | 28 | | string | thumb | | 29 | | string | parentThumb | | 30 | | string | grandparentThumb | | 31 | | Duration | duration | | 32 | | DateTime | addedAt | The date/time the track was added to the database | 33 | | DateTime | updatedAt | The date/time of track was last updated | 34 | | Media | media | The details of the media file itself | 35 | 36 | ## Function List 37 | 38 | | Visibility | Function (parameters,...): return | 39 | | :------------ | :--------------------------------------------------------------------------------------------------------------------------------------- | 40 | | public | __construct(): void
| 41 | | public | __get(string $var): mixed
Magic getter | 42 | | public | __set(string \$var, mixed $val): void
Magic setter | 43 | | public static | fromLibrary(array $library): Album
Create a Album from the Plex API call return | 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | src/ 10 | 11 | 12 | 13 | 14 | tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/jc21/Collections/ItemCollection.php: -------------------------------------------------------------------------------- 1 | collection = []; 31 | } 32 | 33 | /** 34 | * Method to get the count of items in the collection 35 | * 36 | * @return int 37 | */ 38 | public function count(): int 39 | { 40 | return count($this->collection); 41 | } 42 | 43 | /** 44 | * Get the size of the media in the collection 45 | * 46 | * @return Size 47 | */ 48 | public function size(): Size 49 | { 50 | $int = 0; 51 | foreach ($this->collection as $item) { 52 | if (method_exists($item, 'getChildren')) { 53 | $item->getChildren()->size(); 54 | } 55 | /** @var Movie|Episode|Track $item */ 56 | $int += (int) $item->media->size->bytes(); 57 | } 58 | $size = new Size($int); 59 | 60 | return $size; 61 | } 62 | 63 | /** 64 | * Method to get data at a position 65 | * 66 | * @param int $position 67 | * 68 | * @return null|Movie|Show|Season|Episode|Artist|Album|Track 69 | */ 70 | public function getData(int $position = 0) 71 | { 72 | if (isset($this->collection[$position])) { 73 | return $this->collection[$position]; 74 | } 75 | 76 | return null; 77 | } 78 | 79 | /** 80 | * Method to add a Item to the collection 81 | * 82 | * @param Item $Item 83 | */ 84 | public function addData(Item $Item) 85 | { 86 | $this->collection[] = $Item; 87 | } 88 | 89 | /** 90 | * Method to get an iterator for the collection 91 | * 92 | * @return ItemIterator 93 | * 94 | * {@inheritDoc} 95 | */ 96 | public function getIterator(): Iterator 97 | { 98 | return new ItemIterator($this); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/jc21/Iterators/ItemIterator.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 36 | } 37 | 38 | /** 39 | * Method to return the current key position 40 | * 41 | * @return int 42 | */ 43 | public function key() 44 | { 45 | return $this->position; 46 | } 47 | 48 | /** 49 | * Method to return the current Item 50 | * 51 | * @return Item 52 | */ 53 | public function current() 54 | { 55 | return $this->collection->getData($this->position); 56 | } 57 | 58 | /** 59 | * Method to increment the iterator 60 | */ 61 | public function next(): void 62 | { 63 | $this->position++; 64 | } 65 | 66 | /** 67 | * Method to rewind the iterator 68 | */ 69 | public function rewind(): void 70 | { 71 | $this->position = 0; 72 | } 73 | 74 | /** 75 | * Method to check if the current position contains an object 76 | * 77 | * @return bool 78 | */ 79 | public function valid(): bool 80 | { 81 | return !is_null($this->collection->getData($this->position)); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/jc21/Movies/Movie.php: -------------------------------------------------------------------------------- 1 | data = [ 59 | 'ratingKey' => null, 60 | 'key' => null, 61 | 'guid' => null, 62 | 'studio' => null, 63 | 'type' => null, 64 | 'title' => null, 65 | 'titleSort' => null, 66 | 'contentRating' => null, 67 | 'summery' => null, 68 | 'audienceRating' => null, 69 | 'viewCount' => null, 70 | 'lastViewedAt' => null, 71 | 'year' => null, 72 | 'tagline' => null, 73 | 'thumb' => null, 74 | 'art' => null, 75 | 'duration' => null, 76 | 'originallyAvailableAt' => null, 77 | 'addedAt' => null, 78 | 'updatedAt' => null, 79 | 'audienceRatingImage' => null, 80 | 'primaryExtraKey' => null, 81 | 'media' => null, 82 | 'genre' => [], 83 | 'director' => [], 84 | 'writer' => [], 85 | 'country' => [], 86 | 'role' => [], 87 | ]; 88 | } 89 | 90 | /** 91 | * Magic getter method 92 | * 93 | * @param string $var 94 | * 95 | * @return mixed 96 | */ 97 | public function __get(string $var) 98 | { 99 | if (isset($this->data[$var])) { 100 | return $this->data[$var]; 101 | } 102 | 103 | return null; 104 | } 105 | 106 | /** 107 | * Magic setter method 108 | * 109 | * @param string $var 110 | * @param mixed $val 111 | */ 112 | public function __set(string $var, $val) 113 | { 114 | $this->data[$var] = $val; 115 | } 116 | 117 | /** 118 | * Method to create an object from a library 119 | * 120 | * @param array $library 121 | * 122 | * @return Movie 123 | */ 124 | public static function fromLibrary(array $library): Movie 125 | { 126 | $me = new static(); 127 | $me->data = $library; 128 | 129 | if (isset($library['duration'])) { 130 | $me->duration = new Duration($library['duration']); 131 | } 132 | 133 | if (isset($library['lastViewedAt'])) { 134 | $lastViewedAt = new DateTime(); 135 | $lastViewedAt->setTimestamp($library['lastViewedAt']); 136 | $me->lastViewedAt = $lastViewedAt; 137 | } 138 | 139 | $addedAt = new DateTime(); 140 | $addedAt->setTimestamp($library['addedAt']); 141 | $me->addedAt = $addedAt; 142 | 143 | $updatedAt = new DateTime(); 144 | $updatedAt->setTimestamp($library['updatedAt']); 145 | $me->updatedAt = $updatedAt; 146 | 147 | if (isset($library['originallyAvailableAt'])) { 148 | $me->originallyAvailableAt = new DateTime($library['originallyAvailableAt']); 149 | } 150 | 151 | if (isset($library['Genre']) && is_array($library['Genre'])) { 152 | if (count($library['Genre']) == 1) { 153 | $me->data['genre'][] = $library['Genre']['tag']; 154 | } else { 155 | foreach ($library['Genre'] as $g) { 156 | $me->data['genre'][] = $g['tag']; 157 | } 158 | } 159 | unset($me->data['Genre']); 160 | } 161 | 162 | if (isset($library['Director']) && is_array($library['Director'])) { 163 | if (count($library['Director']) == 1) { 164 | $me->data['director'][] = $library['Director']['tag']; 165 | } else { 166 | foreach ($library['Director'] as $d) { 167 | $me->data['director'][] = $d['tag']; 168 | } 169 | } 170 | unset($me->data['Director']); 171 | } 172 | 173 | if (isset($library['Writer']) && is_array($library['Writer'])) { 174 | if (count($library['Writer']) == 1) { 175 | $me->data['writer'][] = $library['Writer']['tag']; 176 | } else { 177 | foreach ($library['Writer'] as $w) { 178 | $me->data['writer'][] = $w['tag']; 179 | } 180 | } 181 | unset($me->data['Writer']); 182 | } 183 | 184 | if (isset($library['Role']) && is_array($library['Role'])) { 185 | if (count($library['Role']) == 1) { 186 | $me->data['role'][] = $library['Role']['tag']; 187 | } else { 188 | foreach ($library['Role'] as $r) { 189 | $me->data['role'][] = $r['tag']; 190 | } 191 | } 192 | unset($me->data['Role']); 193 | } 194 | 195 | if (isset($library['Media'])) { 196 | if (isset($library['Media'][0])) { 197 | $arr = []; 198 | foreach ($library['Media'] as $m) { 199 | $media = Media::fromLibrary($m); 200 | $arr[] = $media; 201 | } 202 | $me->media = $arr; 203 | } else { 204 | $media = Media::fromLibrary($library['Media']); 205 | $me->media = $media; 206 | } 207 | unset($me->data['Media']); 208 | } 209 | 210 | if (isset($library['Collection'])) { 211 | $col = []; 212 | if (count($library['Collection']) == 1) { 213 | $col[] = $library['Collection']['tag']; 214 | } else { 215 | foreach ($library['Collection'] as $c) { 216 | $col[] = $c['tag']; 217 | } 218 | } 219 | $me->collection = $col; 220 | unset($me->data['Collection']); 221 | } 222 | 223 | unset($me->data['Country']); 224 | 225 | return $me; 226 | } 227 | 228 | /** 229 | * Method to serialize the object 230 | * 231 | * @return mixed 232 | */ 233 | public function jsonSerialize() 234 | { 235 | return $this->data; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/jc21/Music/Album.php: -------------------------------------------------------------------------------- 1 | tracks = new ItemCollection(); 63 | } 64 | 65 | /** 66 | * Magic getter 67 | * 68 | * @param string $var 69 | * 70 | * @return mixed 71 | */ 72 | public function __get(string $var) 73 | { 74 | if (isset($this->data[$var])) { 75 | return $this->data[$var]; 76 | } 77 | return null; 78 | } 79 | 80 | /** 81 | * Magic setter 82 | * 83 | * @param string $var 84 | * @param mixed $val 85 | */ 86 | public function __set(string $var, $val) 87 | { 88 | $this->data[$var] = $val; 89 | } 90 | 91 | /** 92 | * Method to retrieve tracks on this album 93 | * 94 | * @return ItemCollection 95 | */ 96 | public function getChildren(): ItemCollection 97 | { 98 | return $this->tracks; 99 | } 100 | 101 | /** 102 | * Add a track to the library 103 | * 104 | * @param Track $t 105 | */ 106 | public function addTrack(Track $t) 107 | { 108 | $this->tracks->addData($t); 109 | } 110 | 111 | /** 112 | * Method to create an object from Plex data 113 | * 114 | * @param array $lib 115 | * 116 | * @return Album 117 | */ 118 | public static function fromLibrary(array $lib): Album 119 | { 120 | if (!isset($GLOBALS['client'])) { 121 | throw new Exception('PlexApi client `$client` not available'); 122 | } 123 | global $client; 124 | 125 | $me = new static(); 126 | $me->data = $lib; 127 | 128 | if (isset($lib['lastViewedAt'])) { 129 | $lastViewedAt = new DateTime(); 130 | $lastViewedAt->setTimestamp($lib['lastViewedAt']); 131 | $me->lastViewedAt = $lastViewedAt; 132 | } 133 | 134 | if (isset($lib['addedAt'])) { 135 | $addedAt = new DateTime(); 136 | $addedAt->setTimestamp($lib['addedAt']); 137 | $me->addedAt = $addedAt; 138 | } 139 | 140 | if (isset($lib['updatedAt'])) { 141 | $updatedAt = new DateTime(); 142 | $updatedAt->setTimestamp($lib['updatedAt']); 143 | $me->updatedAt = $updatedAt; 144 | } 145 | 146 | if (isset($lib['originallyAvailableAt'])) { 147 | $me->originallyAvailableAt = new DateTime($lib['originallyAvailableAt']); 148 | } 149 | 150 | if (isset($lib['Genre']) && is_array($lib['Genre'])) { 151 | if (count($lib['Genre']) == 1) { 152 | $me->data['genre'][] = $lib['Genre']['tag']; 153 | } else { 154 | foreach ($lib['Genre'] as $g) { 155 | $me->data['genre'][] = $g['tag']; 156 | } 157 | } 158 | unset($me->data['Genre']); 159 | } 160 | 161 | if (isset($lib['Director']) && is_array($lib['Director'])) { 162 | if (count($lib['Director']) == 1) { 163 | $me->data['director'][] = $lib['Director']['tag']; 164 | } else { 165 | foreach ($lib['Director'] as $d) { 166 | $me->data['director'][] = $d['tag']; 167 | } 168 | } 169 | unset($me->data['Director']); 170 | } 171 | 172 | $res = $client->call($me->key); 173 | if (isset($res['Track']) && is_array($res['Track']) && count($res['Track'])) { 174 | if (isset($res['Track'][0])) { 175 | foreach ($res['Track'] as $t) { 176 | $track = Track::fromLibrary($t); 177 | $me->addTrack($track); 178 | } 179 | } else { 180 | $track = Track::fromLibrary($res['Track']); 181 | $me->addTrack($track); 182 | } 183 | } 184 | 185 | return $me; 186 | } 187 | 188 | /** 189 | * Method to serialize the object 190 | * 191 | * @return array 192 | */ 193 | public function jsonSerialize() 194 | { 195 | return $this->data; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/jc21/Music/Artist.php: -------------------------------------------------------------------------------- 1 | albums = new ItemCollection(); 53 | } 54 | 55 | /** 56 | * Magic getter method 57 | * 58 | * @param string $var 59 | * 60 | * @return mixed 61 | */ 62 | public function __get(string $var) 63 | { 64 | if (isset($this->data[$var])) { 65 | return $this->data[$var]; 66 | } 67 | 68 | return null; 69 | } 70 | 71 | /** 72 | * Magic setter method 73 | * 74 | * @param string $var 75 | * @param mixed $val 76 | */ 77 | public function __set(string $var, $val) 78 | { 79 | $this->data[$var] = $val; 80 | } 81 | 82 | /** 83 | * Method to retrieve the albums this artist has released 84 | * 85 | * @return ItemCollection 86 | */ 87 | public function getChildren(): ItemCollection 88 | { 89 | return $this->albums; 90 | } 91 | 92 | /** 93 | * Method to add album 94 | * 95 | * @param Album $a 96 | */ 97 | public function addAlbum(Album $a) 98 | { 99 | $this->albums->addData($a); 100 | } 101 | 102 | /** 103 | * Method to create an object from a library 104 | * 105 | * @param array $lib 106 | * 107 | * @return Artist 108 | */ 109 | public static function fromLibrary(array $lib): Artist 110 | { 111 | if (!isset($GLOBALS['client'])) { 112 | throw new Exception('PlexApi client `$client` not available'); 113 | } 114 | global $client; 115 | 116 | $me = new static(); 117 | $me->data = $lib; 118 | 119 | if (isset($lib['lastViewedAt'])) { 120 | $lastViewedAt = new DateTime(); 121 | $lastViewedAt->setTimestamp($lib['lastViewedAt']); 122 | $me->lastViewedAt = $lastViewedAt; 123 | } 124 | 125 | if (isset($lib['addedAt'])) { 126 | $addedAt = new DateTime(); 127 | $addedAt->setTimestamp($lib['addedAt']); 128 | $me->addedAt = $addedAt; 129 | } 130 | 131 | if (isset($lib['updatedAt'])) { 132 | $updatedAt = new DateTime(); 133 | $updatedAt->setTimestamp($lib['updatedAt']); 134 | $me->updatedAt = $updatedAt; 135 | } 136 | 137 | if (isset($lib['Genre']) && is_array($lib['Genre'])) { 138 | if (count($lib['Genre']) == 1) { 139 | $me->data['genre'][] = $lib['Genre']['tag']; 140 | } else { 141 | foreach ($lib['Genre'] as $g) { 142 | $me->data['genre'][] = $g['tag']; 143 | } 144 | } 145 | unset($me->data['Genre']); 146 | } 147 | 148 | unset($me->data['Country']); 149 | 150 | $res = $client->call($me->key); 151 | if (isset($res['Directory']) && is_array($res['Directory']) && count($res['Directory'])) { 152 | if (isset($res['Directory'][0])) { 153 | foreach ($res['Directory'] as $a) { 154 | $a = Album::fromLibrary($a); 155 | $me->addAlbum($a); 156 | } 157 | } else { 158 | $a = Album::fromLibrary($res['Directory']); 159 | $me->addAlbum($a); 160 | } 161 | } 162 | 163 | return $me; 164 | } 165 | 166 | /** 167 | * Serialize the object 168 | * 169 | * @return array 170 | */ 171 | public function jsonSerialize() 172 | { 173 | return $this->data; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/jc21/Music/Track.php: -------------------------------------------------------------------------------- 1 | data[$var])) { 60 | return $this->data[$var]; 61 | } 62 | return null; 63 | } 64 | 65 | /** 66 | * Magic setter 67 | * 68 | * @param string $var 69 | * @param mixed $val 70 | */ 71 | public function __set(string $var, $val) 72 | { 73 | $this->data[$var] = $val; 74 | } 75 | 76 | /** 77 | * Method to create an object from the library 78 | * 79 | * @param array $lib 80 | * 81 | * @return Track 82 | */ 83 | public static function fromLibrary(array $lib): Track 84 | { 85 | $me = new static(); 86 | $me->data = $lib; 87 | 88 | if (isset($lib['addedAt'])) { 89 | $addedAt = new DateTime(); 90 | $addedAt->setTimestamp($lib['addedAt']); 91 | $me->addedAt = $addedAt; 92 | } 93 | 94 | if (isset($lib['updatedAt'])) { 95 | $updatedAt = new DateTime(); 96 | $updatedAt->setTimestamp($lib['updatedAt']); 97 | $me->updatedAt = $updatedAt; 98 | } 99 | 100 | if (isset($lib['duration'])) { 101 | $me->duration = new Duration($lib['duration']); 102 | } 103 | 104 | if (isset($lib['Media'])) { 105 | $me->media = Media::fromLibrary($lib['Media']); 106 | unset($lib['Media']); 107 | } 108 | 109 | return $me; 110 | } 111 | 112 | /** 113 | * Method to serialize the object 114 | * 115 | * @return array 116 | */ 117 | public function jsonSerialize() 118 | { 119 | return $this->data; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/jc21/PlexApi.php: -------------------------------------------------------------------------------- 1 | 20 | * @version 2.1 21 | * 22 | * @example 23 | * 24 | * setAuth('username', 'password'); 27 | * $sections = $client->getLibrarySections(); 28 | * 29 | * 30 | */ 31 | 32 | class PlexApi 33 | { 34 | public const VERSION = '2.1'; 35 | 36 | const GET = 'GET'; 37 | const POST = 'POST'; 38 | const PUT = 'PUT'; 39 | const DELETE = 'DELETE'; 40 | 41 | // Plex agents 42 | public const PLEX_AGENT_NONE = 'com.plexapp.agents.none'; 43 | public const PLEX_MOVIE_AGENT = 'tv.plex.agents.movie'; 44 | public const PLEX_IMDB_MOVIE_AGENT = 'com.plexapp.agents.imdb'; 45 | public const PLEX_TV_AGENT = 'tv.plex.agents.series'; 46 | public const PLEX_MUSIC_AGENT = 'tv.plex.agents.music'; 47 | 48 | // Plex scanners 49 | public const PLEX_MOVIE_SCANNER = 'Plex Movie'; 50 | public const PLEX_TV_SCANNER = 'Plex TV Series'; 51 | public const PLEX_MUSIC_SCANNER = 'Plex Music'; 52 | public const PLEX_PHOTO_SCANNER = 'Plex Photo Scanner'; 53 | public const PLEX_VIDEO_SCANNER = 'Plex Video Files Scanner'; 54 | 55 | /** 56 | * The hostname/ip of the Plex server 57 | * 58 | * @var string 59 | */ 60 | protected $host = null; 61 | 62 | /** 63 | * The Port of the Plex Server 64 | * 65 | * @var int 66 | */ 67 | protected $port = 32400; 68 | 69 | /** 70 | * Use SSL communicating with Plex server 71 | * 72 | * @var int 73 | */ 74 | protected $ssl = false; 75 | 76 | /** 77 | * Your Plex.tv Username or Email 78 | * 79 | * @var string 80 | */ 81 | protected $username = null; 82 | 83 | /** 84 | * Your Plex.tv Password 85 | * 86 | * @var string 87 | */ 88 | protected $password = null; 89 | 90 | /** 91 | * The Plex client identifier for this App/Script. 92 | * This shows up in the Devices section of Plex. 93 | * 94 | * @var string 95 | */ 96 | protected $clientIdentifier = 'ec87b5d1-b5e4-4114-ad66-19c747d87c1f'; 97 | 98 | /** 99 | * The Plex product name. 100 | * This shows up in the Devices section of Plex. 101 | * 102 | * @var string 103 | */ 104 | protected $productName = 'PHPClient'; 105 | 106 | /** 107 | * The Plex device. 108 | * This shows up in the Devices section of Plex. 109 | * 110 | * @var string 111 | */ 112 | protected $device = 'Script'; 113 | 114 | /** 115 | * The Plex device name 116 | * This shows up in the Devices section of Plex. 117 | * 118 | * @var string 119 | */ 120 | protected $deviceName = 'Script'; 121 | 122 | /** 123 | * The Socket Timeout limit in seconds 124 | * 125 | * @var int 126 | * 127 | **/ 128 | protected $timeout = 30; 129 | 130 | /** 131 | * The last curl connection stats 132 | * 133 | * @var array 134 | * 135 | **/ 136 | protected $lastCallStats = null; 137 | 138 | /** 139 | * The Auth Token obtained from Plex.tv login 140 | * 141 | * @var string 142 | * 143 | **/ 144 | protected $token = null; 145 | 146 | 147 | /** 148 | * Instantiate the class with your Host/Port 149 | * 150 | * @param string $host 151 | * @param int $port 152 | */ 153 | public function __construct($host = '127.0.0.1', $port = 32400, $ssl = false) 154 | { 155 | $this->host = $host; 156 | $this->port = (int) $port; 157 | $this->ssl = (bool) $ssl; 158 | } 159 | 160 | 161 | /** 162 | * Credentials for logging into Plex.tv. 163 | * Username can also be an email address. 164 | * 165 | * @param string $username 166 | * @param string $password 167 | * @return void 168 | */ 169 | public function setAuth($username, $password) 170 | { 171 | $this->username = $username; 172 | $this->password = $password; 173 | } 174 | 175 | /** 176 | * Tests the set username and password and returns the auth token 177 | * 178 | * @return string 179 | */ 180 | public function getToken() 181 | { 182 | if ($this->getBaseInfo() !== false) { 183 | return $this->token; 184 | } 185 | return false; 186 | } 187 | 188 | /** 189 | * Sets the token 190 | * 191 | * @return string 192 | */ 193 | public function setToken($token) 194 | { 195 | $this->token = $token; 196 | } 197 | 198 | /** 199 | * Get Plex Server basic info 200 | * 201 | * @return array|bool 202 | */ 203 | public function getBaseInfo() 204 | { 205 | return $this->call('/'); 206 | } 207 | 208 | 209 | /** 210 | * Get Sessions from Plex 211 | * 212 | * @return array|bool 213 | */ 214 | public function getSessions() 215 | { 216 | return $this->call('/status/sessions'); 217 | } 218 | 219 | 220 | /** 221 | * Get Transcode Sessions from Plex 222 | * 223 | * @return array|bool 224 | */ 225 | public function getTranscodeSessions() 226 | { 227 | return $this->call('/transcode/sessions'); 228 | } 229 | 230 | /** 231 | * Method to get plex account data 232 | * 233 | * @return array|bool 234 | */ 235 | public function getAccount() 236 | { 237 | return $this->call('/myplex/account'); 238 | } 239 | 240 | /** 241 | * Get On Deck Info 242 | * 243 | * @param bool $returnCollection 244 | * 245 | * @return ItemCollection|array|bool 246 | */ 247 | public function getOnDeck(bool $returnCollection = false) 248 | { 249 | $results = $this->call('/library/onDeck'); 250 | 251 | return $this->checkResults($results, $returnCollection); 252 | } 253 | 254 | 255 | /** 256 | * Get Library Sections ie Movies, TV Shows etc 257 | * 258 | * @param bool $returnObjects 259 | * 260 | * @return array|bool 261 | */ 262 | public function getLibrarySections(bool $returnObjects = false) 263 | { 264 | $res = $this->call('/library/sections'); 265 | if (!$returnObjects): return $res; 266 | endif; 267 | 268 | $ret = []; 269 | foreach ($res['Directory'] as $s) { 270 | $sec = Section::fromLibrary($s); 271 | $ret[] = $sec; 272 | } 273 | 274 | return $ret; 275 | } 276 | 277 | /** 278 | * Get Library Section contents 279 | * 280 | * @param int $sectionKey Obtained using getLibrarySections() 281 | * @param bool $returnCollection 282 | * 283 | * @return ItemCollection|array|bool 284 | */ 285 | public function getLibrarySectionContents($sectionKey, bool $returnCollection = false) 286 | { 287 | $results = $this->call('/library/sections/' . $sectionKey . '/all'); 288 | 289 | return $this->checkResults($results, $returnCollection); 290 | } 291 | 292 | 293 | /** 294 | * Refresh a Library Section. 295 | * This makes Plex search for new and removed items from the Library paths. 296 | * Doesn't return anything when successful. 297 | * 298 | * @param int $sectionKey Obtained using getLibrarySections() 299 | * @param bool $force 300 | * @return null|bool 301 | */ 302 | public function refreshLibrarySection($sectionKey, $force = false) 303 | { 304 | $options = []; 305 | if ($force) { 306 | $options['force'] = 1; 307 | } 308 | 309 | return $this->call('/library/sections/' . $sectionKey . '/refresh', $options); 310 | } 311 | 312 | 313 | /** 314 | * Refresh a specific item. 315 | * Doesn't return anything when successful. 316 | * 317 | * @param int $item 318 | * @param bool $force 319 | * @return null|bool 320 | */ 321 | public function refreshMetadata($item, $force = false) 322 | { 323 | $options = []; 324 | if ($force) { 325 | $options['force'] = 1; 326 | } 327 | 328 | return $this->call('/library/metadata/' . (int) $item . '/refresh', $options, self::PUT); 329 | } 330 | 331 | 332 | /** 333 | * Get Recently Added 334 | * 335 | * @param bool $returnCollection 336 | * 337 | * @return ItemCollection|array|bool 338 | */ 339 | public function getRecentlyAdded(bool $returnCollection = false) 340 | { 341 | $results = $this->call('/library/recentlyAdded'); 342 | 343 | return $this->checkResults($results, $returnCollection); 344 | } 345 | 346 | 347 | /** 348 | * Get Metadata for an Item 349 | * 350 | * @param int $item 351 | * @param bool $returnObject 352 | * @return array|bool|object 353 | */ 354 | public function getMetadata($item, bool $returnObject = false) 355 | { 356 | $res = $this->call('/library/metadata/' . (int) $item); 357 | if (!$returnObject): return $res; 358 | endif; 359 | 360 | $tag = (isset($res['Video']) ? 'Video' : null); 361 | $tag = (isset($res['Directory']) ? 'Directory' : $tag); 362 | 363 | $ret = $this->array2object($res[$tag]); 364 | return $ret; 365 | } 366 | 367 | 368 | /** 369 | * Method for getting the artwork for a item 370 | * 371 | * @param Item $i 372 | * @param string $tag 373 | */ 374 | public function getArtwork(Item $i, string $tag) 375 | { 376 | return $this->call($i->{$tag}, ['art' => true]); 377 | } 378 | 379 | 380 | /** 381 | * Search for Items 382 | * 383 | * @param string $query 384 | * @param bool $returnCollection 385 | * 386 | * @return ItemCollection|array|bool 387 | */ 388 | public function search($query, bool $returnCollection = false) 389 | { 390 | $results = $this->call('/search', ['query' => $query]); 391 | 392 | return $this->checkResults($results, $returnCollection); 393 | } 394 | 395 | /** 396 | * Method to filter a library 397 | * 398 | * @param int $sectionKey 399 | * @param array $filter 400 | * @param bool $returnCollection 401 | * 402 | * @return ItemCollection|array|bool 403 | */ 404 | public function filter(int $sectionKey, array $filter, bool $returnCollection = false) 405 | { 406 | $results = $this->call("/library/sections/{$sectionKey}/all", $filter); 407 | 408 | return $this->checkResults($results, $returnCollection); 409 | } 410 | 411 | /** 412 | * Analyze an item 413 | * 414 | * @param int $item 415 | * @return bool 416 | */ 417 | public function analyze($item) 418 | { 419 | return $this->call('/library/metadata/' . (int) $item . '/analyze', [], self::PUT); 420 | } 421 | 422 | /** 423 | * Get Potential Metadata Matches for an item 424 | * 425 | * @param int $item 426 | * @param string $agent 427 | * @param string $language 428 | * @return array 429 | */ 430 | public function getMatches($item, $agent = 'com.plexapp.agents.imdb', $language = 'en') 431 | { 432 | return $this->call('/library/metadata/' . (int) $item . '/matches', [ 433 | 'manual' => 1, 434 | 'agent' => $agent, 435 | 'language' => $language 436 | ], self::GET); 437 | } 438 | 439 | /** 440 | * Set the Metadata Match for an item 441 | * 442 | * @param int $item 443 | * @param string $name 444 | * @param string $guid 445 | * @return array 446 | */ 447 | public function setMatch($item, $name, $guid) 448 | { 449 | return $this->call('/library/metadata/' . (int) $item . '/match', [ 450 | 'name' => $name, 451 | 'guid' => $guid 452 | ], self::PUT); 453 | } 454 | 455 | 456 | /** 457 | * Get Servers 458 | * 459 | * @return array|bool 460 | */ 461 | public function getServers() 462 | { 463 | return $this->call('/servers'); 464 | } 465 | 466 | 467 | /** 468 | * setClientIdentifier 469 | * 470 | * @param string $identifier 471 | * @return void 472 | */ 473 | public function setClientIdentifier($identifier) 474 | { 475 | $this->clientIdentifier = $identifier; 476 | } 477 | 478 | 479 | /** 480 | * setProductName 481 | * 482 | * @param string $name 483 | * @return void 484 | */ 485 | public function setProductName($name) 486 | { 487 | $this->productName = $name; 488 | } 489 | 490 | 491 | /** 492 | * setDevice 493 | * 494 | * @param string $name 495 | * @return void 496 | */ 497 | public function setDevice($name) 498 | { 499 | $this->device = $name; 500 | } 501 | 502 | 503 | /** 504 | * setDeviceName 505 | * 506 | * @param string $name 507 | * @return void 508 | */ 509 | public function setDeviceName($name) 510 | { 511 | $this->deviceName = $name; 512 | } 513 | 514 | 515 | /** 516 | * setTimeout 517 | * 518 | * @param int $timeout 519 | * @return void 520 | */ 521 | public function setTimeout($timeout) 522 | { 523 | $this->timeout = (int) $timeout; 524 | } 525 | 526 | 527 | /** 528 | * Get last curl stats, for debugging purposes 529 | * 530 | * @return array 531 | */ 532 | public function getLastCallStats() 533 | { 534 | return $this->lastCallStats; 535 | } 536 | 537 | 538 | /** 539 | * Make an API Call or Login Call 540 | * 541 | * @param string $path 542 | * @param array $params 543 | * @param string $method 544 | * @param bool $isLoginCall 545 | * @return array|bool 546 | * @throws \Exception 547 | */ 548 | public function call($path, $params = [], $method = self::GET, $isLoginCall = false) 549 | { 550 | if (!$this->token && !$isLoginCall) { 551 | $this->call('https://plex.tv/users/sign_in.xml', [], self::POST, true); 552 | if (!$this->token) { 553 | return false; 554 | } 555 | } 556 | 557 | if ($isLoginCall) { 558 | $fullUrl = $path; 559 | } else { 560 | $fullUrl = $this->ssl ? 'https://' : 'http://'; 561 | $fullUrl .= $this->host . ':' . $this->port . $path; 562 | if ($params && count($params)) { 563 | $fullUrl .= '?' . $this->buildHttpQuery($params); 564 | } 565 | } 566 | 567 | // Setup curl array 568 | $curlOptArray = [ 569 | CURLOPT_URL => $fullUrl, 570 | CURLOPT_AUTOREFERER => true, 571 | CURLOPT_RETURNTRANSFER => true, 572 | CURLOPT_CONNECTTIMEOUT => $this->timeout, 573 | CURLOPT_TIMEOUT => $this->timeout, 574 | CURLOPT_FAILONERROR => true, 575 | CURLOPT_FOLLOWLOCATION => true, 576 | CURLOPT_HTTPHEADER => [ 577 | 'X-Plex-Client-Identifier: ' . $this->clientIdentifier, 578 | 'X-Plex-Product: ' . $this->productName, 579 | 'X-Plex-Version: ' . self::VERSION, 580 | 'X-Plex-Device: ' . $this->device, 581 | 'X-Plex-Device-Name: ' . $this->deviceName, 582 | 'X-Plex-Platform: Linux', 583 | 'X-Plex-Platform-Version: ' . self::VERSION, 584 | 'X-Plex-Provides: controller', 585 | 'X-Plex-Username: ' . $this->username, 586 | ], 587 | ]; 588 | 589 | if ($isLoginCall) { 590 | $curlOptArray[CURLOPT_HTTPAUTH] = CURLAUTH_ANY; 591 | $curlOptArray[CURLOPT_USERPWD] = $this->username . ':' . $this->password; 592 | } else { 593 | $curlOptArray[CURLOPT_HTTPHEADER][] = 'X-Plex-Token: ' . $this->token; 594 | } 595 | 596 | if ($method == self::POST) { 597 | $curlOptArray[CURLOPT_POST] = true; 598 | } elseif ($method != self::GET) { 599 | $curlOptArray[CURLOPT_CUSTOMREQUEST] = $method; 600 | } 601 | 602 | // Reset vars 603 | $this->lastCallStats = null; 604 | 605 | // Send 606 | $resource = curl_init(); 607 | curl_setopt_array($resource, $curlOptArray); 608 | 609 | // Send! 610 | $response = curl_exec($resource); 611 | 612 | // Stats 613 | $this->lastCallStats = curl_getinfo($resource); 614 | 615 | // Return if we are getting binary artwork data 616 | if (isset($params['art']) && $params['art'] && $response !== false) { 617 | curl_close($resource); 618 | return $response; 619 | } 620 | 621 | // Errors and redirect failures 622 | if (!$response) { 623 | $response = false; 624 | error_log(curl_errno($resource) . ': ' . curl_error($resource)); 625 | } else { 626 | $response = self::xml2array($response); 627 | 628 | if ($isLoginCall) { 629 | if ($this->lastCallStats['http_code'] != 201) { 630 | throw new \Exception( 631 | "Invalid status code in authentication response from Plex.tv, ". 632 | "expected 201 but got {$this->lastCallStats['http_code']}" 633 | ); 634 | } 635 | 636 | $this->token = $response['authentication-token']; 637 | } 638 | } 639 | 640 | curl_close($resource); 641 | return $response; 642 | } 643 | 644 | /** 645 | * Method to build a query string 646 | * 647 | * @param array $query 648 | * 649 | * @return string 650 | */ 651 | private function buildHttpQuery(array $query): string 652 | { 653 | $ret = ''; 654 | $first = reset($query); 655 | if (isset($first) && is_a($first, 'jc21\Util\Filter')) { 656 | foreach ($query as $q) { 657 | $ret .= (string) $q."&"; 658 | } 659 | return substr($ret, 0, -1); 660 | } 661 | 662 | return http_build_query($query); 663 | } 664 | 665 | /** 666 | * xml2array 667 | * 668 | * @param $xml 669 | * @return mixed 670 | */ 671 | protected static function xml2array($xml) 672 | { 673 | self::normalizeSimpleXML(simplexml_load_string($xml), $result); 674 | return $result; 675 | } 676 | 677 | /** 678 | * Method to convert an array to a collection 679 | * 680 | * @param array $array 681 | * 682 | * @return ItemCollection 683 | */ 684 | public static function array2collection($array) 685 | { 686 | if (is_bool($array)) { 687 | return $array; 688 | } 689 | 690 | $ic = new ItemCollection(); 691 | if (!isset($array[0])) { 692 | $array[0] = $array; 693 | } 694 | 695 | foreach ($array as $a) { 696 | if (!is_array($a) || !isset($a['type'])) { 697 | continue; 698 | } 699 | 700 | $i = self::array2object($a); 701 | 702 | if (is_null($i)) { 703 | continue; 704 | } 705 | 706 | $ic->addData($i); 707 | } 708 | return $ic; 709 | } 710 | 711 | /** 712 | * Method to convert a returned array from the API to an specific object 713 | * 714 | * @param array $arr 715 | * 716 | * @return Show|Season|Episode|Artist|Album|Track|Movie 717 | */ 718 | public static function array2object(array $arr) 719 | { 720 | if (!isset($arr['type'])) { 721 | return null; 722 | } 723 | 724 | $ns = "jc21\\"; 725 | if (in_array($arr['type'], ['show', 'season', 'episode'])) { 726 | $ns .= "TV\\"; 727 | } elseif (in_array($arr['type'], ['artist', 'album', 'track'])) { 728 | $ns .= "Music\\"; 729 | } elseif ($arr['type'] == 'movie') { 730 | $ns .= "Movies\\"; 731 | } 732 | 733 | if ($ns == "jc21\\") { 734 | return null; 735 | } 736 | 737 | $class = $ns.ucfirst($arr['type'])."::fromLibrary"; 738 | if (!method_exists($ns.ucfirst($arr['type']), "fromLibrary")) { 739 | return null; 740 | } 741 | $ret = $class($arr); 742 | return $ret; 743 | } 744 | 745 | /** 746 | * normalizeSimpleXML 747 | * 748 | * @param $obj 749 | * @param $result 750 | */ 751 | protected static function normalizeSimpleXML($obj, &$result) 752 | { 753 | $data = $obj; 754 | if (is_object($data)) { 755 | $data = get_object_vars($data); 756 | } 757 | if (is_array($data)) { 758 | foreach ($data as $key => $value) { 759 | $res = null; 760 | self::normalizeSimpleXML($value, $res); 761 | if (($key == '@attributes') && ($key)) { 762 | $result = $res; 763 | } else { 764 | $result[$key] = $res; 765 | } 766 | } 767 | } else { 768 | $result = $data; 769 | } 770 | } 771 | 772 | /** 773 | * Method to check the results for what is expected 774 | * 775 | * @param array $results 776 | * @param bool $returnCollection 777 | * 778 | * @return ItemCollection|bool|array 779 | */ 780 | private function checkResults($results, bool $returnCollection) 781 | { 782 | if (is_bool($results) || !$returnCollection): return $results; 783 | endif; 784 | 785 | $tag = (isset($results['Video']) ? 'Video' : null); 786 | $tag = (isset($results['Directory']) ? 'Directory' : $tag); 787 | 788 | if (is_null($tag)): return false; 789 | endif; 790 | 791 | return $this->array2collection($results[$tag]); 792 | } 793 | } 794 | -------------------------------------------------------------------------------- /src/jc21/Section.php: -------------------------------------------------------------------------------- 1 | data = [ 50 | 'allowSync' => false, 51 | 'art' => null, 52 | 'composite' => null, 53 | 'filters' => false, 54 | 'refreshing' => false, 55 | 'thumb' => null, 56 | 'key' => null, 57 | 'libraryType' => null, 58 | 'type' => null, 59 | 'title' => null, 60 | 'agent' => null, 61 | 'scanner' => null, 62 | 'language' => null, 63 | 'uuid' => null, 64 | 'createdAt' => null, 65 | 'updatedAt' => null, 66 | 'scannedAt' => null, 67 | 'content' => null, 68 | 'directory' => null, 69 | 'contentChangedAt' => null, 70 | 'hidden' => false, 71 | 'location' => new Location(), 72 | ]; 73 | } 74 | 75 | /** 76 | * Magic getter method 77 | * 78 | * @param string $var 79 | * 80 | * @return mixed 81 | */ 82 | public function __get(string $var): mixed 83 | { 84 | if (isset($this->data[$var])) { 85 | return $this->data[$var]; 86 | } 87 | 88 | return null; 89 | } 90 | 91 | /** 92 | * Magic setter method 93 | * 94 | * @param string $var 95 | * @param mixed $val 96 | */ 97 | public function __set(string $var, $val) 98 | { 99 | $this->data[$var] = $val; 100 | } 101 | 102 | /** 103 | * Method to create an object from library data 104 | * 105 | * @param array $lib 106 | * 107 | * @return Section 108 | */ 109 | public static function fromLibrary(array $lib): self 110 | { 111 | $me = new static(); 112 | 113 | $me->data = $lib; 114 | $me->allowSync = (bool) $lib['allowSync']; 115 | $me->hidden = (bool) $lib['hidden']; 116 | $me->filters = (bool) $lib['filters']; 117 | $me->refreshing = (bool) $lib['refreshing']; 118 | $me->location = Location::fromLibrary($lib['Location']); 119 | 120 | unset($me->data['Location']); 121 | 122 | $me->libraryType = 'public'; 123 | if ($me->agent == PlexApi::PLEX_AGENT_NONE) { 124 | $me->libraryType = 'personal'; 125 | } 126 | 127 | $createdAt = new DateTime(); 128 | $createdAt->setTimestamp($lib['createdAt']); 129 | $me->createdAt = $createdAt; 130 | 131 | $updatedAt = new DateTime(); 132 | $updatedAt->setTimestamp($lib['updatedAt']); 133 | $me->updatedAt = $updatedAt; 134 | 135 | $scannedAt = new DateTime(); 136 | $scannedAt->setTimestamp($lib['scannedAt']); 137 | $me->scannedAt = $scannedAt; 138 | 139 | return $me; 140 | } 141 | 142 | /** 143 | * Method to serialize the object into json 144 | * 145 | * @return mixed 146 | */ 147 | public function jsonSerialize(): mixed 148 | { 149 | return $this->data; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/jc21/TV/Episode.php: -------------------------------------------------------------------------------- 1 | data = [ 61 | 'ratingKey' => null, 62 | 'parentRatingKey' => null, 63 | 'grandparentRatingKey' => null, 64 | 'key' => null, 65 | 'parentKey' => null, 66 | 'grandparentKey' => null, 67 | 'guid' => null, 68 | 'parentGuid' => null, 69 | 'grandparentGuid' => null, 70 | 'type' => null, 71 | 'title' => null, 72 | 'parentTitle' => null, 73 | 'grandparentTitle' => null, 74 | 'contentRating' => null, 75 | 'summary' => null, 76 | 'index' => null, 77 | 'parentIndex' => null, 78 | 'audienceRating' => null, 79 | 'audienceRatingImage' => null, 80 | 'viewCount' => null, 81 | 'lastViewedAt' => null, 82 | 'thumb' => null, 83 | 'parentThumb' => null, 84 | 'grandparentThumb' => null, 85 | 'art' => null, 86 | 'grandparentArt' => null, 87 | 'duration' => new Duration(0), 88 | 'originallyAvailableAt' => null, 89 | 'addedAt' => null, 90 | 'updatedAt' => null, 91 | 'media' => new Media(), 92 | ]; 93 | } 94 | 95 | /** 96 | * Magic getter method 97 | * 98 | * @param string $var 99 | * 100 | * @return mixed 101 | */ 102 | public function __get(string $var) 103 | { 104 | if (isset($this->data[$var])) { 105 | return $this->data[$var]; 106 | } 107 | return null; 108 | } 109 | 110 | /** 111 | * Magic setter method 112 | * 113 | * @param string $var 114 | * @param mixed $val 115 | */ 116 | public function __set(string $var, $val) 117 | { 118 | $this->data[$var] = $val; 119 | } 120 | 121 | /** 122 | * Method to create an object from library data 123 | * 124 | * @param array $lib 125 | * 126 | * @return Episode 127 | */ 128 | public static function fromLibrary(array $lib): Episode 129 | { 130 | $me = new static(); 131 | $me->data = $lib; 132 | 133 | if (isset($lib['lastViewedAt'])) { 134 | $lastViewedAt = new DateTime(); 135 | $lastViewedAt->setTimestamp($lib['lastViewedAt']); 136 | $me->lastViewedAt = $lastViewedAt; 137 | } 138 | 139 | if (isset($lib['addedAt'])) { 140 | $addedAt = new DateTime(); 141 | $addedAt->setTimestamp($lib['addedAt']); 142 | $me->addedAt = $addedAt; 143 | } 144 | 145 | if (isset($lib['updatedAt'])) { 146 | $updatedAt = new DateTime(); 147 | $updatedAt->setTimestamp($lib['updatedAt']); 148 | $me->updatedAt = $updatedAt; 149 | } 150 | 151 | if (isset($lib['duration'])) { 152 | $me->duration = new Duration($lib['duration']); 153 | } 154 | 155 | if (isset($lib['originallyAvailableAt'])) { 156 | $me->originallyAvailableAt = new DateTime($lib['originallyAvailableAt']); 157 | } 158 | 159 | if (isset($lib['Media']) && $lib['Media'] && count($lib['Media'])) { 160 | $me->media = Media::fromLibrary($lib['Media']); 161 | } 162 | 163 | if (isset($lib['Director']) && is_array($lib['Director'])) { 164 | if (isset($lib['Director']['tag'])) { 165 | $me->data['director'][] = $lib['Director']['tag']; 166 | } else { 167 | foreach ($lib['Director'] as $d) { 168 | $me->data['director'][] = $d['tag']; 169 | } 170 | } 171 | unset($me->data['Director']); 172 | } 173 | 174 | if (isset($lib['Writer']) && is_array($lib['Writer'])) { 175 | if (isset($lib['Writer']['tag'])) { 176 | $me->data['writer'][] = $lib['Writer']['tag']; 177 | } else { 178 | foreach ($lib['Writer'] as $w) { 179 | $me->data['writer'][] = $w['tag']; 180 | } 181 | } 182 | unset($me->data['Writer']); 183 | } 184 | 185 | if (isset($lib['Role']) && is_array($lib['Role'])) { 186 | if (isset($lib['Role']['tag'])) { 187 | $me->data['role'][] = $lib['Role']['tag']; 188 | } else { 189 | foreach ($lib['Role'] as $r) { 190 | $me->data['role'][] = $r['tag']; 191 | } 192 | } 193 | unset($me->data['Role']); 194 | } 195 | 196 | unset($me->data['Media']); 197 | 198 | return $me; 199 | } 200 | 201 | /** 202 | * {@inheritDoc} 203 | */ 204 | public function jsonSerialize(): mixed 205 | { 206 | return $this->data; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/jc21/TV/Season.php: -------------------------------------------------------------------------------- 1 | data = [ 58 | 'ratingKey' => null, 59 | 'key' => null, 60 | 'parentRatingKey' => null, 61 | 'guid' => null, 62 | 'parentGuid' => null, 63 | 'parentStudio' => null, 64 | 'type' => null, 65 | 'title' => null, 66 | 'parentKey' => null, 67 | 'parentTitle' => null, 68 | 'summary' => null, 69 | 'index' => null, 70 | 'parentYear' => null, 71 | 'thumb' => null, 72 | 'art' => null, 73 | 'parentThumb' => null, 74 | 'parentTheme' => null, 75 | 'leafCount' => null, 76 | 'viewedLeafCount' => null, 77 | 'addedAt' => null, 78 | 'updatedAt' => null, 79 | ]; 80 | $this->episodes = new ItemCollection(); 81 | } 82 | 83 | /** 84 | * Magic getter method 85 | * 86 | * @param string $var 87 | * 88 | * @return mixed 89 | */ 90 | public function __get(string $var) 91 | { 92 | if (isset($this->data[$var])) { 93 | return $this->data[$var]; 94 | } 95 | return null; 96 | } 97 | 98 | /** 99 | * Magic setter method 100 | * 101 | * @param string $var 102 | * @param mixed $val 103 | */ 104 | public function __set(string $var, $val) 105 | { 106 | $this->data[$var] = $val; 107 | } 108 | 109 | /** 110 | * Method to return the episodes 111 | * 112 | * @return ItemCollection 113 | */ 114 | public function getChildren(): ItemCollection 115 | { 116 | return $this->episodes; 117 | } 118 | 119 | /** 120 | * Method to add an episode to the season 121 | * 122 | * @param Episode $e 123 | */ 124 | public function addEpisode(Episode $e) 125 | { 126 | $this->episodes->addData($e); 127 | } 128 | 129 | /** 130 | * Method to create an object from the library 131 | * 132 | * @param array $lib 133 | * 134 | * @return Season 135 | */ 136 | public static function fromLibrary(array $lib) 137 | { 138 | global $client; 139 | 140 | $me = new static(); 141 | $me->data = $lib; 142 | 143 | $addedAt = new DateTime(); 144 | $addedAt->setTimestamp($lib['addedAt']); 145 | $me->addedAt = $addedAt; 146 | 147 | $updatedAt = new DateTime(); 148 | $updatedAt->setTimestamp($lib['updatedAt']); 149 | $me->updatedAt = $updatedAt; 150 | 151 | $res = $client->call($me->key); 152 | 153 | if (isset($res['Video']) && is_array($res['Video']) && count($res['Video'])) { 154 | if (isset($res['Video'][0])) { 155 | foreach ($res['Video'] as $e) { 156 | $episode = Episode::fromLibrary($e); 157 | $me->addEpisode($episode); 158 | } 159 | } else { 160 | $episode = Episode::fromLibrary($res['Video']); 161 | $me->addEpisode($episode); 162 | } 163 | } 164 | 165 | return $me; 166 | } 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | public function jsonSerialize(): mixed 172 | { 173 | return $this->data; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/jc21/TV/Show.php: -------------------------------------------------------------------------------- 1 | data = [ 66 | 'ratingKey' => null, 67 | 'key' => null, 68 | 'guid' => null, 69 | 'studio' => null, 70 | 'type' => null, 71 | 'title' => null, 72 | 'titleSort' => null, 73 | 'episodeSort' => null, 74 | 'contentRating' => null, 75 | 'summary' => null, 76 | 'index' => null, 77 | 'audienceRating' => null, 78 | 'viewCount' => null, 79 | 'skipCount' => null, 80 | 'lastViewedAt' => null, 81 | 'addedAt' => null, 82 | 'updatedAt' => null, 83 | 'year' => null, 84 | 'thumb' => null, 85 | 'art' => null, 86 | 'duration' => new Duration(0), 87 | 'originallyAvailableAt' => null, 88 | 'leafCount' => null, 89 | 'viewedLeafCount' => null, 90 | 'childCount' => null, 91 | 'audienceRatingImage' => null, 92 | 'genre' => [], 93 | 'role' => [], 94 | ]; 95 | $this->seasons = []; 96 | } 97 | 98 | /** 99 | * Magic getter method 100 | * 101 | * @param string $var 102 | * 103 | * @return mixed 104 | */ 105 | public function __get(string $var) 106 | { 107 | if (isset($this->data[$var])) { 108 | return $this->data[$var]; 109 | } 110 | return null; 111 | } 112 | 113 | /** 114 | * Magic setter method 115 | * 116 | * @param string $var 117 | * @param mixed $val 118 | */ 119 | public function __set(string $var, $val) 120 | { 121 | $this->data[$var] = $val; 122 | } 123 | 124 | /** 125 | * Method to get the seasons 126 | * 127 | * @return array:Season 128 | */ 129 | public function getSeasons(): array 130 | { 131 | return $this->seasons; 132 | } 133 | 134 | /** 135 | * Method to add a season to the show 136 | * 137 | * @param Season $s 138 | * 139 | * @return bool 140 | */ 141 | public function addSeason(Season $s) 142 | { 143 | if (!$s->index) { 144 | return false; 145 | } 146 | 147 | $this->seasons[$s->index] = $s; 148 | return true; 149 | } 150 | 151 | /** 152 | * Method to create an object from the library 153 | * 154 | * @param array $lib 155 | * 156 | * @return Show 157 | * 158 | * @throws Exception 159 | */ 160 | public static function fromLibrary(array $lib): Show 161 | { 162 | if (!isset($GLOBALS['client'])) { 163 | throw new Exception('PlexApi client `$client` not available'); 164 | } 165 | global $client; 166 | 167 | $me = new static(); 168 | $me->data = $lib; 169 | 170 | if (isset($lib['lastViewedAt'])) { 171 | $lastViewedAt = new DateTime(); 172 | $lastViewedAt->setTimestamp($lib['lastViewedAt']); 173 | $me->lastViewedAt = $lastViewedAt; 174 | } 175 | 176 | if (isset($lib['addedAt'])) { 177 | $addedAt = new DateTime(); 178 | $addedAt->setTimestamp($lib['addedAt']); 179 | $me->addedAt = $addedAt; 180 | } 181 | 182 | if (isset($lib['updatedAt'])) { 183 | $updatedAt = new DateTime(); 184 | $updatedAt->setTimestamp($lib['updatedAt']); 185 | $me->updatedAt = $updatedAt; 186 | } 187 | 188 | if (isset($lib['duration'])) { 189 | $me->duration = new Duration($lib['duration']); 190 | } 191 | 192 | if (isset($lib['originallyAvailableAt'])) { 193 | $me->originallyAvailableAt = new DateTime($lib['originallyAvailableAt']); 194 | } 195 | 196 | if (isset($lib['Genre']) && is_array($lib['Genre'])) { 197 | if (count($lib['Genre']) == 1) { 198 | $me->data['genre'][] = $lib['Genre']['tag']; 199 | } else { 200 | foreach ($lib['Genre'] as $g) { 201 | $me->data['genre'][] = $g['tag']; 202 | } 203 | } 204 | unset($me->data['Genre']); 205 | } 206 | 207 | if (isset($lib['Director']) && is_array($lib['Director'])) { 208 | if (count($lib['Director']) == 1) { 209 | $me->data['director'][] = $lib['Director']['tag']; 210 | } else { 211 | foreach ($lib['Director'] as $d) { 212 | $me->data['director'][] = $d['tag']; 213 | } 214 | } 215 | unset($me->data['Director']); 216 | } 217 | 218 | if (isset($lib['Writer']) && is_array($lib['Writer'])) { 219 | if (count($lib['Writer']) == 1) { 220 | $me->data['writer'][] = $lib['Writer']['tag']; 221 | } else { 222 | foreach ($lib['Writer'] as $w) { 223 | $me->data['writer'][] = $w['tag']; 224 | } 225 | } 226 | unset($me->data['Writer']); 227 | } 228 | 229 | if (isset($lib['Role']) && is_array($lib['Role'])) { 230 | if (count($lib['Role']) == 1) { 231 | $me->data['role'][] = $lib['Role']['tag']; 232 | } else { 233 | foreach ($lib['Role'] as $r) { 234 | $me->data['role'][] = $r['tag']; 235 | } 236 | } 237 | unset($me->data['Role']); 238 | } 239 | 240 | $res = $client->call($me->key); 241 | 242 | if (isset($res['Directory']) && is_array($res['Directory']) && count($res['Directory'])) { 243 | if (isset($res['Directory'][0])) { 244 | foreach ($res['Directory'] as $s) { 245 | if ($s['title'] == 'All episodes') { 246 | continue; 247 | } 248 | 249 | $season = Season::fromLibrary($s); 250 | $me->addSeason($season); 251 | } 252 | } else { 253 | $season = Season::fromLibrary($res['Directory']); 254 | $me->addSeason($season); 255 | } 256 | } 257 | 258 | return $me; 259 | } 260 | 261 | /** 262 | * {@inheritDoc} 263 | */ 264 | public function jsonSerialize(): mixed 265 | { 266 | return $this->data; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/jc21/Util/Duration.php: -------------------------------------------------------------------------------- 1 | duration = $duration; 25 | } 26 | 27 | /** 28 | * The duration represented in mins 29 | * 30 | * @return string 31 | */ 32 | public function minutes(): string 33 | { 34 | $min = gmdate("i", (int)$this->duration / 1000); 35 | $hr = gmdate("H", (int)$this->duration / 1000); 36 | return (($hr * 60)+$min); 37 | } 38 | 39 | /** 40 | * The duration represented in seconds 41 | * 42 | * @return string 43 | */ 44 | public function seconds(): string 45 | { 46 | return (string)($this->duration / 1000); 47 | } 48 | 49 | /** 50 | * Method to convert the duration to seconds 51 | * 52 | * @return string 53 | */ 54 | public function __toString(): string 55 | { 56 | return gmdate("H:i:s", (int)$this->duration / 1000); 57 | } 58 | 59 | /** 60 | * Method to serialize the object 61 | * 62 | * @return mixed 63 | */ 64 | public function jsonSerialize() 65 | { 66 | return (string) $this->duration; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/jc21/Util/Filter.php: -------------------------------------------------------------------------------- 1 | =', '<='])) { 43 | throw new Exception("Invalid filter operator"); 44 | } 45 | 46 | if (!in_array($field, ['title', 'rating', 'contentRating', 'year', 'studio', 'resolution'])) { 47 | throw new Exception("Invalid filter field"); 48 | } 49 | 50 | $this->field = $field; 51 | $this->operator = $operator; 52 | $this->value = $value; 53 | } 54 | 55 | /** 56 | * Method to make the object a string 57 | * 58 | * @return string 59 | */ 60 | public function __toString() 61 | { 62 | $val = urlencode($this->value); 63 | return "{$this->field}{$this->operator}{$val}"; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/jc21/Util/Item.php: -------------------------------------------------------------------------------- 1 | data[$var])) { 32 | return $this->data[$var]; 33 | } 34 | 35 | return null; 36 | } 37 | 38 | /** 39 | * Method to create a Location from the library data 40 | * 41 | * @param array $location 42 | * 43 | * @return Location 44 | */ 45 | public static function fromLibrary(array $location): self 46 | { 47 | $me = new static(); 48 | 49 | $me->data = $location; 50 | 51 | return $me; 52 | } 53 | 54 | /** 55 | * Method to serialize the object 56 | * 57 | * @return mixed 58 | */ 59 | public function jsonSerialize() 60 | { 61 | return $this->data; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/jc21/Util/Media.php: -------------------------------------------------------------------------------- 1 | data[$var])) { 50 | return $this->data[$var]; 51 | } 52 | 53 | return null; 54 | } 55 | 56 | /** 57 | * Magic setter method 58 | * 59 | * @param string $var 60 | * @param mixed $val 61 | */ 62 | public function __set(string $var, $val) 63 | { 64 | $this->data[$var] = $val; 65 | } 66 | 67 | /** 68 | * Method to create an object from the library 69 | * 70 | * @param array $library 71 | * 72 | * @return Media 73 | */ 74 | public static function fromLibrary(array $library): Media 75 | { 76 | $me = new static(); 77 | $me->data = $library; 78 | 79 | if (isset($library['duration'])) { 80 | $me->duration = new Duration($library['duration']); 81 | } 82 | 83 | if (isset($library['Part'])) { 84 | if (isset($library['Part']['size']) && $library['Part']['size'] > 0) { 85 | $me->size = new Size($library['Part']['size']); 86 | } 87 | if (isset($library['Part']['file'])) { 88 | $me->path = $library['Part']['file']; 89 | } 90 | } 91 | 92 | unset($me->data['Part']); 93 | 94 | return $me; 95 | } 96 | 97 | /** 98 | * Method to serialize the object 99 | * 100 | * @return mixed 101 | */ 102 | public function jsonSerialize() 103 | { 104 | return $this->data; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/jc21/Util/Size.php: -------------------------------------------------------------------------------- 1 | size = $size; 25 | } 26 | 27 | /** 28 | * Returns the size in TB 29 | * 30 | * @return string 31 | */ 32 | public function TB() 33 | { 34 | return number_format($this->size / 1024 / 1024 / 1024 / 1024, 3); 35 | } 36 | 37 | /** 38 | * Return the size in GB 39 | * 40 | * @return string 41 | */ 42 | public function GB() 43 | { 44 | return number_format($this->size / 1024 / 1024 / 1024, 3); 45 | } 46 | 47 | /** 48 | * Return the size in MB 49 | * 50 | * @return string 51 | */ 52 | public function MB() 53 | { 54 | return number_format($this->size / 1024 / 1024, 3); 55 | } 56 | 57 | /** 58 | * Return the size in KB 59 | * 60 | * @return string 61 | */ 62 | public function KB() 63 | { 64 | return number_format($this->size / 1024, 3); 65 | } 66 | 67 | /** 68 | * Return the size in bytes 69 | * 70 | * @return int 71 | */ 72 | public function bytes() 73 | { 74 | return $this->size; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/ItemList.php: -------------------------------------------------------------------------------- 1 | loadEnv('tests/.env'); 10 | 11 | $client = new PlexApi($_ENV['PLEX_HOST']); 12 | $client->setToken($_ENV['PLEX_TOKEN']); 13 | 14 | print PHP_EOL."Select one of the ID's below to put in your .env file for the *_ITEM_ID value".PHP_EOL; 15 | 16 | if (isset($_ENV['MOVIE_TESTS']) && (bool) $_ENV['MOVIE_TESTS']) { 17 | print PHP_EOL."List of 10 Movies".PHP_EOL; 18 | $movieCollection = $client->getLibrarySectionContents($_ENV['MOVIE_SECTION_KEY'], true); 19 | for ($x = 0; $x < 10; $x++) { 20 | $movie = $movieCollection->getData($x); 21 | print "{$movie->ratingKey}: {$movie->title}".PHP_EOL; 22 | } 23 | print PHP_EOL; 24 | } 25 | 26 | if (isset($_ENV['TV_TESTS']) && (bool) $_ENV['TV_TESTS']) { 27 | print "List of 10 TV shows".PHP_EOL; 28 | $tvCollection = $client->getLibrarySectionContents($_ENV['TV_SECTION_KEY'], true); 29 | for ($x = 0; $x < 10; $x++) { 30 | $tv = $tvCollection->getData($x); 31 | print "{$tv->ratingKey}: {$tv->title}".PHP_EOL; 32 | } 33 | print PHP_EOL; 34 | } 35 | -------------------------------------------------------------------------------- /tests/PlexApiTest.php: -------------------------------------------------------------------------------- 1 | runMovieTests = false; 117 | $this->runTVTests = false; 118 | $this->runMusicTests = false; 119 | 120 | $dot = new Dotenv(); 121 | 122 | $envfname = __DIR__.'/.env'; 123 | if (!file_exists($envfname) || !is_readable($envfname)) { 124 | throw new \InvalidArgumentException(sprintf('%s does not exist or is not readable', $envfname)); 125 | } 126 | 127 | $dot->loadEnv($envfname); 128 | 129 | $this->api = null; 130 | if (!$this->envCheck()) { 131 | die; 132 | } 133 | 134 | $this->api = new PlexApi($this->host, $this->port, $this->ssl); 135 | if ($this->token) { 136 | $this->api->setToken($this->token); 137 | } else { 138 | $this->api->setAuth($this->user, $this->password); 139 | 140 | die("Put this token in your .env file {$envfname} as 'PLEX_TOKEN={$this->api->getToken()}' and then you can remove PLEX_USER and PLEX_PASSWORD if you like"); 141 | } 142 | 143 | $GLOBALS['client'] = $this->api; 144 | } 145 | 146 | /** 147 | * Helper method to check available environment variables 148 | * 149 | * @return bool 150 | */ 151 | private function envCheck() 152 | { 153 | $this->host = (isset($_ENV['PLEX_HOST']) ? $_ENV['PLEX_HOST'] : false); 154 | $this->token = (isset($_ENV['PLEX_TOKEN']) ? $_ENV['PLEX_TOKEN'] : ''); 155 | $this->user = (isset($_ENV['PLEX_USER']) ? $_ENV['PLEX_USER'] : false); 156 | $this->password = (isset($_ENV['PLEX_PASSWORD']) ? $_ENV['PLEX_PASSWORD'] : false); 157 | $this->port = (isset($_ENV['PLEX_PORT']) ? $_ENV['PLEX_PORT'] : 32400); 158 | $this->ssl = (isset($_ENV['PLEX_SSL']) ? (bool) $_ENV['PLEX_SSL'] : false); 159 | $ret = true; 160 | 161 | if ($this->host === false) { 162 | print("PLEX_HOST not found in .env file".PHP_EOL); 163 | } 164 | 165 | if (empty($this->token) && ($this->user === false || $this->password === false)) { 166 | print("PLEX_TOKEN not found in .env file".PHP_EOL); 167 | $ret = false; 168 | } 169 | 170 | if (isset($_ENV['MOVIE_TESTS']) && ((bool) $_ENV['MOVIE_TESTS'])) { 171 | $this->runMovieTests = true; 172 | 173 | if (!isset($_ENV['MOVIE_SECTION_KEY']) || !is_numeric($_ENV['MOVIE_SECTION_KEY'])) { 174 | print("MOVIE_SECTION_KEY not found or not INT in .env, populate with ID of library you want to test".PHP_EOL); 175 | $ret = false; 176 | } 177 | 178 | if (!isset($_ENV['MOVIE_ITEM_ID']) || !is_numeric($_ENV['MOVIE_ITEM_ID'])) { 179 | print("MOVIE_ITEM_ID not found or not INT in .env".PHP_EOL); 180 | $ret = false; 181 | } 182 | 183 | if (!isset($_ENV['MOVIE_SEARCH_QUERY'])) { 184 | print("MOVIE_SEARCH_QUERY not found in .env".PHP_EOL); 185 | $ret = false; 186 | } 187 | 188 | if (!isset($_ENV['MOVIE_FILTER_QUERY'])) { 189 | print("MOVIE_FILTER_QUERY not found in .env, MUST be a title filter".PHP_EOL); 190 | $ret = false; 191 | } 192 | } 193 | 194 | if (isset($_ENV['TV_TESTS']) && ((bool) $_ENV['TV_TESTS'])) { 195 | $this->runTVTests = true; 196 | 197 | if (!isset($_ENV['TV_SECTION_KEY']) || !is_numeric($_ENV['TV_SECTION_KEY'])) { 198 | print("TV_SECTION_KEY not found or not INT in .env, populate with ID of library you want to test".PHP_EOL); 199 | $ret = false; 200 | } 201 | 202 | if (!isset($_ENV['TV_ITEM_ID']) || !is_numeric($_ENV['TV_ITEM_ID'])) { 203 | print("TV_ITEM_ID not found or not INT in .env".PHP_EOL); 204 | $ret = false; 205 | } 206 | 207 | if (!isset($_ENV['TV_SEARCH_QUERY'])) { 208 | print("TV_SEARCH_QUERY not found in .env".PHP_EOL); 209 | $ret = false; 210 | } 211 | 212 | if (!isset($_ENV['TV_FILTER_QUERY'])) { 213 | print("TV_FILTER_QUERY not found in .env, MUST be a title filter".PHP_EOL); 214 | $ret = false; 215 | } 216 | } 217 | 218 | if (isset($_ENV['MUSIC_TESTS']) && ((bool) $_ENV['MUSIC_TESTS'])) { 219 | $this->runMusicTests = true; 220 | 221 | if (!isset($_ENV['MUSIC_SECTION_KEY']) || !is_numeric($_ENV['MUSIC_SECTION_KEY'])) { 222 | print("MUSIC_SECTION_KEY not found in .env".PHP_EOL); 223 | $ret = false; 224 | } 225 | 226 | if (!isset($_ENV['MUSIC_SEARCH_QUERY'])) { 227 | print("MUSIC_SEARCH_QUERY not found in .env".PHP_EOL); 228 | $ret = false; 229 | } 230 | 231 | if (!isset($_ENV['MUSIC_FILTER_QUERY'])) { 232 | print("MUSIC_FILTER_QUERY not found in .env MUST be a title filter".PHP_EOL); 233 | $ret = false; 234 | } 235 | } 236 | 237 | return $ret; 238 | } 239 | 240 | public function testConnection() 241 | { 242 | $this->assertTrue(is_a($this->api, "jc21\PlexApi")); 243 | } 244 | 245 | public function testGetBaseInfo() 246 | { 247 | $res = $this->api->getBaseInfo(); 248 | $this->assertIsArray($res); 249 | $this->assertArrayHasKey('size', $res); 250 | $this->assertGreaterThan(0, $res['size']); 251 | } 252 | 253 | public function testGetAccount() 254 | { 255 | $res = $this->api->getAccount(); 256 | $this->assertIsArray($res); 257 | $this->assertArrayHasKey('signInState', $res); 258 | $this->assertEquals('ok', $res['signInState']); 259 | } 260 | 261 | public function testGetSessions() 262 | { 263 | $this->assertArrayHasKey('size', $this->api->getSessions()); 264 | } 265 | 266 | public function testOnDeck() 267 | { 268 | $od = $this->api->getOnDeck(); 269 | $this->assertArrayHasKey('size', $od); 270 | } 271 | 272 | public function testOnDeckReturnCollection() 273 | { 274 | $od = $this->api->getOnDeck(true); 275 | $this->assertInstanceOf(ItemCollection::class, $od); 276 | } 277 | 278 | public function testOnDeckHasItems() 279 | { 280 | $od = $this->api->getOnDeck(true); 281 | $this->assertGreaterThan(0, $od->count()); 282 | } 283 | 284 | public function testGetRecentlyAdded() 285 | { 286 | $res = $this->api->getRecentlyAdded(); 287 | $this->assertIsArray($res); 288 | $this->assertArrayHasKey('size', $res); 289 | $this->assertGreaterThan(0, $res['size']); 290 | } 291 | 292 | public function testGetSections() 293 | { 294 | $sec = $this->api->getLibrarySections(); 295 | $this->assertArrayHasKey('size', $sec); 296 | $this->assertGreaterThan(0, $sec['size']); 297 | } 298 | 299 | public function testGetSectionsAsObject() 300 | { 301 | $secs = $this->api->getLibrarySections(true); 302 | $this->assertIsArray($secs); 303 | $this->assertIsObject($secs[0]); 304 | $this->assertInstanceOf(Section::class, $secs[0]); 305 | } 306 | 307 | public function testGetMovieLibrarySectionContents() 308 | { 309 | if (!$this->runMovieTests) { 310 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 311 | return; 312 | } 313 | 314 | $res = $this->api->getLibrarySectionContents($_ENV['MOVIE_SECTION_KEY']); 315 | $this->assertArrayHasKey('size', $res); 316 | $this->assertGreaterThan(0, $res['size']); 317 | } 318 | 319 | public function testGetMovieLibrarySectionContentsAsCollection() 320 | { 321 | if (!$this->runMovieTests) { 322 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 323 | return; 324 | } 325 | 326 | $res = $this->api->getLibrarySectionContents($_ENV['MOVIE_SECTION_KEY'], true); 327 | $this->assertInstanceOf(ItemCollection::class, $res); 328 | $this->assertGreaterThan(0, $res->count()); 329 | } 330 | 331 | public function testGetMovieCollectionSize() 332 | { 333 | if (!$this->runMovieTests) { 334 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 335 | return; 336 | } 337 | 338 | $filter = new Filter('title', $_ENV['MOVIE_FILTER_QUERY']); 339 | $res = $this->api->filter($_ENV['MOVIE_SECTION_KEY'], [$filter], true); 340 | 341 | $this->assertEquals($_ENV['MOVIE_FILTER_QUERY_SIZE'], $res->size()->bytes()); 342 | } 343 | 344 | public function testGetMetadata() 345 | { 346 | if (!$this->runMovieTests) { 347 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 348 | return; 349 | } 350 | 351 | $res = $this->api->getMetadata($_ENV['MOVIE_ITEM_ID']); 352 | $this->assertIsArray($res); 353 | $this->assertArrayHasKey('size', $res); 354 | $this->assertGreaterThan(0, $res['size']); 355 | } 356 | 357 | public function testMovieSearch() 358 | { 359 | if (!$this->runMovieTests) { 360 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 361 | return; 362 | } 363 | 364 | $res = $this->api->search($_ENV['MOVIE_SEARCH_QUERY']); 365 | $this->assertIsArray($res); 366 | $this->assertArrayHasKey('Video', $res); 367 | $this->assertGreaterThan(0, count($res['Video'])); 368 | } 369 | 370 | public function testMovieSearchReturnCollection() 371 | { 372 | if (!$this->runMovieTests) { 373 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 374 | return; 375 | } 376 | 377 | $res = $this->api->search($_ENV['MOVIE_SEARCH_QUERY'], true); 378 | $this->assertInstanceOf(ItemCollection::class, $res); 379 | $this->assertGreaterThan(0, $res->count()); 380 | } 381 | 382 | public function testMovieFilterAsFilterArray() 383 | { 384 | if (!$this->runMovieTests) { 385 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 386 | return; 387 | } 388 | 389 | $res = $this->api->filter($_ENV['MOVIE_SECTION_KEY'], ['title' => $_ENV['MOVIE_FILTER_QUERY']]); 390 | $this->assertIsArray($res); 391 | $this->assertArrayHasKey('size', $res); 392 | $this->assertGreaterThan(0, $res['size']); 393 | } 394 | 395 | public function testMovieFilterWithFilterObject() 396 | { 397 | if (!$this->runMovieTests) { 398 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 399 | return; 400 | } 401 | 402 | $filter = new Filter('title', $_ENV['MOVIE_FILTER_QUERY']); 403 | $res = $this->api->filter($_ENV['MOVIE_SECTION_KEY'], [$filter]); 404 | $this->assertIsArray($res); 405 | $this->assertArrayHasKey('size', $res); 406 | $this->assertGreaterThan(0, $res['size']); 407 | } 408 | 409 | public function testMovieFilterReturnCollection() 410 | { 411 | if (!$this->runMovieTests) { 412 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 413 | return; 414 | } 415 | 416 | $res = $this->api->filter($_ENV['MOVIE_SECTION_KEY'], ['title' => $_ENV['MOVIE_FILTER_QUERY']], true); 417 | $this->assertInstanceOf(ItemCollection::class, $res); 418 | $this->assertGreaterThan(0, $res->count()); 419 | } 420 | 421 | public function testMovieFilterWithFilterObjectReturnCollection() 422 | { 423 | if (!$this->runMovieTests) { 424 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 425 | return; 426 | } 427 | 428 | $filter = new Filter('title', $_ENV['MOVIE_FILTER_QUERY']); 429 | $res = $this->api->filter($_ENV['MOVIE_SECTION_KEY'], [$filter], true); 430 | $this->assertInstanceOf(ItemCollection::class, $res); 431 | $this->assertGreaterThan(0, $res->count()); 432 | } 433 | 434 | public function testMovieGetMatches() 435 | { 436 | if (!$this->runMovieTests) { 437 | $this->markTestSkipped(self::MOVIE_OFF_MSG); 438 | return; 439 | } 440 | 441 | $res = $this->api->getMatches($_ENV['MOVIE_ITEM_ID']); 442 | $this->assertIsArray($res); 443 | $this->assertArrayHasKey('size', $res); 444 | $this->assertGreaterThan(0, $res['size']); 445 | } 446 | 447 | public function testGetTVLibrarySectionContents() 448 | { 449 | if (!$this->runTVTests) { 450 | $this->markTestSkipped(self::TV_OFF_MSG); 451 | return; 452 | } 453 | 454 | $res = $this->api->getLibrarySectionContents($_ENV['TV_SECTION_KEY']); 455 | $this->assertIsArray($res); 456 | $this->assertArrayHasKey('size', $res); 457 | $this->assertGreaterThan(0, $res['size']); 458 | } 459 | 460 | public function testGetTVLibrarySectionContentsReturnCollection() 461 | { 462 | if (!$this->runTVTests) { 463 | $this->markTestSkipped(self::TV_OFF_MSG); 464 | return; 465 | } 466 | 467 | $res = $this->api->getLibrarySectionContents($_ENV['TV_SECTION_KEY'], true); 468 | $this->assertInstanceOf(ItemCollection::class, $res); 469 | $this->assertGreaterThan(0, $res->count()); 470 | } 471 | 472 | public function testGetTVItemMetadata() 473 | { 474 | if (!$this->runTVTests) { 475 | $this->markTestSkipped(self::TV_OFF_MSG); 476 | return; 477 | } 478 | 479 | $res = $this->api->getMetadata($_ENV['TV_ITEM_ID']); 480 | $this->assertIsArray($res); 481 | $this->assertArrayHasKey('size', $res); 482 | $this->assertGreaterThan(0, $res['size']); 483 | } 484 | 485 | public function testGetTVItemMetadataAsObject() 486 | { 487 | if (!$this->runTVTests) { 488 | $this->markTestSkipped(self::TV_OFF_MSG); 489 | return; 490 | } 491 | 492 | $res = $this->api->getMetadata($_ENV['TV_ITEM_ID'], true); 493 | $this->assertInstanceOf(Episode::class, $res); 494 | } 495 | 496 | public function testTVSearch() 497 | { 498 | if (!$this->runTVTests) { 499 | $this->markTestSkipped(self::TV_OFF_MSG); 500 | return; 501 | } 502 | 503 | $res = $this->api->search($_ENV['TV_SEARCH_QUERY']); 504 | $this->assertIsArray($res); 505 | $this->assertArrayHasKey('Video', $res); 506 | $this->assertGreaterThan(0, count($res['Video'])); 507 | } 508 | 509 | public function testTVFilterWithFilterObjectReturnCollection() 510 | { 511 | if (!$this->runTVTests) { 512 | $this->markTestSkipped(self::TV_OFF_MSG); 513 | return; 514 | } 515 | 516 | $filter = new Filter('title', $_ENV['TV_FILTER_QUERY']); 517 | $res = $this->api->filter($_ENV['TV_SECTION_KEY'], [$filter], true); 518 | $this->assertInstanceOf(ItemCollection::class, $res); 519 | $this->assertGreaterThan(0, $res->count()); 520 | } 521 | 522 | public function testGetMusic() 523 | { 524 | if (!$this->runMusicTests) { 525 | $this->markTestSkipped(self::MUSIC_OFF_MSG); 526 | return; 527 | } 528 | 529 | $res = $this->api->getLibrarySectionContents($_ENV['MUSIC_SECTION_KEY'], true); 530 | $this->assertGreaterThan(0, $res->count()); 531 | } 532 | 533 | public function testMusicSearch() 534 | { 535 | if (!$this->runMusicTests) { 536 | $this->markTestSkipped(self::MUSIC_OFF_MSG); 537 | return; 538 | } 539 | 540 | $res = $this->api->search($_ENV['MUSIC_SEARCH_QUERY']); 541 | $this->assertIsArray($res); 542 | $this->assertArrayHasKey('Directory', $res); 543 | $this->assertGreaterThan(0, count($res['Directory'])); 544 | } 545 | 546 | public function testMusicFilterAsObject() 547 | { 548 | if (!$this->runMusicTests) { 549 | $this->markTestSkipped(self::MUSIC_OFF_MSG); 550 | return; 551 | } 552 | 553 | $filter = new Filter('title', $_ENV['MUSIC_FILTER_QUERY']); 554 | $res = $this->api->filter($_ENV['MUSIC_SECTION_KEY'], [$filter], true); 555 | $this->assertGreaterThan(0, $res->count()); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /tests/SectionList.php: -------------------------------------------------------------------------------- 1 | loadEnv("tests/.env"); 10 | 11 | $client = new PlexApi($_ENV['PLEX_HOST']); 12 | $client->setToken($_ENV['PLEX_TOKEN']); 13 | $result = $client->getLibrarySections(); 14 | 15 | foreach ($result['Directory'] as $section) { 16 | // Output 17 | print "{$section['key']}: {$section['title']} (type: {$section['type']})".PHP_EOL; 18 | } 19 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | setAuth('username', 'password'); 12 | 13 | $result = $client->getLibrarySections(); 14 | 15 | foreach ($result['Directory'] as $section) { 16 | // Output 17 | print $section['key'] . ': ' . $section['title'] . ' (type: ' . $section['type'] . ')' . PHP_EOL; 18 | 19 | // Refresh 20 | $client->refreshLibrarySection($section['key']); 21 | } 22 | --------------------------------------------------------------------------------