Files
mt/mt.php

471 lines
17 KiB
PHP

<?php
$secure = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
$port = ($secure && $_SERVER['SERVER_PORT'] == 443) || $_SERVER['SERVER_PORT'] == 80 ? '' : ":" . $_SERVER['SERVER_PORT'];
$self = 'http' .
($secure ? 's' : '') .
'://' .
$_SERVER['SERVER_NAME'] .
$port .
preg_replace('/^(.*)\?.*$/', '$1', $_SERVER['REQUEST_URI']);
$flexgetCmd = 'sudo -u ben -H /usr/bin/flexget';
$messages = array();
$errors = array();
$currentSeries = array();
$currentEpisodes = array();
if (isset($_POST['action'])) {
$action = $_POST['action'];
switch ($action) {
case 'inject':
if (!isset($_POST['inject'])) {
$errors[] = 'No inject parameter specified';
break;
}
$injectTitle = escapeshellarg($_POST['inject']);
$injectUrl = escapeshellarg($_POST['injecturl']);
$runCmd = '';
# if there's an url, have it downloaded, else just learn the title
if (preg_match("#https?://.*#", $injectUrl)) {
$runCmd = "{$flexgetCmd} --inject {$injectTitle} {$injectUrl} ACCEPT";
} else {
$runCmd = "{$flexgetCmd} --inject {$injectTitle} --learn";
}
$output = '';
$returnValue = 0;
exec($runCmd, $output, $returnValue);
if ($returnValue) {
$error = urlencode('Failed to inject ' . $_POST['inject'] . "\n" . join("\n", $output));
header("Location: {$self}?error={$error}");
exit(0);
} else {
# Redirect and display a success message
$message = urlencode('Successfully injected ' . $_POST['inject']);
header("Location: {$self}?message={$message}");
exit(0);
}
break;
case 'series-forget':
if (!isset($_POST['series'])) {
$errors[] = 'No series parameter specified';
break;
}
if (!isset($_POST['episode'])) {
$errors[] = 'No episode parameter specified';
break;
} else {
if (!$_POST['episode']) {
$errors[] = 'No episode number specified';
break;
}
}
$forgetSeriesArg = escapeshellarg($_POST['series']);
$forgetEpisodeArg = escapeshellarg($_POST['episode']);
# The database may have multiple entries, so keep removing until it no longer matches anything
do {
$runCmd = "{$flexgetCmd} --series-forget $forgetSeriesArg $forgetEpisodeArg";
$output = '';
$returnValue = 0;
exec($runCmd, $output, $returnValue);
} while (preg_match("/^Removed/", $output[0]));
if ($returnValue) {
$error = urlencode('Failed to forget about ' . $_POST['series'] . ' ' . $_POST['episode'] . "\n" . join("\n", $output));
header("Location: {$self}?error={$error}");
#var_dump($_GET,$_POST);
exit(0);
} else {
$message = urlencode('Successfully forgot about ' . $_POST['series'] . ' ' . $_POST['episode']);
$details = '';
if (isset($_GET['action']) && $_GET['action'] == 'details') {
$details = "&action=details&series=" . urlencode($_POST['series']);
}
header("Location: {$self}?message={$message}{$details}");
exit(0);
}
break;
default:
$error = urlencode('Unsupported action: ' . $_POST['action']);
header("Location: {$self}?error={$error}");
exit(0);
}
} else {
if (isset($_GET['message'])) {
$messages[] = $_GET['message'];
}
if (isset($_GET['error'])) {
$errors[] = $_GET['error'];
}
if (isset($_GET['action'])) {
switch ($_GET['action']) {
case 'details':
if (!isset($_GET['series'])) {
$errors[] = "No series parameter specified";
break;
}
$series = escapeshellarg($_GET['series']);
# Grab the list of episodes for this series
$runCmd = "{$flexgetCmd} --series {$series}";
$output = '';
$returnValue = 0;
exec($runCmd, $output, $returnValue);
if (!$returnValue) {
// Ignore the first and last two lines from the output
assert('count($output) >= 4; // Expecting at least four lines of output from flexget');
$output = array_slice($output, 2, -2);
// Each new entry has one line with the identifier, then one line per version seen
/*
* S01E01
* Human Target 2010 S01E01 HDTV XviD-NoTV(www.usabit.com)... hdtv
* * Human.Target.2010.S01E01.720p.HDTV.[KvadratMalevicha.ru... 720p
* human.target.s01e01.webdl.rus.novafilm.tv.avi [15/3] unknown
*/
$matches = array();
$identifier = null;
foreach ($output as $line) {
if (preg_match('/^\s(s\d\de\d\d|\d+)( - ([0-9dhms ]+))?\s*$/i', $line, $matches)) {
# This line is an identifier
$identifier = $matches[1];
$currentEpisodes[$identifier] = array();
} else {
# This line is an entry for the current identifier
if (!preg_match('/^\s+(\*?)\s+(.{1,64}?)\s+(\S+)\s*$/', $line, $matches)) {
$errors[] = "Ignored unmatched line '$line'";
continue;
}
$currentEpisodes[$identifier][] = array(
'downloaded' => ($matches[1] == '*'),
'title' => $matches[2],
'quality' => $matches[3],
);
}
}
ksort($currentEpisodes);
}
break;
}
}
}
# Grab the up-to-date list of tv series for display
$runCmd = "{$flexgetCmd} --series";
$output = '';
$returnValue = 0;
exec($runCmd, $output, $returnValue);
if (!$returnValue) {
// Ignore the first and last two lines from the output
assert('count($output) >= 4; // Expecting at least four lines of output from flexget');
$output = array_slice($output, 2, -2);
/*
* Name Latest Status
* -------------------------------------------------------------------------------
* Castle S03E24 - 49d 7h *720p
*
*/
foreach ($output as $line) {
$matches = array();
if (!preg_match('/^\s(?P<series>.{1,30})\s+(?P<episode>.{1,20}?)( - (?P<elapsed>[0-9dhms ]+))?\s+(?P<quality>.*)\s+/', $line, $matches)) {
$errors[] = "Failed to parse '$line'";
continue;
}
$currentSeries[trim($matches['series'])] = array(
'episode' => trim($matches['episode']),
'quality' => trim($matches['quality']),
);
}
ksort($currentSeries);
} else {
$errors[] = 'Unable to get the current series/episode list. (Maybe flexget is currently running?)';
}
?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Sihnon bittorrent utilities</title>
<script lang="javascript">
function addEvent(obj, eventname, func, name) {
if (obj.attachEvent) {
//IE
obj.attachEvent('on'+eventname, func);
} else {
//Non-IE (Firefox, Netscape, W3C standard)
obj.addEventListener(eventname, func, false);
}
}
addEvent(window, 'load', function() {
addSimpleFormEvents([id('inject')]);
addSimpleFormEvents([id('forgetSeries'), id('forgetEpisode')]);
});
function addSimpleFormEvents(inputFields) {
for (var i = 0; i < inputFields.length; ++i) {
var field = inputFields[i];
addEvent(field, 'focus', function() {
if (this.value == field.defaultValue) {
this.value = "";
removeClass(this, "default");
}
});
addEvent(field, 'blur', function() {
if (this.value == "") {
this.value = this.defaultValue;
addClass(this, "default");
}
});
}
}
function id(elementId) {
return document.getElementById(elementId);
}
function addClass(element, newclass) {
//Return it immediately if it already exists
if (element.className.indexOf(newclass) > -1)
return;
//Split it into an array, separated by spaces
var classes = element.className.split(' ');
//Add the new class
classes.push(newclass);
//Then return the array joined back into a string with spaces
element.className = classes.join(' ');
}
function removeClass(element, oldclass) {
//Split the class list into an array by spaces
var classes = element.className.split(' ');
for (var cls in classes) {
//Iterate through the array of classes...
if (classes[cls] == oldclass) {
//...removing that class if it exists
classes[cls] = undefined;
}
}
//Then join the array back together and return.
//We remove double-spaces as "delete" doesn't seem to delete the element entirely
element.className = classes.join(' ').replace(' ',' ');
}
function seriesForget(series, episode) {
id("listAction").value = "series-forget";
id("listSeries").value = series;
id("listEpisode").value = episode;
id("listOperations").submit();
}
function episodeForget(series, episode) {
id("listOperations").action = "<?=$self?>?action=details";
seriesForget(series, episode);
}
</script>
<style>
body {
font-family: verdana, helvetica, sans-serif;
}
a {
color: gray;
}
label {
margin-left: 1em;
margin-right: 1em;
}
table {
border-collapse: collapse;
}
tr {
border: 1px dashed darkGray;
}
tr:hover {
background-color: lightBlue;
}
#errors {
border: 1px solid firebrick;
background: peachpuff;
color: darkred;
margin: 1em;
}
#messages {
border: 1px solid cornflowerblue;
background: lightcyan;
color: darkblue;
margin: 1em;
}
.default {
background: beige;
color: darkgray;
font-style: italic;
}
.icon {
height: 16px;
width: 16px;
}
</style>
</head>
<body>
<h1>Sihnon bittorrent utilities</h1>
<?php if ($errors) { ?>
<div id="errors">
<ul>
<?php foreach ($errors as $error) { ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if ($messages) { ?>
<div id="messages">
<ul>
<?php foreach ($messages as $message) { ?>
<li><?php echo htmlspecialchars($message); ?></li>
<?php } ?>
</ul>
</div>
<?php } ?>
<h2>Inject a seen episode title into the flexget seen list</h2>
<form action="<?=$self?>" method="post">
<fieldset>
<legend>Inject an entry into the list of seen TV episodes, and optionally download it</legend>
<p>If an URL is provided, the episode will be injected into the list of seen episodes, and also downloaded immediately. If the URL is left blank, the episode will be learned only.</p>
<input type="hidden" name="action" value="inject" />
<label for="inject">Title:</label><input type="text" id="inject" name="inject" value="some.show.s01e01.quality" class="default" />
<label for="injecturl">Url:</label><input type="text" id="injecturl" name="injecturl" value="http://" class="default" />
<input type="submit" id="injectSubmit" name="submit" value="Send" />
</fieldset>
</form>
<h2>Forget about an already seen episode</h2>
<form action="<?=$self?>" method="post">
<fieldset>
<legend>Forget about an already seen episode</legend>
<input type="hidden" name="action" value="series-forget" />
<label for="forgetSeries">Series:</label><input type="text" id="forgetSeries" name="series" value="some show" class="default" />
<label for="forgetEpisode">Episode:</label><input type="text" id="forgetEpisode" name="episode" value="s01e01" class="default" />
<input type="submit" id="forgetSubmit" name="submit" value="Send" />
</fieldset>
</form>
<?php if (isset($_GET['action']) && $_GET['action'] == 'details') { ?>
<h2>'<?=htmlspecialchars($_GET['series'])?>' Episode List</h2>
<form action="<?$self?>" method="post" id="episodeOperations">
<fieldset>
<legend>Details of each episode for '<?=htmlspecialchars($_GET['series'])?>'</legend>
<table id="episodeList">
<thead>
<tr>
<th>Episode</th>
<th>Downloaded</th>
<th>Title</th>
<th>Quality</th>
<th>Operations</th>
</tr>
</thead>
<?php
foreach ($currentEpisodes as $identifier => $episodes) {
$entryCount = count($episodes);
$rowspan = $entryCount ? " rowspan=\"{$entryCount}\"" : '';
$rowIndex = 0;
foreach ($episodes as $details) {
?>
<tr>
<?php if (!$rowIndex) { ?>
<td<?=$rowspan?>><?=$identifier?></td>
<?php } ?>
<td style="text-align: right;"><?=$details['downloaded'] ? '*' : '&nbsp;'?></td>
<td><?=htmlspecialchars($details['title'])?></td>
<td><?=htmlspecialchars($details['quality'])?></td>
<?php if (!$rowIndex) { ?>
<td<?=$rowspan?>><a title="Forget" onclick="episodeForget('<?=htmlspecialchars($_GET['series'])?>','<?=htmlspecialchars($identifier)?>')"><img class="icon" src="bin.png" /></a></td>
<?php } ?>
</tr>
<?php
++$rowIndex;
}
}
?>
</table>
</fieldset>
</form>
<?php } ?>
<h2>Currently known series/episodes</h2>
<form action="<?$self?>" method="post" id="listOperations">
<fieldset>
<legend>Details for most recent episode of each series</legend>
<input type="hidden" id="listAction" name="action" value="" />
<input type="hidden" id="listSeries" name="series" value="" />
<input type="hidden" id="listEpisode" name="episode" value="" />
<table id="seriesList">
<thead>
<tr>
<th>Series Name</th>
<th>Current episode</th>
<th>Qualities seen</th>
<th>Operations</th>
</tr>
</thead>
<?php foreach ($currentSeries as $series => $details) { ?>
<tr>
<td><a title="List episodes" href="<?=$self?>?action=details&series=<?=urlencode($series)?>"><?=htmlspecialchars($series)?></a></td>
<td><?=htmlspecialchars($details['episode'])?></td>
<td><?=htmlspecialchars($details['quality'])?></td>
<td><a title="Forget" onclick="seriesForget('<?=htmlspecialchars($series)?>','<?=htmlspecialchars($details['episode'])?>')"><img class="icon" src="bin.png" /></a></td>
</tr>
<?php } ?>
</table>
</fieldset>
</body>
</html>