Last active 2 weeks ago

See what's coming and if the episode was grabbed

index.html Raw
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <meta name="robots" content="noindex">
7 <title>Schedule</title>
8
9 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css">
10 <style>
11 body {
12 overflow-y: scroll;
13 }
14
15 h1 {
16 margin: 1em 0;
17 }
18
19 .nopics {
20 display: none;
21 }
22
23 h3 {
24 margin: 2em 0 0.5em;
25 }
26
27 .episode {
28 align-items: center;
29 border-left: 4px solid transparent;
30 display: flex;
31 margin-bottom: 1em;
32 padding-left: 5px;
33 }
34
35 .episode-ready {
36 border-left: 4px solid green;
37 }
38
39 .episode-wait {
40 border-left: 4px solid red;
41 }
42
43 img {
44 height: 100px;
45 width: 68px;
46 }
47
48 .details {
49 padding: 0 1em;
50 }
51 </style>
52</head>
53<body>
54 <h1>Schedule</h1>
55 <small class="nopics">(Can't see pictures? Disable your adblock)</small>
56 <div class="stuff">Loading the stuff...</div>
57 <script>
58 function buildPage (j) {
59 const container = document.createElement('div')
60 container.className = 'stuff'
61
62 let lastDate = 0
63
64 j.forEach((i) => {
65 const d = new Date(i.timestamp)
66 let waiting = d < (new Date())
67
68 if (d.getDate() !== lastDate) {
69 lastDate = d.getDate()
70 const dayTitle = document.createElement('h3')
71 dayTitle.innerHTML = d.toLocaleDateString()
72 container.appendChild(dayTitle)
73 }
74
75 const e = document.createElement('div')
76
77 const imgPoster = i.images.find((el) => el.coverType === 'poster')
78 let img
79 if (imgPoster) {
80 img = document.createElement('img')
81 img.src = imgPoster.remoteUrl
82 img.loading = 'lazy'
83 img.height = '100'
84 img.width = '68'
85 img.onerror = () => {
86 document.querySelector('.nopics').style.display = 'block'
87 }
88 }
89
90 const ed = document.createElement('div')
91 ed.className = 'details'
92 let edbody = `
93 <b>${i.show}</b><br/>
94 S${i.season.toString().padStart(2, '0')}E${i.episode.toString().padStart(2, '0')}
95 - ${i.title}<br/><br/>
96 `
97
98 if (i.ready) {
99 const qual = i.quality.split('-')
100 edbody += `Available in ${qual[qual.length - 1]} <small>(${i.mediainfo.videoCodec}/${i.mediainfo.audioCodec})</small>`
101 } else if (waiting) {
102 edbody += `Aired at ${d.toLocaleTimeString({}, { hour: '2-digit', minute: '2-digit' })}, waiting for import`
103 } else {
104 edbody += `Airs at ${d.toLocaleTimeString({}, { hour: '2-digit', minute: '2-digit' })}`
105 }
106 ed.innerHTML = edbody
107
108 if (imgPoster) e.appendChild(img)
109 e.appendChild(ed)
110
111 e.className = 'episode' + (i.ready ? ' episode-ready' : waiting ? ' episode-wait' : '')
112 container.appendChild(e)
113 })
114
115 document.querySelector('.stuff').replaceWith(container)
116 }
117
118 document.addEventListener('DOMContentLoaded', () => {
119 fetch('stuff.php')
120 .then((r) => r.json())
121 .then(buildPage)
122 })
123 </script>
124</body>
125</html>
126
stuff.php Raw
1<?php
2$domain = ''; // EDIT ME (eg. 'sonarr.example.com')
3$apikey = ''; // EDIT ME (found in Settings > General)
4
5$start = date('Y-m-d', strtotime('-1 days'));
6$end = date('Y-m-d', strtotime('+7 days'));
7
8// ---
9
10$url = sprintf(
11 'https://%s/api/v3/calendar?start=%s&end=%s&includeSeries=true&includeEpisodeFile=true&includeEpisodeImages=true',
12 $domain,
13 $start,
14 $end,
15);
16
17$curl = curl_init();
18curl_setopt_array($curl, array(
19 CURLOPT_URL => $url,
20 CURLOPT_RETURNTRANSFER => true,
21 CURLOPT_SSL_VERIFYHOST => 0,
22 CURLOPT_SSL_VERIFYPEER => 0,
23 CURLOPT_ENCODING => '',
24 CURLOPT_MAXREDIRS => 10,
25 CURLOPT_TIMEOUT => 0,
26 CURLOPT_FOLLOWLOCATION => true,
27 CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
28 CURLOPT_HTTPHEADER => array(
29 'X-Api-Key: ' . $apikey,
30 )
31));
32
33$response = curl_exec($curl);
34curl_close($curl);
35
36$data = json_decode($response);
37
38$out = [];
39
40foreach($data as $d) {
41 $out[] = array(
42 'show' => $d->series->title,
43 'images' => $d->series->images,
44 'season' => $d->seasonNumber,
45 'episode' => $d->episodeNumber,
46 'title' => $d->title,
47 'airdate' => $d->airDateUtc,
48 'timestamp' => strtotime($d->airDateUtc) * 1000,
49 'ready' => $d->hasFile,
50 // 'sceneName' => @$d->episodeFile->sceneName,
51 'quality' => @$d->episodeFile->quality->quality->name,
52 'mediainfo' => @$d->episodeFile->mediaInfo,
53 );
54}
55
56header('Content-Type: application/json');
57echo json_encode($out);
58