Sonarr.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. <?php
  2. namespace Kryptonit3\Sonarr;
  3. use GuzzleHttp\Client;
  4. class Sonarr
  5. {
  6. protected $url;
  7. protected $apiKey;
  8. protected $httpAuthUsername;
  9. protected $httpAuthPassword;
  10. public $options;
  11. public function __construct($url, $apiKey, $type = 'sonarr', $httpAuthUsername = null, $httpAuthPassword = null, $options = [])
  12. {
  13. $this->url = rtrim($url, '/\\'); // Example: http://127.0.0.1:8989 (no trailing forward-backward slashes)
  14. $this->apiKey = $apiKey;
  15. $this->type = strtolower($type);
  16. $this->httpAuthUsername = $httpAuthUsername;
  17. $this->httpAuthPassword = $httpAuthPassword;
  18. $this->options = $options;
  19. }
  20. /**
  21. * Gets upcoming episodes, if start/end are not supplied episodes airing today and tomorrow will be returned
  22. * When supplying start and/or end date you must supply date in format yyyy-mm-dd
  23. * Example: $sonarr->getCalendar('2015-01-25', '2016-01-15');
  24. * 'start' and 'end' not required. You may supply, one or both.
  25. *
  26. * @param string|null $start
  27. * @param string|null $end
  28. * @return array|object|string
  29. */
  30. public function getCalendar($start = null, $end = null, $sonarrUnmonitored = 'false')
  31. {
  32. $uriData = [];
  33. if ( $start ) {
  34. if ( $this->validateDate($start) ) {
  35. $uriData['start'] = $start;
  36. } else {
  37. return json_encode(array(
  38. 'error' => array(
  39. 'msg' => 'Start date string was not recognized as a valid DateTime. Format must be yyyy-mm-dd.',
  40. 'code' => 400,
  41. ),
  42. ));
  43. exit();
  44. }
  45. }
  46. if ( $end ) {
  47. if ( $this->validateDate($end) ) {
  48. $uriData['end'] = $end;
  49. } else {
  50. return json_encode(array(
  51. 'error' => array(
  52. 'msg' => 'End date string was not recognized as a valid DateTime. Format must be yyyy-mm-dd.',
  53. 'code' => 400,
  54. ),
  55. ));
  56. exit();
  57. }
  58. }
  59. if ( $sonarrUnmonitored == 'true' ) {
  60. $uriData['unmonitored'] = 'true';
  61. }
  62. if ( $this->type == 'lidarr' ) {
  63. $uriData['includeArtist'] = 'true';
  64. }
  65. if ( $this->type == 'sonarr' ) {
  66. $uriData['includeSeries'] = 'true';
  67. }
  68. $response = [
  69. 'uri' => 'calendar',
  70. 'type' => 'get',
  71. 'data' => $uriData
  72. ];
  73. return $this->processRequest($response);
  74. }
  75. /**
  76. * Queries the status of a previously started command, or all currently started commands.
  77. *
  78. * @param null $id Unique ID of the command
  79. * @return array|object|string
  80. */
  81. public function getCommand($id = null)
  82. {
  83. $uri = ($id) ? 'command/' . $id : 'command';
  84. $response = [
  85. 'uri' => $uri,
  86. 'type' => 'get',
  87. 'data' => []
  88. ];
  89. return $this->processRequest($response);
  90. }
  91. /**
  92. * Publish a new command for Sonarr to run.
  93. * These commands are executed asynchronously; use GET to retrieve the current status.
  94. *
  95. * Commands and their parameters can be found here:
  96. * https://github.com/Sonarr/Sonarr/wiki/Command#commands
  97. *
  98. * @param $name
  99. * @param array|null $params
  100. * @return string
  101. */
  102. public function postCommand($name, array $params = null)
  103. {
  104. $uri = 'command';
  105. $uriData = [
  106. 'name' => $name
  107. ];
  108. if (is_array($params)) {
  109. foreach($params as $key=>$value) {
  110. $uriData[$key] = $value;
  111. }
  112. }
  113. $response = [
  114. 'uri' => $uri,
  115. 'type' => 'post',
  116. 'data' => $uriData
  117. ];
  118. return $this->processRequest($response);
  119. }
  120. /**
  121. * Gets Diskspace
  122. *
  123. * @return array|object|string
  124. */
  125. public function getDiskspace()
  126. {
  127. $uri = 'diskspace';
  128. $response = [
  129. 'uri' => $uri,
  130. 'type' => 'get',
  131. 'data' => []
  132. ];
  133. return $this->processRequest($response);
  134. }
  135. /**
  136. * Returns all episodes for the given series
  137. *
  138. * @param $seriesId
  139. * @return array|object|string
  140. */
  141. public function getEpisodes($seriesId)
  142. {
  143. $uri = 'episode';
  144. $response = [
  145. 'uri' => $uri,
  146. 'type' => 'get',
  147. 'data' => [
  148. 'SeriesId' => $seriesId
  149. ]
  150. ];
  151. return $this->processRequest($response);
  152. }
  153. /**
  154. * Returns the episode with the matching id
  155. *
  156. * @param $id
  157. * @return string
  158. */
  159. public function getEpisode($id)
  160. {
  161. $uri = 'episode';
  162. $response = [
  163. 'uri' => $uri . '/' . $id,
  164. 'type' => 'get',
  165. 'data' => []
  166. ];
  167. return $this->processRequest($response);
  168. }
  169. /**
  170. * Update the given episodes, currently only monitored is changed, all other modifications are ignored.
  171. *
  172. * Required: All parameters; You should perform a getEpisode(id)
  173. * and submit the full body with the changes, as other values may be editable in the future.
  174. *
  175. * @param array $data
  176. * @return string
  177. */
  178. public function updateEpisode(array $data)
  179. {
  180. $uri = 'episode';
  181. $response = [
  182. 'uri' => $uri,
  183. 'type' => 'put',
  184. 'data' => $data
  185. ];
  186. return $this->processRequest($response);
  187. }
  188. /**
  189. * Returns all episode files for the given series
  190. *
  191. * @param $seriesId
  192. * @return array|object|string
  193. */
  194. public function getEpisodeFiles($seriesId)
  195. {
  196. $uri = 'episodefile';
  197. $response = [
  198. 'uri' => $uri,
  199. 'type' => 'get',
  200. 'data' => [
  201. 'SeriesId' => $seriesId
  202. ]
  203. ];
  204. return $this->processRequest($response);
  205. }
  206. /**
  207. * Returns the episode file with the matching id
  208. *
  209. * @param $id
  210. * @return string
  211. */
  212. public function getEpisodeFile($id)
  213. {
  214. $uri = 'episodefile';
  215. $response = [
  216. 'uri' => $uri . '/' . $id,
  217. 'type' => 'get',
  218. 'data' => []
  219. ];
  220. return $this->processRequest($response);
  221. }
  222. /**
  223. * Delete the given episode file
  224. *
  225. * @param $id
  226. * @return string
  227. */
  228. public function deleteEpisodeFile($id)
  229. {
  230. $uri = 'episodefile';
  231. $response = [
  232. 'uri' => $uri . '/' . $id,
  233. 'type' => 'delete',
  234. 'data' => []
  235. ];
  236. return $this->processRequest($response);
  237. }
  238. /**
  239. * Gets history (grabs/failures/completed).
  240. *
  241. * @param int $page Page Number
  242. * @param int $pageSize Results per Page
  243. * @param string $sortKey 'series.title' or 'date'
  244. * @param string $sortDir 'asc' or 'desc'
  245. * @return array|object|string
  246. */
  247. public function getHistory($page = 1, $pageSize = 10, $sortKey = 'series.title', $sortDir = 'asc')
  248. {
  249. $uri = 'history';
  250. $response = [
  251. 'uri' => $uri,
  252. 'type' => 'get',
  253. 'data' => [
  254. 'page' => $page,
  255. 'pageSize' => $pageSize,
  256. 'sortKey' => $sortKey,
  257. 'sortDir' => $sortDir
  258. ]
  259. ];
  260. return $this->processRequest($response);
  261. }
  262. /**
  263. * Gets missing episode (episodes without files).
  264. *
  265. * @param int $page Page Number
  266. * @param int $pageSize Results per Page
  267. * @param string $sortKey 'series.title' or 'airDateUtc'
  268. * @param string $sortDir 'asc' or 'desc'
  269. * @return array|object|string
  270. */
  271. public function getWantedMissing($page = 1, $pageSize = 10, $sortKey = 'series.title', $sortDir = 'asc')
  272. {
  273. $uri = 'wanted/missing';
  274. $response = [
  275. 'uri' => $uri,
  276. 'type' => 'get',
  277. 'data' => [
  278. 'page' => $page,
  279. 'pageSize' => $pageSize,
  280. 'sortKey' => $sortKey,
  281. 'sortDir' => $sortDir
  282. ]
  283. ];
  284. return $this->processRequest($response);
  285. }
  286. /**
  287. * Displays currently downloading info
  288. *
  289. * @return array|object|string
  290. */
  291. public function getQueue()
  292. {
  293. $uri = 'queue';
  294. $response = [
  295. 'uri' => $uri,
  296. 'type' => 'get',
  297. 'data' => [
  298. 'includeUnknownSeriesItems' => 'false',
  299. 'pageSize' => 1000
  300. ]
  301. ];
  302. if ( $this->type == 'sonarr' ) {
  303. $response['data']['includeSeries'] = 'true';
  304. $response['data']['includeEpisode'] = 'true';
  305. }
  306. return $this->processRequest($response);
  307. }
  308. /**
  309. * Gets all quality profiles
  310. *
  311. * @return array|object|string
  312. */
  313. public function getProfiles()
  314. {
  315. $uri = 'profile';
  316. $response = [
  317. 'uri' => $uri,
  318. 'type' => 'get',
  319. 'data' => []
  320. ];
  321. return $this->processRequest($response);
  322. }
  323. /**
  324. * Get release by episode id
  325. *
  326. * @param $episodeId
  327. * @return string
  328. */
  329. public function getRelease($episodeId)
  330. {
  331. $uri = 'release';
  332. $uriData = [
  333. 'episodeId' => $episodeId
  334. ];
  335. $response = [
  336. 'uri' => $uri,
  337. 'type' => 'get',
  338. 'data' => $uriData
  339. ];
  340. return $this->processRequest($response);
  341. }
  342. /**
  343. * Adds a previously searched release to the download client,
  344. * if the release is still in Sonarr's search cache (30 minute cache).
  345. * If the release is not found in the cache Sonarr will return a 404.
  346. *
  347. * @param $guid
  348. * @return string
  349. */
  350. public function postRelease($guid)
  351. {
  352. $uri = 'release';
  353. $uriData = [
  354. 'guid' => $guid
  355. ];
  356. $response = [
  357. 'uri' => $uri,
  358. 'type' => 'post',
  359. 'data' => $uriData
  360. ];
  361. return $this->processRequest($response);
  362. }
  363. /**
  364. * Push a release to download client
  365. *
  366. * @param $title
  367. * @param $downloadUrl
  368. * @param $downloadProtocol (Usenet or Torrent)
  369. * @param $publishDate (ISO8601 Date)
  370. * @return string
  371. */
  372. public function postReleasePush($title, $downloadUrl, $downloadProtocol, $publishDate)
  373. {
  374. $uri = 'release';
  375. $uriData = [
  376. 'title' => $title,
  377. 'downloadUrl' => $downloadUrl,
  378. 'downloadProtocol' => $downloadProtocol,
  379. 'publishDate' => $publishDate
  380. ];
  381. $response = [
  382. 'uri' => $uri,
  383. 'type' => 'post',
  384. 'data' => $uriData
  385. ];
  386. return $this->processRequest($response);
  387. }
  388. /**
  389. * Gets root folder
  390. *
  391. * @return array|object|string
  392. */
  393. public function getRootFolder()
  394. {
  395. $uri = 'rootfolder';
  396. $response = [
  397. 'uri' => $uri,
  398. 'type' => 'get',
  399. 'data' => []
  400. ];
  401. return $this->processRequest($response);
  402. }
  403. /**
  404. * Returns all series in your collection
  405. *
  406. * @return array|object|string
  407. */
  408. public function getSeries()
  409. {
  410. $uri = 'series';
  411. $response = [
  412. 'uri' => $uri,
  413. 'type' => 'get',
  414. 'data' => []
  415. ];
  416. return $this->processRequest($response);
  417. }
  418. /**
  419. * Adds a new series to your collection
  420. *
  421. * NOTE: if you do not add the required params, then the series wont function.
  422. * Some of these without the others can indeed make a "series". But it wont function properly in Sonarr.
  423. *
  424. * Required: tvdbId (int) title (string) qualityProfileId (int) titleSlug (string) seasons (array)
  425. * See GET output for format
  426. *
  427. * path (string) - full path to the series on disk or rootFolderPath (string)
  428. * Full path will be created by combining the rootFolderPath with the series title
  429. *
  430. * Optional: tvRageId (int) seasonFolder (bool) monitored (bool)
  431. *
  432. * @param array $data
  433. * @param bool|true $onlyFutureEpisodes It can be used to control which episodes Sonarr monitors
  434. * after adding the series, setting to true (default) will only monitor future episodes.
  435. *
  436. * @return array|object|string
  437. */
  438. public function postSeries(array $data, $onlyFutureEpisodes = true)
  439. {
  440. $uri = 'series';
  441. $uriData = [];
  442. // Required
  443. $uriData['tvdbId'] = $data['tvdbId'];
  444. $uriData['title'] = $data['title'];
  445. $uriData['qualityProfileId'] = $data['qualityProfileId'];
  446. if ( array_key_exists('titleSlug', $data) ) { $uriData['titleSlug'] = $data['titleSlug']; }
  447. if ( array_key_exists('seasons', $data) ) { $uriData['seasons'] = $data['seasons']; }
  448. if ( array_key_exists('path', $data) ) { $uriData['path'] = $data['path']; }
  449. if ( array_key_exists('rootFolderPath', $data) ) { $uriData['rootFolderPath'] = $data['rootFolderPath']; }
  450. if ( array_key_exists('tvRageId', $data) ) { $uriData['tvRageId'] = $data['tvRageId']; }
  451. $uriData['seasonFolder'] = ( array_key_exists('seasonFolder', $data) ) ? $data['seasonFolder'] : true;
  452. if ( array_key_exists('monitored', $data) ) { $uriData['monitored'] = $data['monitored']; }
  453. if ( $onlyFutureEpisodes ) {
  454. $uriData['addOptions'] = [
  455. 'ignoreEpisodesWithFiles' => true,
  456. 'ignoreEpisodesWithoutFiles' => true
  457. ];
  458. }
  459. $response = [
  460. 'uri' => $uri,
  461. 'type' => 'post',
  462. 'data' => $uriData
  463. ];
  464. return $this->processRequest($response);
  465. }
  466. /**
  467. * Delete the series with the given ID
  468. *
  469. * @param int $id
  470. * @param bool|true $deleteFiles
  471. * @return string
  472. */
  473. public function deleteSeries($id, $deleteFiles = true)
  474. {
  475. $uri = 'series';
  476. $uriData = [];
  477. $uriData['deleteFiles'] = ($deleteFiles) ? 'true' : 'false';
  478. $response = [
  479. 'uri' => $uri . '/' . $id,
  480. 'type' => 'delete',
  481. 'data' => $uriData
  482. ];
  483. return $this->processRequest($response);
  484. }
  485. /**
  486. * Searches for new shows on trakt
  487. * Search by name or tvdbid
  488. * Example: 'The Blacklist' or 'tvdb:266189'
  489. *
  490. * @param string $searchTerm query string for the search (Use tvdb:12345 to lookup TVDB ID 12345)
  491. * @return string
  492. */
  493. public function getSeriesLookup($searchTerm)
  494. {
  495. $uri = 'series/lookup';
  496. $uriData = [
  497. 'term' => $searchTerm
  498. ];
  499. $response = [
  500. 'uri' => $uri,
  501. 'type' => 'get',
  502. 'data' => $uriData
  503. ];
  504. return $this->processRequest($response);
  505. }
  506. /**
  507. * Get System Status
  508. *
  509. * @return string
  510. */
  511. public function getSystemStatus()
  512. {
  513. $uri = 'system/status';
  514. $response = [
  515. 'uri' => $uri,
  516. 'type' => 'get',
  517. 'data' => []
  518. ];
  519. return $this->processRequest($response);
  520. }
  521. /**
  522. * Process requests with Guzzle
  523. *
  524. * @param array $params
  525. * @return \Psr\Http\Message\ResponseInterface
  526. */
  527. protected function _request(array $params)
  528. {
  529. $client = new Client($this->options);
  530. $options = [
  531. 'headers' => [
  532. 'X-Api-Key' => $this->apiKey
  533. ]
  534. ];
  535. if ( $this->httpAuthUsername && $this->httpAuthPassword ) {
  536. $options['auth'] = [
  537. $this->httpAuthUsername,
  538. $this->httpAuthPassword
  539. ];
  540. }
  541. if($this->type == 'lidarr'){
  542. $params['version'] = 'v1/';
  543. }
  544. $version = $params['version'] ?? '';
  545. if ( $params['type'] == 'get' ) {
  546. $url = $this->url . '/api/' . $version . $params['uri'] . '?' . http_build_query($params['data']);
  547. return $client->get($url, $options);
  548. }
  549. if ( $params['type'] == 'put' ) {
  550. $url = $this->url . '/api/' . $version . $params['uri'];
  551. $options['json'] = $params['data'];
  552. return $client->put($url, $options);
  553. }
  554. if ( $params['type'] == 'post' ) {
  555. $url = $this->url . '/api/' . $version . $params['uri'];
  556. $options['json'] = $params['data'];
  557. return $client->post($url, $options);
  558. }
  559. if ( $params['type'] == 'delete' ) {
  560. $url = $this->url . '/api/' . $version . $params['uri'] . '?' . http_build_query($params['data']);
  561. return $client->delete($url, $options);
  562. }
  563. }
  564. /**
  565. * Process requests, catch exceptions, return json response
  566. *
  567. * @param array $request uri, type, data from method
  568. * @return string json encoded response
  569. */
  570. protected function processRequest(array $request)
  571. {
  572. try {
  573. switch ($this->type){
  574. case 'sonarr':
  575. $versionCheck = 'v3/';
  576. break;
  577. case 'radarr':
  578. $versionCheck = 'v3/';
  579. break;
  580. case 'lidarr':
  581. $versionCheck = 'v1/';
  582. break;
  583. default:
  584. $versionCheck = '';
  585. }
  586. } catch ( \Exception $e ) {
  587. return json_encode(array(
  588. 'error' => array(
  589. 'msg' => $e->getMessage(),
  590. 'code' => $e->getCode(),
  591. ),
  592. ));
  593. exit();
  594. }
  595. try {
  596. $response = $this->_request(
  597. [
  598. 'uri' => $request['uri'],
  599. 'type' => $request['type'],
  600. 'data' => $request['data'],
  601. 'version' => $versionCheck
  602. ]
  603. );
  604. } catch ( \Exception $e ) {
  605. return json_encode(array(
  606. 'error' => array(
  607. 'msg' => $e->getMessage(),
  608. 'code' => $e->getCode(),
  609. ),
  610. ));
  611. exit();
  612. }
  613. return $response->getBody()->getContents();
  614. }
  615. /**
  616. * Verify date is in proper format
  617. *
  618. * @param $date
  619. * @param string $format
  620. * @return bool
  621. */
  622. private function validateDate($date, $format = 'Y-m-d')
  623. {
  624. $d = \DateTime::createFromFormat($format, $date);
  625. return $d && $d->format($format) == $date;
  626. }
  627. }