1396 lines
63 KiB
PHP
Executable File
1396 lines
63 KiB
PHP
Executable File
<?php
|
|
/*
|
|
* Wish List
|
|
* Revamp argument stack. Currently does not maintain things like searches or filters when adding other compatible arguments like show_seen
|
|
* Streamline 403 workflow
|
|
* Clean Ancient Database entries mechanism to remove seen items that have been pushed out of video list results
|
|
* IPv6 support
|
|
* Import subscription list from YouTube and/or NewPipe
|
|
* Paging/continuous scrolling. Or at least a configurable video count.
|
|
* Alternative ORDER options (probably put in Search menu)
|
|
*/
|
|
|
|
$debug = 0;
|
|
$root = realpath($_SERVER['DOCUMENT_ROOT']);
|
|
$database = "../db/youtube.sqlite";
|
|
$debug_text = '';
|
|
$error_text = '';
|
|
|
|
# Detect mobile
|
|
$useragent = $_SERVER['HTTP_USER_AGENT'];
|
|
$mobile = 0;
|
|
if ( preg_match('/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i',$useragent)||preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr($useragent,0,4) ) ) {
|
|
$mobile = 1;
|
|
}
|
|
|
|
# Push message to either debug or error depending on mode
|
|
function error($msg)
|
|
{
|
|
global $debug;
|
|
if ($debug) {
|
|
global $debug_text;
|
|
$debug_text .= $msg;
|
|
} else {
|
|
global $error_text;
|
|
$error_text .= $msg;
|
|
}
|
|
}
|
|
|
|
# Initialize database
|
|
function initialize()
|
|
{
|
|
global $root;
|
|
return shell_exec(escapeshellcmd("$root/../bin/initialize.pl"));
|
|
}
|
|
|
|
# Delete database
|
|
function delete_db()
|
|
{
|
|
global $root;
|
|
return shell_exec(escapeshellcmd("rm $root/../db/youtube.sqlite"));
|
|
}
|
|
|
|
function update_cron($interval) {
|
|
global $root;
|
|
if ($interval == 1) {
|
|
$interval = '*';
|
|
} else {
|
|
$interval = "*/".escapeshellcmd($interval);
|
|
}
|
|
$crontab = shell_exec("/usr/bin/crontab -l");
|
|
if (preg_match("/".preg_replace('#/#g','\/',$root)."\/\.\.\/bin\/refresh_youtube\.pl/",$crontab)) {
|
|
$crontab = preg_replace("#\*(/\d+)? \* \* \* \* $root/../bin/refresh_youtube.pl &2> /dev/null#m",
|
|
"$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null", $crontab);
|
|
} else {
|
|
$crontab .= "$interval * * * * $root/../bin/refresh_youtube.pl &2> /dev/null\n";
|
|
}
|
|
file_put_contents("$root/.crontab.tmp", $crontab);
|
|
error(shell_exec(escapeshellcmd("/usr/bin/crontab $root/.crontab.tmp")));
|
|
error(shell_exec(escapeshellcmd("/bin/rm $root/.crontab.tmp")));
|
|
}
|
|
|
|
# Refresh feeds
|
|
function refresh()
|
|
{
|
|
global $root;
|
|
return shell_exec(escapeshellcmd("$root/../bin/refresh_youtube.pl"));
|
|
}
|
|
|
|
# Fix quotes for SQL input
|
|
# Convert ip4 to a long int for comparison in permissions
|
|
function ip4_2_long($ip)
|
|
{
|
|
$ips = preg_split('/\./',$ip);
|
|
return($ips[3] | $ips[2] << 8 | $ips[1] << 16 | $ips[0] << 24);
|
|
}
|
|
|
|
# Convert ip6 to a long int for comparison in permissions
|
|
function ip6_2_long($ip)
|
|
{
|
|
if (!function_exists('gmp_init') && !function_exists('bcadd')) {
|
|
error('<h1>403: Permission Denied</h1>
|
|
<p>Requires either GMP or BCMATH extension</p>');
|
|
}
|
|
$ip_n = inet_pton($ip);
|
|
$bin = '';
|
|
for ($bit = strlen($ip_n) - 1; $bit >= 0; $bit--) {
|
|
$bin = sprintf('%08b', ord($ip_n[$bit])) . $bin;
|
|
}
|
|
|
|
if (function_exists('gmp_init')) {
|
|
return gmp_strval(gmp_init($bin, 2), 10);
|
|
} elseif (function_exists('bcadd')) {
|
|
$dec = '0';
|
|
for ($i = 0; $i < strlen($bin); $i++) {
|
|
$dec = bcmul($dec, '2', 0);
|
|
$dec = bcadd($dec, $bin[$i], 0);
|
|
}
|
|
return $dec;
|
|
} else {
|
|
exit();
|
|
trigger_error('GMP or BCMATH extension not installed!', E_USER_ERROR);
|
|
}
|
|
}
|
|
|
|
# Check if given IP is in a ip4 CIDR range
|
|
function in_range4($allowed,$cidr,$ip)
|
|
{
|
|
if ( ( ( ip4_2_long($ip) >= ip4_2_long($allowed) ) && ( ip4_2_long($ip) <= (ip4_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# Check if write is outside of read range
|
|
function write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) {
|
|
$readtop = ip4_2_long($read4ip)+2**(32-$read4cidr);
|
|
$writetop = ip4_2_long($write4ip)+2**(32-$write4cidr);
|
|
if (!in_range4($read4ip,$read4cidr,$write4ip) || ($writetop > $readtop)) {
|
|
error("<p>write range ($write4ip/$write4cidr) must be contained within read range ($read4ip/$read4cidr)</p>");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# Check if given IP is in a ip6 CIDR range
|
|
function in_range6($allowed,$cidr,$ip)
|
|
{
|
|
if ( ( ( ip6_2_long($ip) >= ip6_2_long($allowed) ) && ( ip6_2_long($ip) <= (ip6_2_long($allowed)+2**(32-$cidr)) ) ) || ($ip == $_SERVER['SERVER_ADDR'])) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# Strip argument key and value from current target URL and return valid updated URL
|
|
function remove_arg($target,$arg)
|
|
{
|
|
global $debug_text;
|
|
if (preg_match("/\?$arg/",$target)) {
|
|
# First argument, leave ? and remove trailing & if it exists
|
|
$target = preg_replace("/$arg=[^&]*&?/",'',$target);
|
|
if (preg_match("/\?$/",$target)) {
|
|
$target = rtrim($target,"?");
|
|
}
|
|
} else {
|
|
# Secondary argument, remove leading &
|
|
$target = preg_replace("/&$arg=[^&]*/",'',$target);
|
|
}
|
|
$debug_text .= "<p>Just removed $arg</p><p>Target is now: $target</p>";
|
|
return $target;
|
|
}
|
|
|
|
# Return whether the client IP is ip4 or ip6
|
|
function ip_version($ip)
|
|
{
|
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
|
return 4;
|
|
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
|
return 6;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
# Return read and write permissions given client IP and preferences
|
|
function get_permissions($settings)
|
|
{
|
|
$permissions = array();
|
|
if ($settings['ipv'] == 4) {
|
|
$permissions['rip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']);
|
|
$permissions['wip'] = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['write4']);
|
|
$permissions['rcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']);
|
|
$permissions['wcidr'] = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['write4']);
|
|
if ($settings['enable4']) {
|
|
$permissions['readable'] = in_range4($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']);
|
|
$permissions['writable'] = in_range4($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']);
|
|
} else {
|
|
$permissions['readable'] = 0;
|
|
$permissions['writable'] = 0;
|
|
}
|
|
} else {
|
|
$permissions['rip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['read6']);
|
|
$permissions['wip'] = preg_replace("/([\d:]+)\/\d+/","$1",$settings['write6']);
|
|
$permissions['rcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['read6']);
|
|
$permissions['wcidr'] = preg_replace("/[\d:]+\/(\d+)/","$1",$settings['write6']);
|
|
if ($settings['enable6']) {
|
|
$permissions['readable'] = 1;
|
|
//$permissions['readable'] = in_range6($permissions['rip'],$permissions['rcidr'],$_SERVER['REMOTE_ADDR']);
|
|
$permissions['writable'] = 0;
|
|
//$permissions['writable'] = in_range6($permissions['wip'],$permissions['wcidr'],$_SERVER['REMOTE_ADDR']);
|
|
} else {
|
|
$permissions['readable'] = 0;
|
|
$permissions['writable'] = 0;
|
|
}
|
|
}
|
|
return $permissions;
|
|
}
|
|
|
|
# Print one row given the video details
|
|
function print_video($video,$settings,$mobile,$show_seen)
|
|
{
|
|
$html = '
|
|
<div class="row">
|
|
<div value="' . $video['videoId'] . '" id="' . $video['videoId'] . '" class="video_body">';
|
|
if ($settings['player'] == 'embed') {
|
|
if (isset($settings['embed_type']) && $settings['embed_type'] == 'nocookie') {
|
|
$default .= '
|
|
<a href="https://www.youtube-nocookie.com/embed/' . $video['videoId'] . '" target="_blank">';
|
|
} elseif (isset($settings['embed_type']) && $settings['embed_type'] == 'proxy') {
|
|
$default .= '
|
|
<a href="https://www.duckduckgo.com/video_frame?url=https%3A%2F%2Fwww.youtube-nocookie.com%2Fembed%2F' . $video['videoId'] . '" target="_blank">';
|
|
} else {
|
|
$default .= '
|
|
<a href="https://www.youtube.com/embed/' . $video['videoId'] . '" target="_blank">';
|
|
}
|
|
} elseif ($settings['player'] == 'clip') {
|
|
$default .= '
|
|
<a onclick="clipboard(\'' . $video['videoId'] . '\',\'https://youtube.com/watch?v=' . $video['videoId'] . '\');">';
|
|
} else {
|
|
$default .= '
|
|
<a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/watch?v=' . $video['videoId'] . '" target="_blank">';
|
|
}
|
|
$html .= "
|
|
$default" . '
|
|
<div class="thumb' . ($settings['thumbnail_quality'] == 'maxresdefault' ? '' : ' letterbox') . '" id="thumbnail:' . $video['videoId'] . '">
|
|
<img src="https://i.ytimg.com/vi/' . $video['videoId'] . '/' . ($settings['thumbnail_quality'] ? $settings['thumbnail_quality'] : 'default') . '.jpg"/>
|
|
<div class="time">
|
|
<h4>' . $video['lengthText'] . '</h4>
|
|
</div>
|
|
</div>';
|
|
if ($mobile) {
|
|
$html .= '
|
|
</a>
|
|
<div class="details">
|
|
<div class="playmodes">';
|
|
if ($settings['embed_type'] == 'proxy') {
|
|
$html .= '
|
|
<a href="https://www.duckduckgo.com/video_frame?url=https%3A%2F%2Fwww.youtube-nocookie.com%2Fembed%2F' . $video['videoId'] . '" target="_blank"><div class="embed"><img src="images/proxy.svg"></div></a>';
|
|
} elseif ($settings['embed_type'] == 'nocookie') {
|
|
$html .= '
|
|
<a href="https://www.youtube-nocookie.com/embed/' . $video['videoId'] . '" target="_blank"><div class="embed"><img src="images/nocookie.svg"></div></a>';
|
|
} else {
|
|
$html .= '
|
|
<a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/embed/' . $video['videoId'] . '" target="_blank"><div class="embed"><img src="images/embed.svg"></div></a>';
|
|
}
|
|
$html .= '
|
|
<a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/watch?v=' . $video['videoId'] . '" target="_blank"><div class="web"><img src="images/web.svg"></div></a>
|
|
<a onclick="clipboard(\'' . $video['videoId'] . '\',\'https://youtube.com/watch?v=' . $video['videoId'] . '\');"><div class="clip"><img src="images/clip.svg"></div></a>';
|
|
if (isset($show_seen) && $show_seen && isset($video['seen']) && $video['seen']) {
|
|
$html .= '
|
|
<a href="?unrm=' . $video['videoId'] . '">
|
|
<div class="seen">
|
|
<span class="unmark">
|
|
<h3>*</h3>
|
|
</span>
|
|
</div>';
|
|
} else {
|
|
$html .= '
|
|
<a href="?rm=' . $video['videoId'] . '">
|
|
<div class="seen">
|
|
<span class="mark">
|
|
<h3><b>X</b></h3>
|
|
</span>
|
|
</div>';
|
|
}
|
|
$html .= '
|
|
</a>
|
|
</div>
|
|
' . $default .'
|
|
<div class="title">
|
|
<img src="' . $video['channelThumbnail'] . '"/>
|
|
<h2>' . $video['title'] . '</h2>
|
|
<h4 class="views">' . $video['shortViewCountText'] . '</h4>
|
|
<h4 class="upload">' . $video['publishedTimeText'] . '</h4>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>';
|
|
} else {
|
|
$html .= '
|
|
<div class="details">
|
|
<img src="' . $video['channelThumbnail'] . '"/>
|
|
<h2>' . $video['title'] . '</h2>
|
|
<h4 class="views">' . $video['shortViewCountText'] . '</h4>
|
|
<h4 class="upload">' . $video['publishedTimeText'] . '</h4>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
<div class="playmodes">';
|
|
if ($settings['embed_type'] == 'proxy') {
|
|
$html .= '
|
|
<div class="embed"><a href="https://www.duckduckgo.com/video_frame?url=https%3A%2F%2Fwww.youtube-nocookie.com%2Fembed%2F' . $video['videoId'] . '" target="_blank"><img src="images/proxy.svg"></a></div>';
|
|
} elseif ($settings['embed_type'] == 'nocookie') {
|
|
$html .= '
|
|
<div class="embed"><a href="https://www.youtube-nocookie.com/embed/' . $video['videoId'] . '" target="_blank"><img src="images/nocookie.svg"></a></div>';
|
|
} else {
|
|
$html .= '
|
|
<div class="embed"><a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/embed/' . $video['videoId'] . '" target="_blank"><img src="images/embed.svg"></a></div>';
|
|
}
|
|
$html .= '
|
|
<div class="web"><a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/watch?v=' . $video['videoId'] . '" target="_blank"><img src="images/web.svg"></a></div>
|
|
<div class="clip"><a onclick="clipboard(\'' . $video['videoId'] . '\',\'https://youtube.com/watch?v=' . $video['videoId'] . '\');"><img src="images/clip.svg"></a></div>
|
|
</div>';
|
|
if (isset($show_seen) && $show_seen && isset($video['seen']) && $video['seen']) {
|
|
$html .= '
|
|
<a href="?unrm=' . $video['videoId'] . '">
|
|
<div class="seen"><span class="unmark"><h3>*</h3></span></div></a>';
|
|
} else {
|
|
$html .= '
|
|
<a href="?rm=' . $video['videoId'] . '">
|
|
<div class="seen"><span class="mark"><h3><b>X</b></h3></span></div></a>';
|
|
}
|
|
$html .= '
|
|
</div>';
|
|
}
|
|
return $html;
|
|
}
|
|
|
|
# Add a new subscription.
|
|
function add_channel($chan)
|
|
{
|
|
global $root;
|
|
return shell_exec(escapeshellcmd("$root/../bin/add_youtube_subscription.pl $chan"));
|
|
}
|
|
|
|
# Fix quotes for SQL input
|
|
function sql_escape($text)
|
|
{
|
|
return preg_replace("/'/","''",$text);
|
|
}
|
|
|
|
# Open database and fetch all settings
|
|
$no_db = 1;
|
|
if (!file_exists($database)) {
|
|
$permissions = array("readable"=>"1","writable"=>"1");
|
|
} else {
|
|
$no_db = 0;
|
|
$handle = new SQLite3($database);
|
|
$settings_row = $handle->query('SELECT * FROM settings');
|
|
$settings = $settings_row->fetchArray();
|
|
}
|
|
|
|
# If default player is 'embed' we need to select the right 'embed_type'
|
|
if (isset($settings['player']) && $settings['player'] == 'embed' && isset($settings['embed_type'])) {
|
|
$settings['player'] == $settings['embed_type'];
|
|
}
|
|
|
|
# Fetch client IP version, then check permissions for that IP
|
|
if (!isset($permissions)) {
|
|
$settings['ipv'] = ip_version($_SERVER['REMOTE_ADDR']);
|
|
if (isset($settings['ipv'])) {
|
|
$permissions = get_permissions($settings);
|
|
# Reject connection if PHP does not support ip6
|
|
} elseif (!defined('AF_INET6')) {
|
|
error('<h1>403: Permission Denied</h1>
|
|
<p>PHP does not support IPv6 and your address is: ' . $_SERVER['REMOTE_ADDR'] . '</p>');
|
|
# Reject connection if not a valid ip4 or ip6 address (what else is it?)
|
|
} else {
|
|
error('<h1>403: Permission Denied</h1>
|
|
<p>Invalid remote IP address: ' . $_SERVER['REMOTE_ADDR'] . '</p>');
|
|
}
|
|
}
|
|
|
|
# Reject connection if not readable
|
|
if (!$permissions['readable']) {
|
|
error('<h1>403: Permission Denied</h1>
|
|
<p>You do not have readable access to this page</p>');
|
|
if ($settings['ipv'] == 4) {
|
|
if ($settings['enable4']) {
|
|
error('<p>Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read4'] . ')</p>');
|
|
} else {
|
|
error('<p>IPv4 Interface is disabled</p>');
|
|
}
|
|
} else {
|
|
if ($settings['enable6']) {
|
|
error('<p>Your IP (' . $_SERVER['REMOTE_ADDR'] . ') falls outside of readable range (' . $settings['read6'] . ')</p>');
|
|
} else {
|
|
error('<p>IPv6 Interface is disabled</p>');
|
|
}
|
|
}
|
|
}
|
|
|
|
# Collect URL arguments
|
|
$target = $_SERVER['REQUEST_URI'];
|
|
$args = array();
|
|
parse_str($_SERVER['QUERY_STRING'], $args);
|
|
|
|
# Some arguments will require the page to be reloaded after processing.
|
|
# They will set this flag which will be checked after all arguements are done.
|
|
$reload = 0;
|
|
|
|
## STARTREAD: Read options included prior to the ENDREAD line
|
|
|
|
if (isset($args['mobile'])) {
|
|
if ($args['mobile'] != '' && $args['mobile'] != '0') {
|
|
$mobile = 1;
|
|
} else {
|
|
$mobile = 0;
|
|
}
|
|
$target = remove_arg($target,'mobile');
|
|
unset($args['mobile']);
|
|
}
|
|
|
|
# Retrieve passed error message
|
|
if (isset($args['msg'])) {
|
|
if ($args['msg'] != '') {
|
|
error(urldecode($args['msg']));
|
|
}
|
|
$target = remove_arg($target,'msg');
|
|
unset($args['msg']);
|
|
}
|
|
|
|
# Add parameters to SQL query to restrict by search results
|
|
if (isset($args['search'])) {
|
|
if (isset($args['search_clear']) || $args['search'] == '') {
|
|
unset($args['search_clear']);
|
|
$target = remove_arg($target,'search_clear');
|
|
$target = remove_arg($target,'search');
|
|
} else {
|
|
$search = urldecode($args['search']);
|
|
# If search is quoted with '/', treat it as a regex
|
|
if (preg_match('#^/.*/$#',$search)) {
|
|
$sql_search = preg_replace('#^/(.*)/$#', '$1', $search);
|
|
$regex_search = $handle->loadExtension('pcre.so');
|
|
if ($regex_search) {
|
|
$sql_search = " videos.title REGEXP '" . $sql_search . "'";
|
|
} else {
|
|
# emulate regex if module is unavailable
|
|
$new_search = '';
|
|
if (preg_match('/^\^/', $sql_search)) {
|
|
$start = preg_replace('/^\^([^\.]*).*/','$1',$sql_search);
|
|
$sql_search = preg_replace('/^\^[^\.]*(.*)$/','$1',$sql_search);
|
|
$new_search .= " AND videos.title LIKE '" . $start . "%'";
|
|
}
|
|
if (preg_match('/\$$/', $sql_search)) {
|
|
$tail = preg_replace('/^.*?([^\*]*)\$$/','$1',$sql_search);
|
|
$sql_search = preg_replace('/^(.*?)[^\*]*\$$/','$1',$sql_search);
|
|
$new_search .= " AND videos.title LIKE '%" . $tail . "'";
|
|
}
|
|
$sql_search = preg_replace('/(\.\*)?(.*)(\.\*)?/','$2',$sql_search);
|
|
$searches = preg_split('/\.\*/', $sql_search);
|
|
foreach ($searches as $s) {
|
|
if ($s != '') {
|
|
$new_search .= " AND videos.title LIKE '%" . $s . "%'";
|
|
}
|
|
}
|
|
$sql_search = preg_replace('/ AND(.*)/', '$1', $new_search);
|
|
error("You do not have the pcre.so extension for SQLite3.");
|
|
if ($permissions['writable']) {
|
|
error(" <p>You will need to add the path to your extensions directory with an argument in your php.ini file like:</p><p><i>sqlite3.extension_dir = \"/usr/lib/sqlite3/\"</i></p>");
|
|
}
|
|
error ("<p>Simulating basic wildcard (.*) and anchor (^$) expression:</p><p><i>$sql_search</i></p>");
|
|
}
|
|
} else {
|
|
$sql_search = " videos.title LIKE '%" . sql_escape($search) . "%'";
|
|
}
|
|
}
|
|
unset($args['search']);
|
|
$target = remove_arg($target,'search');
|
|
}
|
|
|
|
# Set flag to not hide videos marked as seen from SQL query results
|
|
if (isset($args['show_seen'])) {
|
|
$show_seen = $args['show_seen'];
|
|
unset($args['show_seen']);
|
|
$target = remove_arg($target,'show_seen');
|
|
}
|
|
|
|
# Remove previous filter arguments to revert to regular query results
|
|
if (isset($args['filter_clear'])) {
|
|
unset($args['filter_clear']);
|
|
$target = remove_arg($target,'filter_clear');
|
|
unset($args['filter']);
|
|
$target = remove_arg($target,'filter');
|
|
unset($args['filter[]']);
|
|
$target = remove_arg($target,urlencode('filter[]'));
|
|
}
|
|
|
|
# Add claus(es) to SQL query to limit by channel or category
|
|
if (isset($args['filter'])) {
|
|
$categories = array();
|
|
if (!is_array($args['filter'])) {
|
|
if (preg_match('/^UC[0-9a-zA-Z\_\-]{22}$/', $args['filter'])) {
|
|
$chan_filter = $args['filter'];
|
|
} elseif ($args['filter'] != '-') {
|
|
$categories[0] = $args['filter'];
|
|
}
|
|
$target = remove_arg($target,'filter');
|
|
$target = remove_arg($target,urlencode('filter[]'));
|
|
} else {
|
|
foreach ($args['filter'] as $filter) {
|
|
$categories[] = urldecode($filter);
|
|
$target = remove_arg($target,urlencode('filter[]'));
|
|
}
|
|
$target = remove_arg($target,'filter');
|
|
}
|
|
unset($args['filter']);
|
|
unset($args['filter[]']);
|
|
}
|
|
|
|
## ENDREAD
|
|
|
|
# If there are remaining arguments, ensure that the client has write permissions
|
|
if ($permissions['writable']) {
|
|
if (sizeof($args) > 0) {
|
|
$reload = 1;
|
|
}
|
|
} elseif ($permissions['readable']) {
|
|
if (sizeof($args) > 0) {
|
|
$reload = 1;
|
|
error("<p>You are allowed to read but not write. However, a write argument was passed.</p>");
|
|
unset($args);
|
|
$target = preg_replace('/^([^\?]*)\?.*/','$1',$_SERVER['REQUEST_URI']);
|
|
}
|
|
} else {
|
|
error('<div id="debug">' . $debug_text . '</div>');
|
|
return http_response_code(403);
|
|
}
|
|
|
|
## IMPORTANT: Write operations to be included after this point only.
|
|
|
|
# Initialize database
|
|
if ($no_db && isset($args['initialize'])) {
|
|
$debug_text .= "<p>Initializing database...</p>";
|
|
$debug_text .= "<p>" . initialize() . "</p>";
|
|
$reload = 1;
|
|
$target = remove_arg($target,'initialize');
|
|
$handle = new SQLite3($database);
|
|
$no_db = 0;
|
|
$args['refresh'] = 15;
|
|
}
|
|
|
|
# Delete database
|
|
if (isset($args['delete_db'])) {
|
|
$debug_text .= "<p>Deleting database</p>";
|
|
$err = delete_db();
|
|
if ($err) {
|
|
error("<p>Failed to delete db: $err</p>");
|
|
} else {
|
|
$debug_text .= "<p>Database deleted</p>";
|
|
}
|
|
}
|
|
|
|
# Handle all change requests.
|
|
if ( !$no_db && $reload && $permissions['writable'] ) {
|
|
|
|
# Force feeds to refresh
|
|
if (isset($args['force_refresh']) && $args['force_refresh'] == 1) {
|
|
$debug_text .= "<p>Refreshing feeds...</p>";
|
|
# Must free database in order to insert
|
|
$handle->close();
|
|
$debug_text .= "<p>" . refresh() . "</p>";
|
|
$handle = new SQLite3($database);
|
|
$target = remove_arg($target,'force_refresh');
|
|
$reload = 1;
|
|
}
|
|
|
|
# Mark one or all videos as played.
|
|
if (isset($args['rm'])) {
|
|
if (preg_match("/^([^']{11})$/",urldecode($args['rm']))) {
|
|
$debug_text .= "<p>Removing " . urldecode($args['rm']) . "</p>";
|
|
$rm = sql_escape(urldecode($args['rm']));
|
|
$handle->query("UPDATE videos SET seen = 1 WHERE videoId = '$rm'");
|
|
} elseif (urldecode($args['rm']) == '*') {
|
|
$debug_text .= "<p>Removing All<p/>";
|
|
$handle->query("UPDATE videos SET seen = 1 WHERE seen = 0");
|
|
}
|
|
$target = remove_arg($target,'rm');
|
|
}
|
|
|
|
# Mark one or all videos as played.
|
|
if (isset($args['unrm'])) {
|
|
if (preg_match("/^([^']{11})$/",urldecode($args['unrm']))) {
|
|
$debug_text .= "<p>Restoring " . urldecode($args['unrm']) . "</p>";
|
|
$unrm = sql_escape(urldecode($args['unrm']));
|
|
$handle->query("UPDATE videos SET seen = 0 WHERE videoId = '$unrm'");
|
|
}
|
|
$target = remove_arg($target,'unrm');
|
|
}
|
|
|
|
# Update the refresh interval
|
|
if (isset($args['refresh'])) {
|
|
$interval = urldecode($args['refresh']);
|
|
if (preg_match('/^[0-9]+$/',$interval) && $interval <= 60) {
|
|
$handle->query("UPDATE settings SET refresh = '$interval'");
|
|
update_cron($interval);
|
|
$debug_text .= "<p>Set update interval to $interval</p>";
|
|
} else {
|
|
error("<p>Update interval must be an integer lower than or equal to 60, $interval is not</p>");
|
|
}
|
|
$target = remove_arg($target,'refresh');
|
|
}
|
|
|
|
# Update the theme
|
|
if (isset($args['theme'])) {
|
|
$found = 0;
|
|
$theme = urldecode($args['theme']);
|
|
foreach (glob($root.'/css/*.css') as $file) {
|
|
$file = preg_replace('/.*\/([^\/]*)\.css$/','\1',$file);
|
|
if ($args['theme'] == $file && $file != 'style') {
|
|
$handle->query("UPDATE settings SET theme = '" . sql_escape($theme) . "'");
|
|
$debug_text .= "<p>Set theme $theme</p>";
|
|
$found = 1;
|
|
}
|
|
}
|
|
if (!$found) {
|
|
error("<p>" . $theme . " is not a valid theme option</p>");
|
|
}
|
|
$target = remove_arg($target,'theme');
|
|
}
|
|
|
|
# Update the thumbnail quality
|
|
if (isset($args['thumbnail_quality'])) {
|
|
if (preg_match("/^(maxresdefault|sddefault|hqdefault|mqdefault|default|1|2|3)$/",urldecode($args['thumbnail_quality']))) {
|
|
$thumbnail_quality = sql_escape(urldecode($args['thumbnail_quality']));
|
|
if (!isset($thumbnail_quality) || $thumbnail_quality == '') {
|
|
$thumbnail_quality = 'default';
|
|
}
|
|
$thumbnail_quality = sql_escape(urldecode($args['thumbnail_quality']));
|
|
$handle->query("UPDATE settings SET thumbnail_quality = '$thumbnail_quality'");
|
|
$debug_text .= "<p>Set thumbnail_quality $thumbnail_quality</p>";
|
|
} else {
|
|
error("<p>" . $args['thumbnail_quality'] . " is not a valid thumbnail_quality option</p>");
|
|
}
|
|
$target = remove_arg($target,'thumbnail_quality');
|
|
}
|
|
|
|
# Update the embedded player type
|
|
if (isset($args['embed_type'])) {
|
|
if (preg_match("/^(embed|nocookie|proxy)$/",urldecode($args['embed_type']))) {
|
|
$embed_type = sql_escape(urldecode($args['embed_type']));
|
|
$handle->query("UPDATE settings SET embed_type = '$embed_type'");
|
|
$debug_text .= "<p>Set embed_type $embed_type</p>";
|
|
} else {
|
|
error("<p>" . $args['embed_type'] . " is not a valid embed_type option</p>");
|
|
}
|
|
$target = remove_arg($target,'embed_type');
|
|
}
|
|
|
|
# Update the default player
|
|
if (isset($args['player'])) {
|
|
if (preg_match("/^(clip|embed|web)$/",urldecode($args['player']))) {
|
|
$player = sql_escape(urldecode($args['player']));
|
|
$handle->query("UPDATE settings SET player = '$player'");
|
|
$debug_text .= "<p>Set player $player</p>";
|
|
} else {
|
|
error("<p>" . $args['player'] . " is not a valid player option</p>");
|
|
}
|
|
$target = remove_arg($target,'player');
|
|
}
|
|
|
|
# Subscribe to a new channel
|
|
if (isset($args['sub'])) {
|
|
$debug_text .= "<p>Adding " . urldecode($args['sub']) . "...</p>";
|
|
# Must free database in order to insert
|
|
$handle->close();
|
|
$debug_text .= "<p>" . add_channel(urldecode($args['sub'])) . "</p>";
|
|
$handle = new SQLite3($database);
|
|
$reload = 1;
|
|
$target = remove_arg($target,'sub');
|
|
}
|
|
|
|
# Unsubscribe and remove channel's videos.
|
|
if (isset($args['unsub'])) {
|
|
$unsub = sql_escape(urldecode($args['unsub']));
|
|
$channels = $handle->query("SELECT * FROM channels WHERE channelId = '$unsub'");
|
|
$chan = $channels->fetchArray();
|
|
$debug_text .= "<p>Removing " . $chan['channelName'] . "</p>";
|
|
$handle->query("DELETE FROM channels WHERE channelId = '$unsub'");
|
|
$handle->query("DELETE FROM videos WHERE channelId = '$unsub'");
|
|
$target = remove_arg($target,'unsub');
|
|
}
|
|
|
|
# Update IPv4 writeable range
|
|
if (isset($args['write4'])) {
|
|
$write4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['write4']));
|
|
$write4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['write4']));
|
|
if (isset($args['read4'])) {
|
|
$read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4']));
|
|
$read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4']));
|
|
} else {
|
|
$read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",$settings['read4']);
|
|
$read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",$settings['read4']);
|
|
}
|
|
if ( write_outside_read4($read4ip,$read4cidr,$write4ip,$write4cidr) ) {
|
|
error("<p>Invalid read/write. Write must be completely contained within read.</p>");
|
|
$target = remove_arg($target,'write4');
|
|
$target = remove_arg($target,'read4');
|
|
unset($args['read4']);
|
|
} elseif ( in_range4($write4ip,$write4cidr,$_SERVER['REMOTE_ADDR']) ) {
|
|
$handle->query("UPDATE settings SET write4 = '$write4ip/$write4cidr'");
|
|
$debug_text .= "<p>Updated writable IPv4 to $write4ip/$write4cidr</p>";
|
|
$target = remove_arg($target,'write4');
|
|
} else {
|
|
error("<p>You are not allowed to remove write access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $write4ip/$write4cidr</p>");
|
|
$target = remove_arg($target,'write4');
|
|
}
|
|
}
|
|
|
|
# Update IPv4 readable range
|
|
if (isset($args['read4'])) {
|
|
# Update whether IPv4 is enabled
|
|
if (isset($args['enable4']) && $args['enable4'] == 'on') {
|
|
if (!$settings['enable4']) {
|
|
$debug_text .= "<p>Enabling IPv4</p>";
|
|
$handle->query("UPDATE settings SET enable4 = 1");
|
|
}
|
|
} else {
|
|
if ($settings['ipv'] == 4) {
|
|
error("<p>You are currently using the IPv4 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.</p>");
|
|
} else {
|
|
$debug_text .= "<p>Disabling IPv4</p>";
|
|
#$handle->query("UPDATE settings SET enable4 = 0");
|
|
}
|
|
}
|
|
$target = remove_arg($target,'enable4');
|
|
$read4ip = preg_replace("/([\d\.]+)\/\d+/","$1",urldecode($args['read4']));
|
|
$read4cidr = preg_replace("/[\d\.]+\/(\d+)/","$1",urldecode($args['read4']));
|
|
if ( in_range4($read4ip,$read4cidr,$_SERVER['REMOTE_ADDR']) ) {
|
|
$handle->query("UPDATE settings SET read4 = '$read4ip/$read4cidr'");
|
|
$debug_text .= "<p>Updated readable IPv4 to $read4ip/$read4cidr</p>";
|
|
$target = remove_arg($target,'read4');
|
|
} else {
|
|
error("<p>You are not allowed to remove read access from yourself. " . $_SERVER['REMOTE_ADDR'] . " is not in range $read4ip/$read4cidr</p>");
|
|
$target = remove_arg($target,'read4');
|
|
}
|
|
}
|
|
|
|
# Update IPv6 readable range
|
|
if (isset($args['read6'])) {
|
|
# Update whether IPv6 is enabled
|
|
if (isset($args['enable6']) && $args['enable6'] == 'on') {
|
|
if (!$settings['enable6']) {
|
|
$debug_text .= "<p>Enabling IPv6</p>";
|
|
$handle->query("UPDATE settings SET enable6 = 1");
|
|
}
|
|
} else {
|
|
if ($settings['ipv'] == 6) {
|
|
error("<p>You are currently using the IPv6 interface (Your IP: " . $_SERVER['REMOTE_ADDR'] . "). You cannot remove access from yourself.</p>");
|
|
} else {
|
|
$debug_text .= "<p>Disabling IPv6</p>";
|
|
$handle->query("UPDATE settings SET enable6 = 0");
|
|
}
|
|
}
|
|
$target = remove_arg($target,'enable6');
|
|
$handle->query("UPDATE settings SET read6 = '" . urldecode($args['read6']) . "'");
|
|
$debug_text .= "<p>Updated readable IPv6 to " . $args['read6'] . "</p>";
|
|
$target = remove_arg($target,'read6');
|
|
}
|
|
if (isset($args['write6'])) {
|
|
$handle->query("UPDATE settings SET write6 = '" . urldecode($args['write6']) . "'");
|
|
$debug_text .= "<p>Updated writable IPv6 to " . $args['write6'] . "</p>";
|
|
$target = remove_arg($target,'write6');
|
|
}
|
|
|
|
# Update the Category for channel
|
|
if (isset($args['category'])) {
|
|
# URL arg is formatted as 'channelId category,other,third'
|
|
$category_array = preg_split('/\ /',urldecode($args['category']));
|
|
foreach ($category_array as $item) {
|
|
# First item is the channelId
|
|
if (!isset($category_chan)) {
|
|
$category_chan = $item;
|
|
# All others are categories
|
|
} elseif (!isset($category_name)) {
|
|
$category_name = $item;
|
|
# Restore spaces removed by split
|
|
} else {
|
|
$category_name .= ' ' . $item;
|
|
}
|
|
}
|
|
$category_chan = sql_escape($category_chan);
|
|
$category_name = sql_escape($category_name);
|
|
if ($category_name != '') {
|
|
$category_name = ','.$category_name.',';
|
|
}
|
|
$debug_text .= '<p>Add Category - Channel: ' . $category_chan . ' Category: ' . $category_name . '</p>';
|
|
$handle->query("UPDATE channels SET category = '$category_name' WHERE channelId = '$category_chan'");
|
|
$debug_text .= '<p>Category updated</p>';
|
|
$target = remove_arg($target,'category');
|
|
}
|
|
|
|
# Update the Title Filter RegExp for a channel
|
|
if (isset($args['regex'])) {
|
|
$regex_array = preg_split('/\ /',urldecode($args['regex']));
|
|
foreach ($regex_array as $item) {
|
|
if (!isset($regex_chan)) {
|
|
$regex_chan = $item;
|
|
} elseif (!isset($regex_exp)) {
|
|
$regex_exp = $item;
|
|
} else {
|
|
$regex_exp .= ' ' . $item;
|
|
}
|
|
}
|
|
$regex_chan = sql_escape($regex_chan);
|
|
$regex_exp = sql_escape($regex_exp);
|
|
$debug_text .= '<p>Add Regex - Channel: ' . $regex_chan . ' Exp: ' . $regex_exp . '</p>';
|
|
$handle->query("UPDATE channels SET regex = '$regex_exp' WHERE channelId = '$regex_chan'");
|
|
$debug_text .= '<p>Regex updated</p>';
|
|
$target = remove_arg($target,'regex');
|
|
}
|
|
|
|
# If there are remaining arguments, they are invalid
|
|
if (preg_match("/[^\?]*\?(.*)/",$target)) {
|
|
$remaining = preg_replace("/^(.)*\?(.*)$/",'$2',$target);
|
|
$debug_text .= "<p>Remaining: $remaining</p>";
|
|
$bad_args = preg_split("/\&/",$remaining);
|
|
foreach ($bad_args as $arg) {
|
|
$value = preg_replace("/[^=]*=([^=]*)/","$1",$arg);
|
|
$arg = preg_replace("/([^=]*)=[^=]*/","$1",$arg);
|
|
error('<p>Ignoring invalid argument: ' . urldecode($arg) . ' with value ' . urldecode($value) . '</p>');
|
|
$target = remove_arg($target,$arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Clean up target URL given intact arguments
|
|
$target = preg_replace('/[\?&]$/', '', $target);
|
|
|
|
# Close DB then refresh the page with what should be a clean URL
|
|
if ($reload) {
|
|
if ($debug && isset($debug_text) && $debug_text != '') {
|
|
$debug_text .= '<a onclick="window.location = \'' . $target . '\';"><div class="button">Load ' . $target . '</div></a>';
|
|
$debug_text = '<div id="debug"><h2>DEBUG MODE</h2>' . $debug_text . '</div>';
|
|
} else {
|
|
if (isset($handle)) {
|
|
$handle->close();
|
|
}
|
|
header("Refresh:0; url=$target" . ((isset($error_text) && $error_text != '') ? "?msg=".urlencode($error_text) : ''));
|
|
exit();
|
|
}
|
|
}
|
|
|
|
$head = '
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>YouTube Subscriptions</title>
|
|
<link rel="icon" href="images/favicon.ico" type="image/x-icon"/>
|
|
<link rel="stylesheet" type="text/css" href="css/style.css"/>';
|
|
if ($mobile) {
|
|
$head .= '
|
|
<link rel="stylesheet" type="text/css" href="css/mobile.css"/>';
|
|
}
|
|
$head .= '
|
|
<link rel="stylesheet" type="text/css" href="css/default.css"/>';
|
|
if (isset($settings['theme']) && $settings['theme'] != 'default') {
|
|
$head .= '
|
|
<link rel="stylesheet" type="text/css" href="css/' . $settings['theme'] . '.css"/>';
|
|
}
|
|
$head .= '
|
|
<script>
|
|
// Copy link to clipboard
|
|
function insertAfter(referenceNode,newNode) {
|
|
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
|
|
}
|
|
async function clipboard(video,url) {
|
|
var e = document.getElementById(video);
|
|
var l = document.getElementById(\'clipboard\');
|
|
// Temporary input to hold link
|
|
l.value = url;
|
|
// Copy link to clipboard
|
|
l.select();
|
|
document.execCommand("copy");
|
|
//e.parentNode.removeChild(tmp);
|
|
// Copied pop-up
|
|
var copied = document.createElement("div");
|
|
copied.id = "copied";
|
|
copied.innerHTML = \'<h3>Copied to Clipboard</h3>\';
|
|
insertAfter(e,copied);
|
|
await sleep(1000);
|
|
e.parentNode.removeChild(copied);
|
|
}
|
|
// For menues, allow swapping between visible and hidden
|
|
function toggle_visibility(id) {
|
|
var div = document.getElementById(id);
|
|
var img = document.getElementById(id+\'-img\');
|
|
if (div.style.display == \'block\') {
|
|
div.style.display = \'none\';
|
|
img.classList.remove(\'tab-focused\');;
|
|
} else {
|
|
div.style.display = \'block\';
|
|
img.classList.add(\'tab-focused\');;
|
|
}
|
|
}
|
|
// For menues, specifically hide other menues
|
|
function invisible(id) {
|
|
var div = document.getElementById(id);
|
|
var img = document.getElementById(id+\'-img\');
|
|
div.style.display = \'none\';
|
|
img.classList.remove(\'tab-focused\');;
|
|
}
|
|
// Assume that the page has timed out unless \'elapsed\' is disabled by the end of the page
|
|
var elapsed = 1;
|
|
function sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve,ms));
|
|
}
|
|
async function timeout() {
|
|
await sleep(3000);
|
|
if (elapsed) {
|
|
toggle_visibility(\'timeout\');
|
|
}
|
|
}
|
|
timeout();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<input id="clipboard" value="">';
|
|
if (isset($permissions['readable']) && $permissions['readable']) {
|
|
|
|
# HEADER
|
|
|
|
$header = '
|
|
<div id="timeout" style="display: none;">Response took longer than expected. The database was probably locked by another process. Please refresh the page.</div>
|
|
<style>.' . ((isset($settings['player'])) ? $settings['player'] : 'web') . ' { background: #3232dd !important; }</style>
|
|
<div id="header">
|
|
<div id="title" title="Home (Reset all arguments)">
|
|
<a href="' . preg_replace('/\?.*/','',$target) . '"><h1>YouTube Subscriptions</h1></a>
|
|
<a onclick="toggle_visibility(\'conf\');invisible(\'rm\');invisible(\'sub\');invisible(\'cat\');invisible(\'search\');"><img title="Settings" id="conf-img" alt="Manage Application Settings" src="images/conf.svg"></a>
|
|
<a onclick="toggle_visibility(\'rm\');invisible(\'conf\');invisible(\'sub\');invisible(\'cat\');invisible(\'search\');"><img title="Remove/Manage Videos" id="rm-img" alt="Trash" src="images/' . ((isset($show_seen) && $show_seen) ? 'no-' : '') . 'rm.svg"></a>
|
|
<a onclick="toggle_visibility(\'sub\');invisible(\'conf\');invisible(\'rm\');invisible(\'cat\');invisible(\'search\');"><img title="Add Subscriptions" id="sub-img" alt="Add" src="images/sub.svg"></a>
|
|
<a onclick="toggle_visibility(\'cat\');invisible(\'conf\');invisible(\'rm\');invisible(\'sub\');invisible(\'search\');"><img title="Change Categories" id="cat-img" alt="Categories" src="images/cat.svg"></a>
|
|
<a onclick="toggle_visibility(\'search\');invisible(\'conf\');invisible(\'rm\');invisible(\'sub\');invisible(\'cat\');"><img title="Search Titles" id="search-img" alt="Search" src="images/search.svg"></a>';
|
|
if (!$no_db) {
|
|
$header .= '
|
|
<a onclick="target = \'' . $target . '?force_refresh=1\'; window.location.assign(target);"><img title="Force Refresh" src="images/refresh.svg"></a>';
|
|
}
|
|
$header .= '
|
|
</div>
|
|
<div id="search" class="menu"' . ((isset($search)) ? 'style="display: block"' : '') . '>';
|
|
|
|
if ($no_db) {
|
|
$header .= '
|
|
Not connected to database.';
|
|
} else {
|
|
$header .= '
|
|
<form name="search" action="' . $target . '" method="get">
|
|
Search Titles <input type="textbox" name="search"';
|
|
|
|
# If there is already a search pattern, populate the box with this, otherwise use placeholder.
|
|
if (isset($search) && $search != '') {
|
|
$header .= 'value="' . $search . '">';
|
|
} else {
|
|
$header .= 'placeholder="Phrase or /RegEx/">';
|
|
}
|
|
$header .= '</input>
|
|
<button type="submit">Search</button>
|
|
<button type="submit" name="search_clear"><b>X</b></button>
|
|
</form>';
|
|
}
|
|
|
|
$header .= '
|
|
</div>
|
|
<div id="cat" class="menu"' . ((isset($chan_filter) || isset($categories)) ? 'style="display: block"' : '') . '>';
|
|
|
|
if ($no_db) {
|
|
$header .= '
|
|
Not connected to database.';
|
|
} else {
|
|
$header .= '
|
|
<form>';
|
|
|
|
# Get all available categories given current selections
|
|
if (!isset($chan_filter)) {
|
|
$header .= '
|
|
Select Categories:<ul>';
|
|
$query = "SELECT category FROM channels";
|
|
$limit = '';
|
|
if (isset($categories)) {
|
|
foreach ($categories as $c) {
|
|
if ($limit == '') {
|
|
$limit .= " WHERE channels.category LIKE '%" . sql_escape($c) . "%'";
|
|
} else {
|
|
$limit .= " AND channels.category LIKE '%" . sql_escape($c) . "%'";
|
|
}
|
|
}
|
|
}
|
|
$query .= $limit;
|
|
|
|
$available_categories = $handle->query($query);
|
|
|
|
# Get individual categories and counts
|
|
$cats = array();
|
|
while ($cat = $available_categories->fetchArray()) {
|
|
if ($cat['category'] == '') {
|
|
if (isset($cats['UNCATEGORIZED'])) {
|
|
$cats['UNCATEGORIZED']++;
|
|
} else {
|
|
$cats['UNCATEGORIZED'] = 1;
|
|
}
|
|
} else {
|
|
$channel_cats = preg_split('/,/',$cat['category']);
|
|
foreach ($channel_cats as $c) {
|
|
if ($c == '') {
|
|
continue;
|
|
} elseif (isset($cats[$c])) {
|
|
$cats[$c] += 1;
|
|
} else {
|
|
$cats[$c] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($cats as $category => $count) {
|
|
$header .= '
|
|
<li><input type="checkbox" name="filter[]" value="' . $category . '"';
|
|
if (isset($categories) && (preg_grep("/^$category$/", $categories))) {
|
|
$header .= ' checked="checked"';
|
|
}
|
|
$header .= '>' . $category . ' (' . $count . ')</input><li/>';
|
|
}
|
|
if (!isset($categories) && sizeof($cats) == 0) {
|
|
$header .= '<li>None</li>';
|
|
}
|
|
$header .= '
|
|
</ul><button type="submit">Submit</button>';
|
|
if (isset($categories)) {
|
|
$header .= '
|
|
<button type="submit" name="filter_clear">Clear</button>';
|
|
}
|
|
} else {
|
|
$header .= 'Currently showing only channel: ' . $chan_filter . '
|
|
<button type="submit" name="filter_clear">Clear</button>';
|
|
}
|
|
|
|
$header .= '
|
|
</form>';
|
|
}
|
|
|
|
$header .= '
|
|
</div>
|
|
<div id="sub" class="menu">';
|
|
|
|
if ($no_db) {
|
|
$header .= '
|
|
Not connected to database.';
|
|
} else {
|
|
$header .= '
|
|
<form>
|
|
Add Channel <input type="textbox" name="sub" placeholder="Channel ID, Name, URL">
|
|
<button type="submit">Submit</button>
|
|
</form>';
|
|
}
|
|
|
|
$header .= '
|
|
</div>
|
|
<div id="rm" class="menu">';
|
|
|
|
if ($no_db) {
|
|
$header .= '
|
|
Not connected to database.';
|
|
} else {
|
|
$header .= '
|
|
<form>';
|
|
if (isset($show_seen) && $show_seen) {
|
|
$header .= '
|
|
<a href="' . $target . '?show_seen=0"><div class="button">Hide Seen Videos</div></a>';
|
|
} else {
|
|
$header .= '
|
|
<a onclick="target = \'' . $target . '?show_seen=1\'; window.location.assign(target);"><div class="button">Show Seen Videos</div></a>';
|
|
}
|
|
$header .= '
|
|
<a onclick="if (confirm(\'Are you sure? Clicking OK will remove all items from the list.\')) { var target = \'' . $target . '\' + \'?rm=*\'; window.location = target; }"><div class="button">Mark All As Seen</div></a>
|
|
<!-- Not yet implemented
|
|
<a onclick="if (confirm(\'When videos are removed, they remain in the database as SEEN rather than being deleted. This prevents them from being added back the next time videos are fetched.\n\nBy clicking OK vidoes that no longer appear on the first page of results - from which this list is fetched - will actually get deleted, reducing the size of the database.\n\nThis may take a couple of minutes. The database is currently: ' . round(((filesize($database)/1024)/1024),2) . 'MB\')) { var target = \'' . $target . '?clear=1\'; window.location.assign(target); }"><div class="button">Clean Ancient Database Items</div></a>
|
|
-->
|
|
<a onclick="if (confirm(\'Are you sure you want to delete the entire database? All of your subscriptions will be removed.\')) { var target = \'' . $target . '?delete_db=1\'; window.location.assign(target); }"><div class="button">Delete Database Entirely</div></a>
|
|
</form>';
|
|
}
|
|
$header .= '
|
|
</div>
|
|
<div id="conf" class="menu">';
|
|
if ($no_db) {
|
|
$header .= '
|
|
Not connected to database.';
|
|
} else {
|
|
$header .= '
|
|
<form name="conf" action="' . $target . '" method="get">
|
|
<ul>
|
|
<li>Enable IPv4<input type="checkbox" name="enable4"' . (($settings['enable4']) ? 'checked="checked"' : '') . '"></li>
|
|
<li>IPv4 Read<input type="textbox" name="read4" value="' . $settings['read4'] . '"></li>
|
|
<li>IPv4 Write<input type="textbox" name="write4" value="' . $settings['write4'] . '"></li>
|
|
<hr/>
|
|
<li>Enable IPv6<input type="checkbox" name="enable6"' . (($settings['enable6']) ? 'checked="checked"' : '') . '"></li>
|
|
<li>IPv6 Read<input type="textbox" name="read6" value="' . $settings['read6'] . '"></li>
|
|
<li>IPv6 Write<input type="textbox" name="write6" value="' . $settings['write6'] . '"></li>
|
|
<hr/>
|
|
<li>Refresh Interval (minutes)<input name="refresh" type="textbox" value="' . $settings['refresh'] . '"></li>
|
|
<hr/>
|
|
<li>Application Theme<select name="theme" autocomplete="off">';
|
|
foreach (glob($root.'/css/*.css') as $theme) {
|
|
$theme = preg_replace('/.*\/([^\/]*)\.css$/','\1',$theme);
|
|
if ($theme == 'mobile') {
|
|
next;
|
|
}
|
|
if ($theme != 'style') {
|
|
$header .= '<option value="' . $theme . '"';
|
|
if ($settings['theme'] == $theme) {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>' . $theme . '</option>';
|
|
}
|
|
}
|
|
$header .= '</select></li>
|
|
<li>Thumbnails<br/><small>* letterboxed</small><select name="thumbnail_quality" autocomplete="off"><option value="maxresdefault"';
|
|
if ($settings['thumbnail_quality'] == 'maxres' || $settings['thumbnail_quality'] == 'maxresdefault') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>HD (1280x720)</option><option value="sddefault"';
|
|
if ($settings['thumbnail_quality'] == 'sddefault') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>SD (640x480*)</option><option value="hqdefault"';
|
|
if ($settings['thumbnail_quality'] == 'high' || $settings['thumbnail_quality'] == 'hqdefault') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>High (480x360*)</option><option value="mqdefault"';
|
|
if ($settings['thumbnail_quality'] == 'medium' || $settings['thumbnail_quality'] == 'mqdefault') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Medium (320x180*)</option><option value="default"';
|
|
if ($settings['thumbnail_quality'] == 'low' || $settings['thumbnail_quality'] == 'default') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Low (120x90*)</option><option value="1"';
|
|
if ($settings['thumbnail_quality'] == 'start' || $settings['thumbnail_quality'] == '1') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Start (120x90*)</option><option value="2"';
|
|
if ($settings['thumbnail_quality'] == 'middle' || $settings['thumbnail_quality'] == '2') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Middle (120x90*)</option><option value="3"';
|
|
if ($settings['thumbnail_quality'] == 'end' || $settings['thumbnail_quality'] == '3') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>End (120x90*)</option>';
|
|
$header .= '</select></li>
|
|
<li>Embedded Player Type<select name="embed_type" autocomplete="off"><option value="embed"';
|
|
if ($settings['embed_type'] == 'embed') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Standard</option><option value="nocookie"';
|
|
if ($settings['embed_type'] == 'nocookie') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>No Cookie</option><option value="proxy"';
|
|
if ($settings['embed_type'] == 'proxy') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Proxy (DuckDuckGo)</option></select></li>
|
|
</select></li>
|
|
<li>Default Player Type<select name="player" autocomplete="off"><option value="embed"';
|
|
if ($settings['player'] == 'embed' || $settings['player'] == 'nocookie' || $settings['player'] == 'proxy') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Embedded Player</option><option value="web"';
|
|
if ($settings['player'] == 'web') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Regular Website</option><option value="clip"';
|
|
if ($settings['player'] == 'clip') {
|
|
$header .= ' selected="selected"';
|
|
}
|
|
$header .= '>Copy to Clipboard</option></select></li>
|
|
</ul>
|
|
<button type="submit">Save</button>
|
|
</form>';
|
|
}
|
|
$header .= '
|
|
</div>';
|
|
|
|
# SUBSCRIPTION BAR
|
|
|
|
$header .= '
|
|
<div id="subs">
|
|
<ul>';
|
|
|
|
# Fetch all subscriptions based on the current filters; index thumbnails
|
|
if (!$no_db) {
|
|
$thumbnails = array();
|
|
if (isset($chan_filter)) {
|
|
$channels = $handle->query("SELECT * FROM channels WHERE channelId = '" . $chan_filter . "'");
|
|
} elseif (!isset($categories)) {
|
|
$channels = $handle->query("SELECT * FROM channels ORDER BY LOWER(channelName)");
|
|
} elseif ($categories[0] == 'UNCATEGORIZED') {
|
|
$channels = $handle->query("SELECT * FROM channels WHERE category IS NULL ORDER BY LOWER(channelName)");
|
|
} elseif (preg_match('/^[0-9a-zA-Z_\-]{24}$/',$categories[0])) {
|
|
$channels = $handle->query("SELECT * FROM channels WHERE channelId = '" . $categories[0] . "' ORDER BY LOWER(channelName)");
|
|
} else {
|
|
$channels = $handle->query("SELECT * FROM channels" . $limit . " ORDER BY LOWER(channelName)");
|
|
}
|
|
|
|
# Print subscription list as thumbnails with available options
|
|
$filters = array();
|
|
while ($row = $channels->fetchArray()) {
|
|
$thumbnails[$row['channelId']] = $row['channelThumbnail'];
|
|
$header .= '
|
|
<li>
|
|
<a href="https://'.($mobile ? 'm' : 'www').'.youtube.com/channel/' . $row['channelId'] . '/videos" target="_blank"><img title="' . $row['channelName'] . '" src="' . $row['channelThumbnail'] . '"></a>
|
|
<div class="unsubscribe" title="Unsubscribe from ' . $row['channelName'] . '">
|
|
<a onclick="if (confirm(\'Unsubscribe from ' . $row['channelName'] . '?\')) { window.location = \'' . $target . ((isset($category)) ? '?filter='.$category.'&' : '?') . 'unsub=' . $row['channelId'] . '\'; }"><b>X</b></a>
|
|
</div>';
|
|
|
|
if (isset($chan_filter)) {
|
|
$header .= '
|
|
<div class="solo" title="Return to all channels">
|
|
<a href="' . $target . '">✓</a>
|
|
</div>';
|
|
} else {
|
|
$header .= '
|
|
<div class="no-solo" title="View Only This Channel">
|
|
<a href="' . $target . '?filter=' . $row['channelId'] . '">✓</a>
|
|
</div>';
|
|
}
|
|
|
|
if (isset($row['category']) && $row['category'] != '') {
|
|
$row['category'] = preg_replace('/^,(.*),$/','$1',$row['category']);
|
|
$header .= '
|
|
<div class="category" title="Change Category: ' . $row['category'] . '">
|
|
<a onclick="var category = prompt(\'Modify category (separate with , ):\', \'' . $row['category'] . '\'); var target = \'' . $target . ((isset($category)) ? '?filter='.$category.'&' : '?') . 'category=' . $row['channelId'] . '\%20\' + category; window.location = target;">#</a>
|
|
</div>';
|
|
} else {
|
|
$header .= '
|
|
<div class="no-category" title="Categorize">
|
|
<a onclick="var category = prompt(\'Add category (separate with , ):\', \'\'); if (category) { var target = \'' . $target . ((isset($category))? '?filter='.$category.'&' : '?') . 'category=' . $row['channelId'] . '\%20\' + category; window.location = target; }">#</a>
|
|
</div>';
|
|
}
|
|
|
|
if (isset($row['regex']) && $row['regex'] != '') {
|
|
$filters[$row['channelId']]=$row['regex'];
|
|
$header .= '
|
|
<div class="regex" title="Title Filter: ' . $row['regex'] . '">
|
|
<a onclick="var regex = prompt(\'Modify the current filter? (Perl Compatible Regular Expression)\', \'' . $row['regex'] . '\'); var target = \'' . $target . ((isset($category)) ? '?filter='.$category.'&' : '?') . 'regex=' . $row['channelId'] . '\%20\' + regex; window.location = target;">/*/</a>
|
|
</div>
|
|
</li>';
|
|
} else {
|
|
$header .= '
|
|
<div class="no-regex" title="Add Title Filter">
|
|
<a onclick="var regex = prompt(\'Add title filter? (Perl Compatible Regular Expression)\', \'Good Series Only - Episode #\\\\d+\'); if (regex) { var target = \'' . $target . ((isset($category)) ? '?filter='.$category.'&' : '?') . 'regex=' . $row['channelId'] . '\%20\' + regex; window.location = target; }">/*/</a>
|
|
</div>
|
|
</li>';
|
|
}
|
|
}
|
|
}
|
|
|
|
$header .= '
|
|
</ul>
|
|
</div>
|
|
</div>';
|
|
|
|
$body = '
|
|
<div id="videos">';
|
|
|
|
# VIDEOS
|
|
|
|
if ($no_db) {
|
|
$body .= '
|
|
<div id="notice" class="row">
|
|
<h2>Database not yet created</h2>
|
|
<p>It appears that you have not yet initialized your subscriptions database. You can do this with the button below.</p>
|
|
<p>Note that this will create an empty database with default settings. The default settings allow read and write access from all IPs, so you will probably want to update the settings immediately. This can be done using the cog icon in the top right corner.</p>
|
|
<a onclick="window.location.assign(\'' . $target . '?initialize=1\');"><div class="button">Initialize</div></a>
|
|
</div>';
|
|
} else {
|
|
# Build SQL query for desired videos
|
|
$query = "SELECT * FROM videos ";
|
|
$limit = "LIMIT 100 ";
|
|
$and = array();
|
|
if (isset($chan_filter)) {
|
|
$and[] = "channelId = '" . sql_escape($chan_filter) . "'";
|
|
} elseif (isset($categories) && sizeof($categories)) {
|
|
if ($categories[0] == 'UNCATEGORIZED') {
|
|
$query .= "JOIN channels ON videos.channelId=channels.channelId";
|
|
$and[] = 'channels.category IS NULL';
|
|
} else {
|
|
$query .= "JOIN channels ON videos.channelId=channels.channelId";
|
|
foreach ($categories as $cat) {
|
|
$and[] = "category like '%" . sql_escape($cat) . "%'";
|
|
}
|
|
}
|
|
} else {
|
|
$query .= "JOIN channels ON videos.channelId=channels.channelId";
|
|
}
|
|
if (isset($sql_search) && $sql_search != '') {
|
|
$and[] = $sql_search;
|
|
}
|
|
if (!isset($show_seen) || $show_seen == 0) {
|
|
$and[] = "seen != 1";
|
|
}
|
|
if (sizeof($and)) {
|
|
$query .= " WHERE " . join(' AND ', $and);
|
|
}
|
|
$query .= " ORDER BY age DESC " . $limit . ";";
|
|
$result = $handle->query($query);
|
|
|
|
# Loop through list of videos collected
|
|
$x = 0;
|
|
while ($x <= 100 && $row = $result->fetchArray()) {
|
|
if (isset($filters[$row['channelId']]) && preg_match('#^/.*/[gmi]*$#',$filters[$row['channelId']]) && !preg_match($filters[$row['channelId']],$row['title'])) {
|
|
continue;
|
|
} elseif (isset($filters[$row['channelId']]) && !preg_match("/".preg_replace('#/#','\\\/',$filters[$row['channelId']])."/i",$row['title'])) {
|
|
continue;
|
|
} else {
|
|
$row['channelThumbnail'] = $thumbnails[$row['channelId']];
|
|
$body .= print_video($row,$settings,$mobile,$show_seen);
|
|
$x++;
|
|
}
|
|
}
|
|
$handle->close();
|
|
if ($x == 0) {
|
|
if (sizeof($thumbnails) == 0 && !isset($filter)) {
|
|
$body .= '
|
|
<div id="notice" class="row">
|
|
<h2>No Subscriptions</h2>
|
|
<p>Use the <b>+</b> menu above to add new subscriptions</p>
|
|
<script>toggle_visibility(\'sub\');</script>
|
|
<p>This box will accept a variety of inputs including:
|
|
<ul>
|
|
<li>UC-unique-channel-code-Q</li>
|
|
<li>ChannelName</li>
|
|
<li>https://www.youtube.com/channel/UC-unique-channel-code-Q</li>
|
|
<li>https://www.youtube.com/channel/UC-unique-channel-code-Q/videos</li>
|
|
<li>https://www.youtube.com/c/ChannelName</li>
|
|
<li>https://www.youtube.com/user/ChannelName</li>
|
|
</ul>
|
|
</p>';
|
|
} else {
|
|
$body .= '
|
|
<div id="notice" class="row">
|
|
<h2>No results</h2>
|
|
<p>You may wish to try one of the following:</p>
|
|
<ul>
|
|
<li><b>🗘</b> Force a refresh</li>
|
|
<li><b>🔍</b> Broaden your search criteria</li>
|
|
<li><b># </b> Change categories</li>
|
|
<li><b>🗑</b> Show already seen videos</li>
|
|
</ul>
|
|
<a onclick="window.location.assign(\'' . $target . '\');"><div class="button">Clear All Filters</div></a>
|
|
</div>';
|
|
}
|
|
}
|
|
}
|
|
|
|
# FOOTER
|
|
|
|
$footer = '
|
|
</div>
|
|
<script>
|
|
// Clear this flag to prevent timeout warning
|
|
elapsed = 0;
|
|
</script>
|
|
<div id="footer">
|
|
<p>YouTube is a registered trademark of Alphabet Incorporated. All videos and channel names are property of their respective copyright holders. The author of this software claims no ownership of any content outside of the source code of this application.</p>
|
|
<a id="copyright" href="https://john.me.tz/terms/copyright.php?offset=Software">© John Mertz</a>
|
|
<a id="source" href="https://git.john.me.tz/jpm/ytyt-legacy">Source Here</a>
|
|
</div>
|
|
</body>
|
|
</html>';
|
|
|
|
}
|
|
|
|
# Print page
|
|
|
|
print $head;
|
|
|
|
if (($debug && isset($debug_text) && $debug_text != '') || !$permissions['readable']) {
|
|
if (isset($debug_text) && $debug_text != '') {
|
|
print $debug_text;
|
|
} else {
|
|
print '<div id="debug"><h2>Access Denied</h2><p>An unknown error has occurred</p></div>';
|
|
}
|
|
} else {
|
|
print $header;
|
|
if (isset($error_text) && $error_text != '') {
|
|
print '
|
|
<div id="error" class="row"><a href="' . preg_replace('/msg=[^&]*&?/','',$target) . '"><div id="dismiss" class="button mark">X</div></a><h2>Error:</h2><p>' . $error_text . '</p></div>';
|
|
}
|
|
print $body;
|
|
}
|
|
print $footer;
|
|
|