458 lines
17 KiB
PHP
458 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 transmission -H /var/transmission/flexget-dev/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+)\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+(.*)\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 s02e10 *hdtv-Proper
|
|
*/
|
|
foreach ($output as $line) {
|
|
$matches = array();
|
|
if (!preg_match('/^\s(?P<series>.{1,30})\s+(?P<episode>.{1,20})\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;
|
|
}
|
|
|
|
#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'] ? '*' : ' '?></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>
|