forked from lolcat/4get
boobs
This commit is contained in:
parent
edc917f5ee
commit
addc5a14a9
31
api.txt
31
api.txt
|
@ -267,20 +267,23 @@
|
||||||
Each entry under "song" contains a array index called "stream" that
|
Each entry under "song" contains a array index called "stream" that
|
||||||
looks like this ::
|
looks like this ::
|
||||||
|
|
||||||
endpoint: audio_sc
|
endpoint: sc
|
||||||
url: https://api-v2.soundcloud <...>
|
url: https://api-v2.soundcloud <...>
|
||||||
|
|
||||||
|
|
||||||
When the endpoint is "audio_sc", you MUST use 4get's audio_sc
|
When the endpoint is something else than "linear", you MUST use
|
||||||
endpoint, for example, if you want an audio stream back. Otherwise,
|
the specified endpoint. Otherwise, you are free to handle that
|
||||||
you are free to handle the json+m3u8 crap yourself. If the endpoint
|
json+m3u8 crap yourself. If the endpoint is equal to "linear", the
|
||||||
is equal to "audio", that URL SHOULD return a valid HTTP audio
|
URL should return a valid HTTP audio stream. To access the endpoint,
|
||||||
stream, and using the "audio" endpoint becomes optional again.
|
you must add the following prefix in your request, like so:
|
||||||
|
|
||||||
|
https://4get.ca/audio/<endpoint>?s=<url>
|
||||||
|
|
||||||
|
|
||||||
+ /favicon
|
+ /favicon
|
||||||
Get the favicon for a website. The only parameter is "s", and must
|
Get the favicon for a website. The only parameter is "s", and must
|
||||||
include the protocol.
|
include the protocol for fetching in case the favicon is not cached
|
||||||
|
yet.
|
||||||
|
|
||||||
Example ::
|
Example ::
|
||||||
|
|
||||||
|
@ -313,14 +316,14 @@
|
||||||
is set.
|
is set.
|
||||||
|
|
||||||
|
|
||||||
+ /audio
|
+ /audio/linear
|
||||||
Get a proxied audio file. Does not support "Range" headers, as it's
|
Get a proxied audio file. Does not support "Range" headers, as it's
|
||||||
only used to proxy small files.
|
only used to proxy small files (hence why it's called linear DUH)
|
||||||
|
|
||||||
The parameter is "s" for the audio link.
|
The parameter is "s" for the audio link.
|
||||||
|
|
||||||
|
|
||||||
+ /audio_sc
|
+ /audio/sc
|
||||||
Get a proxied audio file for SoundCloud. Does not support downloads
|
Get a proxied audio file for SoundCloud. Does not support downloads
|
||||||
trough WGET or CURL, since it returns 30kb~160kb "206 Partial
|
trough WGET or CURL, since it returns 30kb~160kb "206 Partial
|
||||||
Content" parts, due to technical limitations that comes with
|
Content" parts, due to technical limitations that comes with
|
||||||
|
@ -334,6 +337,14 @@
|
||||||
does not support "normal" SoundCloud URLs at this time.
|
does not support "normal" SoundCloud URLs at this time.
|
||||||
|
|
||||||
|
|
||||||
|
+ /audio/spotify
|
||||||
|
Get a proxied Spotify audio file. Accepts a track ID for the "s"
|
||||||
|
parameter. Will only allow you to fetch the 30 second preview since
|
||||||
|
I don't feel like fucking with cookies and accounts every fucking
|
||||||
|
living moment of my life. You must handle the initial 302 redirect
|
||||||
|
to the /audio/linear endpoint.
|
||||||
|
|
||||||
|
|
||||||
+ Appendix
|
+ Appendix
|
||||||
If you have any questions or need clarifications, please send an
|
If you have any questions or need clarifications, please send an
|
||||||
email my way to will at lolcat.ca
|
email my way to will at lolcat.ca
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if(!isset($_GET["s"])){
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
header("X-Error: No SOUND(s) provided!");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
include "../data/config.php";
|
||||||
|
include "../lib/curlproxy.php";
|
||||||
|
$proxy = new proxy();
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
$proxy->stream_linear_audio($_GET["s"]);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
header("X-Error: " . $error->getMessage());
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
new sc_audio();
|
||||||
|
|
||||||
|
class sc_audio{
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
include "../lib/curlproxy.php";
|
||||||
|
$this->proxy = new proxy();
|
||||||
|
|
||||||
|
if(isset($_GET["u"])){
|
||||||
|
|
||||||
|
/*
|
||||||
|
we're now proxying audio
|
||||||
|
*/
|
||||||
|
$viewkey = $_GET["u"];
|
||||||
|
|
||||||
|
if(!isset($_GET["r"])){
|
||||||
|
|
||||||
|
$this->do404("Ranges(r) are missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ranges = explode(",", $_GET["r"]);
|
||||||
|
|
||||||
|
// sanitize ranges
|
||||||
|
foreach($ranges as &$range){
|
||||||
|
|
||||||
|
if(!is_numeric($range)){
|
||||||
|
|
||||||
|
$this->do404("Invalid range specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
$range = (int)$range;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort ranges (just to make sure)
|
||||||
|
sort($ranges);
|
||||||
|
|
||||||
|
// convert ranges to pairs
|
||||||
|
$last = -1;
|
||||||
|
foreach($ranges as &$r){
|
||||||
|
|
||||||
|
$tmp = $r;
|
||||||
|
$r = [$last + 1, $r];
|
||||||
|
|
||||||
|
$last = $tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
$browser_headers = getallheaders();
|
||||||
|
|
||||||
|
// get the requested range from client
|
||||||
|
$client_range = 0;
|
||||||
|
foreach($browser_headers as $key => $value){
|
||||||
|
|
||||||
|
if(strtolower($key) == "range"){
|
||||||
|
|
||||||
|
preg_match(
|
||||||
|
'/bytes=([0-9]+)/',
|
||||||
|
$value,
|
||||||
|
$client_regex
|
||||||
|
);
|
||||||
|
|
||||||
|
if(isset($client_regex[1])){
|
||||||
|
|
||||||
|
$client_range = (int)$client_regex[1];
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$client_range = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
$client_range < 0 ||
|
||||||
|
$client_range > $ranges[count($ranges) - 1][1]
|
||||||
|
){
|
||||||
|
|
||||||
|
// range is not satisfiable
|
||||||
|
http_response_code(416);
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$rng = null;
|
||||||
|
for($i=0; $i<count($ranges); $i++){
|
||||||
|
|
||||||
|
if($ranges[$i][0] <= $client_range){
|
||||||
|
|
||||||
|
$rng = $ranges[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxy data!
|
||||||
|
http_response_code(206); // partial content
|
||||||
|
header("Accept-Ranges: bytes");
|
||||||
|
header("Content-Range: bytes {$rng[0]}-{$rng[1]}/" . ($ranges[count($ranges) - 1][1] + 1));
|
||||||
|
|
||||||
|
$viewkey =
|
||||||
|
preg_replace(
|
||||||
|
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
|
||||||
|
'/media/$1/' . $rng[0] . '/' . $rng[1],
|
||||||
|
$viewkey
|
||||||
|
);
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
$this->proxy->stream_linear_audio(
|
||||||
|
$viewkey
|
||||||
|
);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
$this->do404("Could not read stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
redirect user to correct resource
|
||||||
|
we need to scrape and store the byte positions in the result URL
|
||||||
|
*/
|
||||||
|
if(!isset($_GET["s"])){
|
||||||
|
|
||||||
|
$this->do404("The URL(s) parameter is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewkey = $_GET["s"];
|
||||||
|
|
||||||
|
if(
|
||||||
|
preg_match(
|
||||||
|
'/soundcloud\.com$/',
|
||||||
|
parse_url($viewkey, PHP_URL_HOST)
|
||||||
|
) === false
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->do404("This endpoint can only be used for soundcloud streams");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
$json = $this->proxy->get($viewkey)["body"];
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
$this->do404("Curl error: " . $error->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($json, true);
|
||||||
|
|
||||||
|
if(!isset($json["url"])){
|
||||||
|
|
||||||
|
$this->do404("Could not get URL from JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewkey = $json["url"];
|
||||||
|
|
||||||
|
$m3u8 = $this->proxy->get($viewkey)["body"];
|
||||||
|
|
||||||
|
$m3u8 = explode("\n", $m3u8);
|
||||||
|
|
||||||
|
$lineout = null;
|
||||||
|
$streampos_arr = [];
|
||||||
|
foreach($m3u8 as $line){
|
||||||
|
|
||||||
|
$line = trim($line);
|
||||||
|
if($line[0] == "#"){
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($lineout === null){
|
||||||
|
$lineout = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match(
|
||||||
|
'/\/media\/[0-9]+\/([0-9]+)\/([0-9]+)/',
|
||||||
|
$line,
|
||||||
|
$matches
|
||||||
|
);
|
||||||
|
|
||||||
|
if(isset($matches[0])){
|
||||||
|
|
||||||
|
$streampos_arr[] = [
|
||||||
|
(int)$matches[1],
|
||||||
|
(int)$matches[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($lineout === null){
|
||||||
|
|
||||||
|
$this->do404("Could not get stream URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineout =
|
||||||
|
preg_replace(
|
||||||
|
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
|
||||||
|
'/media/$1/0/0',
|
||||||
|
$lineout
|
||||||
|
);
|
||||||
|
|
||||||
|
$streampos = [];
|
||||||
|
|
||||||
|
foreach($streampos_arr as $pos){
|
||||||
|
|
||||||
|
$streampos[] = $pos[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$streampos = implode(",", $streampos);
|
||||||
|
|
||||||
|
header("Location: /audio/sc?u=" . urlencode($lineout) . "&r=$streampos");
|
||||||
|
header("Accept-Ranges: bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function do404($error){
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
header("X-Error: $error");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if(!isset($_GET["s"])){
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
header("X-Error: No SOUND(s) provided!");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
include "../data/config.php";
|
||||||
|
include "../lib/curlproxy.php";
|
||||||
|
$proxy = new proxy();
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
$proxy->stream_linear_audio($_GET["s"]);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
header("X-Error: " . $error->getMessage());
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
include "../data/config.php";
|
||||||
|
new spotify();
|
||||||
|
|
||||||
|
class spotify{
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
include "../lib/fuckhtml.php";
|
||||||
|
$this->fuckhtml = new fuckhtml();
|
||||||
|
|
||||||
|
if(
|
||||||
|
!isset($_GET["s"]) ||
|
||||||
|
!preg_match(
|
||||||
|
'/^(track|episode)\.([A-Za-z0-9]{22})$/',
|
||||||
|
$_GET["s"],
|
||||||
|
$matches
|
||||||
|
)
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->do404("The track ID(s) parameter is missing or invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
if($matches[1] == "episode"){
|
||||||
|
|
||||||
|
$uri = "show";
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$uri = $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$embed =
|
||||||
|
$this->get("https://embed.spotify.com/{$uri}/" . $matches[2]);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
$this->do404("Failed to fetch embed data");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fuckhtml->load($embed);
|
||||||
|
|
||||||
|
$json =
|
||||||
|
$this->fuckhtml
|
||||||
|
->getElementById(
|
||||||
|
"__NEXT_DATA__",
|
||||||
|
"script"
|
||||||
|
);
|
||||||
|
|
||||||
|
if($json === null){
|
||||||
|
|
||||||
|
$this->do404("Failed to extract JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
$json =
|
||||||
|
json_decode($json["innerHTML"], true);
|
||||||
|
|
||||||
|
if($json === null){
|
||||||
|
|
||||||
|
$this->do404("Failed to decode JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch($matches[1]){
|
||||||
|
|
||||||
|
case "track":
|
||||||
|
if(
|
||||||
|
isset(
|
||||||
|
$json
|
||||||
|
["props"]
|
||||||
|
["pageProps"]
|
||||||
|
["state"]
|
||||||
|
["data"]
|
||||||
|
["entity"]
|
||||||
|
["audioPreview"]
|
||||||
|
["url"]
|
||||||
|
)
|
||||||
|
){
|
||||||
|
|
||||||
|
header("Content-type: audio/mpeg");
|
||||||
|
header(
|
||||||
|
"Location: /audio/linear?s=" .
|
||||||
|
urlencode(
|
||||||
|
$json
|
||||||
|
["props"]
|
||||||
|
["pageProps"]
|
||||||
|
["state"]
|
||||||
|
["data"]
|
||||||
|
["entity"]
|
||||||
|
["audioPreview"]
|
||||||
|
["url"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$this->do404("Could not extract playback URL");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "episode":
|
||||||
|
if(
|
||||||
|
isset(
|
||||||
|
$json
|
||||||
|
["props"]
|
||||||
|
["pageProps"]
|
||||||
|
["state"]
|
||||||
|
["data"]
|
||||||
|
["entity"]
|
||||||
|
["id"]
|
||||||
|
)
|
||||||
|
){
|
||||||
|
|
||||||
|
try{
|
||||||
|
$json =
|
||||||
|
$this->get(
|
||||||
|
"https://spclient.wg.spotify.com/soundfinder/v1/unauth/episode/" .
|
||||||
|
$json
|
||||||
|
["props"]
|
||||||
|
["pageProps"]
|
||||||
|
["state"]
|
||||||
|
["data"]
|
||||||
|
["entity"]
|
||||||
|
["id"] .
|
||||||
|
"/com.widevine.alpha"
|
||||||
|
);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
$this->do404("Failed to fetch audio resource");
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode($json, true);
|
||||||
|
|
||||||
|
if($json === null){
|
||||||
|
|
||||||
|
$this->do404("Failed to decode audio resource JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
isset($json["passthrough"]) &&
|
||||||
|
$json["passthrough"] == "ALLOWED" &&
|
||||||
|
isset($json["passthroughUrl"])
|
||||||
|
){
|
||||||
|
|
||||||
|
header(
|
||||||
|
"Location:" .
|
||||||
|
"/audio/linear.php?s=" .
|
||||||
|
urlencode(
|
||||||
|
str_replace(
|
||||||
|
"http://",
|
||||||
|
"https://",
|
||||||
|
$json["passthroughUrl"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$this->do404("Failed to find passthroughUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$this->do404("Failed to find episode ID");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get($url){
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
"User-Agent: " . config::USER_AGENT,
|
||||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||||
|
"Accept-Language: en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding: gzip",
|
||||||
|
"DNT: 1",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Upgrade-Insecure-Requests: 1",
|
||||||
|
"Sec-Fetch-Dest: document",
|
||||||
|
"Sec-Fetch-Mode: navigate",
|
||||||
|
"Sec-Fetch-Site: none",
|
||||||
|
"Sec-Fetch-User: ?1"
|
||||||
|
];
|
||||||
|
|
||||||
|
$curlproc = curl_init();
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
|
||||||
|
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
|
||||||
|
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
$data = curl_exec($curlproc);
|
||||||
|
|
||||||
|
if(curl_errno($curlproc)){
|
||||||
|
throw new Exception(curl_error($curlproc));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($curlproc);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function do404($error){
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
header("Content-Type: text/plain");
|
||||||
|
header("X-Error: $error");
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
124
captcha.php
124
captcha.php
|
@ -1,47 +1,104 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
if(
|
if(
|
||||||
!isset($_GET["k"]) ||
|
isset($_GET["v"]) === false ||
|
||||||
|
is_array($_GET["v"]) === true ||
|
||||||
preg_match(
|
preg_match(
|
||||||
'/^c\.[0-9]+$/',
|
'/^c[0-9]+\.[A-Za-z0-9_]{20}$/',
|
||||||
$_GET["k"]
|
$_GET["v"]
|
||||||
)
|
) === 0
|
||||||
){
|
){
|
||||||
|
|
||||||
|
http_response_code(401);
|
||||||
header("Content-Type: text/plain");
|
header("Content-Type: text/plain");
|
||||||
echo "Fuck you";
|
echo "Fuck my feathered cloaca";
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
header("Content-Type: image/jpeg");
|
//header("Content-Type: image/jpeg");
|
||||||
|
include "data/config.php";
|
||||||
|
|
||||||
$grid = apcu_fetch($_GET["k"]);
|
if(config::BOT_PROTECTION !== 1){
|
||||||
|
|
||||||
if(
|
header("Content-Type: text/plain");
|
||||||
$grid === false ||
|
echo "The IQ test is disabled";
|
||||||
$grid[3] === true // has already been generated
|
die();
|
||||||
){
|
}
|
||||||
|
|
||||||
|
$grid = apcu_fetch($_GET["v"]);
|
||||||
|
|
||||||
|
if($grid !== false){
|
||||||
|
|
||||||
|
// captcha already generated
|
||||||
http_response_code(304); // not modified
|
http_response_code(304); // not modified
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header("Content-Type: image/jpeg");
|
||||||
header("Last-Modified: Thu, 01 Oct 1970 00:00:00 GMT");
|
header("Last-Modified: Thu, 01 Oct 1970 00:00:00 GMT");
|
||||||
|
|
||||||
// only generate one captcha with this config
|
// ** generate captcha data
|
||||||
|
// get the positions for the answers
|
||||||
|
// will return between 3 and 6 answer positions
|
||||||
|
$range = range(0, 15);
|
||||||
|
$answer_pos = [];
|
||||||
|
|
||||||
|
array_splice($range, 0, 1);
|
||||||
|
|
||||||
|
$picks = random_int(3, 6);
|
||||||
|
|
||||||
|
for($i=0; $i<$picks; $i++){
|
||||||
|
|
||||||
|
$answer_pos_tmp =
|
||||||
|
array_splice(
|
||||||
|
$range,
|
||||||
|
random_int(
|
||||||
|
0,
|
||||||
|
14 - $i
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
$answer_pos[] = $answer_pos_tmp[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose a dataset
|
||||||
|
$c = count(config::CAPTCHA_DATASET);
|
||||||
|
$choosen = config::CAPTCHA_DATASET[random_int(0, $c - 1)];
|
||||||
|
$choices = [];
|
||||||
|
|
||||||
|
for($i=0; $i<$c; $i++){
|
||||||
|
|
||||||
|
if(config::CAPTCHA_DATASET[$i][0] == $choosen[0]){
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$choices[] = config::CAPTCHA_DATASET[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate grid data
|
||||||
|
$grid = [];
|
||||||
|
|
||||||
|
for($i=0; $i<16; $i++){
|
||||||
|
|
||||||
|
if(in_array($i, $answer_pos)){
|
||||||
|
|
||||||
|
$grid[] = $choosen;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$grid[] = $choices[random_int(0, count($choices) - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store grid data for form validation on captcha_gen.php
|
||||||
apcu_store(
|
apcu_store(
|
||||||
$_GET["k"],
|
$_GET["v"],
|
||||||
[
|
$answer_pos,
|
||||||
$grid[0],
|
60 // we give user 1 minute to solve
|
||||||
$grid[1],
|
|
||||||
$grid[2],
|
|
||||||
true // has captcha been generated?
|
|
||||||
],
|
|
||||||
120 // we give user another 2 minutes to solve
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate image
|
// generate image
|
||||||
|
|
||||||
if(random_int(0,1) === 0){
|
if(random_int(0,1) === 0){
|
||||||
|
|
||||||
$theme = [
|
$theme = [
|
||||||
|
@ -57,7 +114,7 @@ if(random_int(0,1) === 0){
|
||||||
}
|
}
|
||||||
|
|
||||||
$im = new Imagick();
|
$im = new Imagick();
|
||||||
$im->newImage(400, 400, $theme["bg"]);
|
$im->newImage(400, 427, $theme["bg"]);
|
||||||
$im->setImageBackgroundColor($theme["bg"]);
|
$im->setImageBackgroundColor($theme["bg"]);
|
||||||
$im->setImageFormat("jpg");
|
$im->setImageFormat("jpg");
|
||||||
|
|
||||||
|
@ -76,12 +133,18 @@ for($y=0; $y<4; $y++){
|
||||||
|
|
||||||
for($x=0; $x<4; $x++){
|
for($x=0; $x<4; $x++){
|
||||||
|
|
||||||
$tmp = new Imagick("./data/captcha/" . $grid[0][$i][0] . "/" . random_int(1, $grid[0][$i][1]) . ".png");
|
$tmp = new Imagick("./data/captcha/" . $grid[$i][0] . "/" . random_int(1, $grid[$i][1]) . ".png");
|
||||||
|
|
||||||
// convert transparency correctly
|
// convert transparency correctly
|
||||||
$tmp->setImageBackgroundColor("black");
|
$tmp->setImageBackgroundColor("black");
|
||||||
$tmp->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
|
$tmp->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
|
||||||
|
|
||||||
|
// randomly mirror
|
||||||
|
if(random_int(0,1) === 1){
|
||||||
|
|
||||||
|
$tmp->flopImage();
|
||||||
|
}
|
||||||
|
|
||||||
// distort $tmp
|
// distort $tmp
|
||||||
$tmp->distortImage(
|
$tmp->distortImage(
|
||||||
$distort[random_int(0,1)],
|
$distort[random_int(0,1)],
|
||||||
|
@ -101,21 +164,15 @@ for($y=0; $y<4; $y++){
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$tmp->addNoiseImage($noise[random_int(0, 1)]);
|
||||||
|
|
||||||
// append image
|
// append image
|
||||||
$im->compositeImage($tmp->getImage(), Imagick::COMPOSITE_DEFAULT, $x * 100, $y * 100);
|
$im->compositeImage($tmp->getImage(), Imagick::COMPOSITE_DEFAULT, $x * 100, ($y * 100) + 27);
|
||||||
|
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add noise
|
|
||||||
$im->addNoiseImage($noise[random_int(0, 1)]);
|
|
||||||
|
|
||||||
// expand top of image
|
|
||||||
$im->setImageGravity(Imagick::GRAVITY_SOUTH);
|
|
||||||
$im->chopImage(0, -27, 400, 400);
|
|
||||||
$im->extentImage(0, 0, 0, -27);
|
|
||||||
|
|
||||||
// add text
|
// add text
|
||||||
$draw = new ImagickDraw();
|
$draw = new ImagickDraw();
|
||||||
$draw->setFontSize(20);
|
$draw->setFontSize(20);
|
||||||
|
@ -123,7 +180,7 @@ $draw->setFillColor($theme["fg"]);
|
||||||
//$draw->setTextAntialias(false);
|
//$draw->setTextAntialias(false);
|
||||||
$draw->setFont("./data/captcha/font.ttf");
|
$draw->setFont("./data/captcha/font.ttf");
|
||||||
|
|
||||||
$text = "Pick " . $grid[1] . " images of " . str_replace("_", " ", $grid[2]);
|
$text = "Pick " . $picks . " images of " . str_replace("_", " ", $choosen[0]);
|
||||||
|
|
||||||
$pos = 200 - ($im->queryFontMetrics($draw, $text)["textWidth"] / 2);
|
$pos = 200 - ($im->queryFontMetrics($draw, $text)["textWidth"] / 2);
|
||||||
|
|
||||||
|
@ -143,5 +200,4 @@ for($i=0; $i<strlen($text); $i++){
|
||||||
|
|
||||||
$im->setFormat("jpeg");
|
$im->setFormat("jpeg");
|
||||||
$im->setImageCompressionQuality(90);
|
$im->setImageCompressionQuality(90);
|
||||||
$im->setImageCompression(Imagick::COMPRESSION_JPEG2000);
|
|
||||||
echo $im->getImageBlob();
|
echo $im->getImageBlob();
|
||||||
|
|
|
@ -5,7 +5,7 @@ class config{
|
||||||
// any parameters.
|
// any parameters.
|
||||||
|
|
||||||
// 4get version. Please keep this updated
|
// 4get version. Please keep this updated
|
||||||
const VERSION = 6;
|
const VERSION = 7;
|
||||||
|
|
||||||
// Will be shown pretty much everywhere.
|
// Will be shown pretty much everywhere.
|
||||||
const SERVER_NAME = "4get";
|
const SERVER_NAME = "4get";
|
||||||
|
@ -24,10 +24,10 @@ class config{
|
||||||
const API_ENABLED = true;
|
const API_ENABLED = true;
|
||||||
|
|
||||||
// Bot protection
|
// Bot protection
|
||||||
// 4get.ca has been hit with 250k bot reqs every single day for months
|
// 4get.ca has been hit with 500k bot reqs every single day for months
|
||||||
// you probably want to enable this if your instance is public...
|
// you probably want to enable this if your instance is public...
|
||||||
// 0 = disabled
|
// 0 = disabled
|
||||||
// 1 = ask for image captcha (requires image dataset & imagick 6.9.11-60)
|
// 1 = ask for image captcha (requires imagemagick v6 or higher)
|
||||||
// @TODO: 2 = invite only (users needs a pass)
|
// @TODO: 2 = invite only (users needs a pass)
|
||||||
const BOT_PROTECTION = 0;
|
const BOT_PROTECTION = 0;
|
||||||
|
|
||||||
|
@ -62,20 +62,27 @@ class config{
|
||||||
"https://4get.zzls.xyz",
|
"https://4get.zzls.xyz",
|
||||||
"https://4getus.zzls.xyz",
|
"https://4getus.zzls.xyz",
|
||||||
"https://4get.silly.computer",
|
"https://4get.silly.computer",
|
||||||
"https://4g.opnxng.com",
|
|
||||||
"https://4get.konakona.moe",
|
"https://4get.konakona.moe",
|
||||||
"https://4get.lvkaszus.pl",
|
"https://4get.lvkaszus.pl",
|
||||||
"https://4g.ggtyler.dev",
|
"https://4g.ggtyler.dev",
|
||||||
"https://4get.perennialte.ch",
|
"https://4get.perennialte.ch",
|
||||||
"https://4get.sihj.net",
|
"https://4get.sijh.net",
|
||||||
"https://4get.hbubli.cc",
|
"https://4get.hbubli.cc",
|
||||||
"https://4get.plunked.party",
|
"https://4get.plunked.party",
|
||||||
"https://4get.seitan-ayoub.lol"
|
"https://4get.seitan-ayoub.lol",
|
||||||
|
"https://4get.etenie.pl",
|
||||||
|
"https://4get.lunar.icu",
|
||||||
|
"https://4get.dcs0.hu",
|
||||||
|
"https://4get.kizuki.lol",
|
||||||
|
"https://4get.psily.garden",
|
||||||
|
"https://search.milivojevic.in.rs",
|
||||||
|
"https://4get.snine.nl",
|
||||||
|
"https://4get.datura.network"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Default user agent to use for scraper requests. Sometimes ignored to get specific webpages
|
// Default user agent to use for scraper requests. Sometimes ignored to get specific webpages
|
||||||
// Changing this might break things.
|
// Changing this might break things.
|
||||||
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/120.0";
|
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0";
|
||||||
|
|
||||||
// Proxy pool assignments for each scraper
|
// Proxy pool assignments for each scraper
|
||||||
// false = Use server's raw IP
|
// false = Use server's raw IP
|
||||||
|
@ -94,6 +101,8 @@ class config{
|
||||||
const PROXY_YT = false; // youtube
|
const PROXY_YT = false; // youtube
|
||||||
const PROXY_YEP = false;
|
const PROXY_YEP = false;
|
||||||
const PROXY_PINTEREST = false;
|
const PROXY_PINTEREST = false;
|
||||||
|
const PROXY_SEZNAM = false;
|
||||||
|
const PROXY_NAVER = false;
|
||||||
const PROXY_FTM = false; // findthatmeme
|
const PROXY_FTM = false; // findthatmeme
|
||||||
const PROXY_IMGUR = false;
|
const PROXY_IMGUR = false;
|
||||||
const PROXY_YANDEX_W = false; // yandex web
|
const PROXY_YANDEX_W = false; // yandex web
|
||||||
|
@ -107,8 +116,8 @@ class config{
|
||||||
// SOUNDCLOUD
|
// SOUNDCLOUD
|
||||||
// Get these parameters by making a search on soundcloud with network
|
// Get these parameters by making a search on soundcloud with network
|
||||||
// tab open, then filter URLs using "search?q=". (No need to login)
|
// tab open, then filter URLs using "search?q=". (No need to login)
|
||||||
const SC_USER_ID = "361066-632137-891392-693457";
|
const SC_USER_ID = "59333-426459-717969-168008";
|
||||||
const SC_CLIENT_TOKEN = "nUB9ZvnjRiqKF43CkKf3iu69D8bboyKY";
|
const SC_CLIENT_TOKEN = "8BBZpqUP1KSN4W6YB64xog2PX4Dw98b1";
|
||||||
|
|
||||||
// MARGINALIA
|
// MARGINALIA
|
||||||
// Get an API key by contacting the Marginalia.nu maintainer. The "public" key
|
// Get an API key by contacting the Marginalia.nu maintainer. The "public" key
|
||||||
|
|
|
@ -15,10 +15,11 @@ $get = $frontend->parsegetfilters($_GET, $filters);
|
||||||
/*
|
/*
|
||||||
Captcha
|
Captcha
|
||||||
*/
|
*/
|
||||||
include "lib/captcha_gen.php";
|
include "lib/bot_protection.php";
|
||||||
new captcha($frontend, $get, $filters, "images", true);
|
new bot_protection($frontend, $get, $filters, "images", true);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"images" => "",
|
"images" => "",
|
||||||
"nextpage" => ""
|
"nextpage" => ""
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class captcha{
|
class bot_protection{
|
||||||
|
|
||||||
public function __construct($frontend, $get, $filters, $page, $output){
|
public function __construct($frontend, $get, $filters, $page, $output){
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class captcha{
|
||||||
if(
|
if(
|
||||||
// check if key is not malformed
|
// check if key is not malformed
|
||||||
preg_match(
|
preg_match(
|
||||||
'/^c[0-9]+\.[A-Za-z0-9]{20}$/',
|
'/^k[0-9]+\.[A-Za-z0-9_]{20}$/',
|
||||||
$_COOKIE["pass"]
|
$_COOKIE["pass"]
|
||||||
) &&
|
) &&
|
||||||
// does key exist
|
// does key exist
|
||||||
|
@ -39,7 +39,7 @@ class captcha{
|
||||||
// we start counting from 1
|
// we start counting from 1
|
||||||
// when it has been incremented to 102, it has reached
|
// when it has been incremented to 102, it has reached
|
||||||
// 100 reqs
|
// 100 reqs
|
||||||
if($inc >= 102){
|
if($inc >= config::MAX_SEARCHES + 2){
|
||||||
|
|
||||||
// reached limit, delete and give captcha
|
// reached limit, delete and give captcha
|
||||||
apcu_delete($_COOKIE["pass"]);
|
apcu_delete($_COOKIE["pass"]);
|
||||||
|
@ -62,7 +62,7 @@ class captcha{
|
||||||
|
|
||||||
if($output === false){
|
if($output === false){
|
||||||
|
|
||||||
http_response_code(429); // too many reqs
|
http_response_code(401); // forbidden
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"status" => "The \"pass\" token in your cookies is missing or has expired!!"
|
"status" => "The \"pass\" token in your cookies is missing or has expired!!"
|
||||||
]);
|
]);
|
||||||
|
@ -104,10 +104,13 @@ class captcha{
|
||||||
!isset($regex[0][1])
|
!isset($regex[0][1])
|
||||||
){
|
){
|
||||||
|
|
||||||
// check if its k
|
// check if its the v key
|
||||||
if(
|
if(
|
||||||
$line[0] == "k" &&
|
$line[0] == "v" &&
|
||||||
strpos($line[1], "c.") === 0
|
preg_match(
|
||||||
|
'/^c[0-9]+\.[A-Za-z0-9_]{20}$/',
|
||||||
|
$line[1]
|
||||||
|
)
|
||||||
){
|
){
|
||||||
|
|
||||||
$key = apcu_fetch($line[1]);
|
$key = apcu_fetch($line[1]);
|
||||||
|
@ -132,24 +135,18 @@ class captcha{
|
||||||
|
|
||||||
if(
|
if(
|
||||||
!$invalid &&
|
!$invalid &&
|
||||||
$key !== false
|
$key !== false // has captcha been gen'd?
|
||||||
){
|
){
|
||||||
$check = $key[1];
|
$check = count($key);
|
||||||
|
|
||||||
// validate answer
|
// validate answer
|
||||||
for($i=0; $i<count($key[0]); $i++){
|
for($i=0; $i<count($answers); $i++){
|
||||||
|
|
||||||
if(!in_array($i, $answers)){
|
if(in_array($answers[$i], $key)){
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($key[0][$i][0] == $key[2]){
|
|
||||||
|
|
||||||
$check--;
|
$check--;
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
// got a wrong answer
|
|
||||||
$check = -1;
|
$check = -1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -160,21 +157,8 @@ class captcha{
|
||||||
// we passed the captcha
|
// we passed the captcha
|
||||||
// set cookie
|
// set cookie
|
||||||
$inc = apcu_inc("cookie");
|
$inc = apcu_inc("cookie");
|
||||||
$chars =
|
|
||||||
array_merge(
|
|
||||||
range("A", "Z"),
|
|
||||||
range("a", "z"),
|
|
||||||
range(0, 9)
|
|
||||||
);
|
|
||||||
|
|
||||||
$c = count($chars) - 1;
|
$key = "k" . $inc . "." . $this->randomchars();
|
||||||
|
|
||||||
$key = "c" . $inc . ".";
|
|
||||||
|
|
||||||
for($i=0; $i<20; $i++){
|
|
||||||
|
|
||||||
$key .= $chars[random_int(0, $c)];
|
|
||||||
}
|
|
||||||
|
|
||||||
apcu_inc($key, 1, $stupid, 86400);
|
apcu_inc($key, 1, $stupid, 86400);
|
||||||
|
|
||||||
|
@ -203,84 +187,23 @@ class captcha{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the positions for the answers
|
$key = "c" . apcu_inc("captcha_gen", 1) . "." . $this->randomchars();
|
||||||
// will return between 3 and 6 answer positions
|
|
||||||
$range = range(0, 15);
|
|
||||||
$answer_pos = [];
|
|
||||||
|
|
||||||
array_splice($range, 0, 1);
|
|
||||||
|
|
||||||
for($i=0; $i<random_int(3, 6); $i++){
|
|
||||||
|
|
||||||
$answer_pos_tmp =
|
|
||||||
array_splice(
|
|
||||||
$range,
|
|
||||||
random_int(
|
|
||||||
0,
|
|
||||||
14 - $i
|
|
||||||
),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
$answer_pos[] = $answer_pos_tmp[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// choose a dataset
|
|
||||||
$c = count(config::CAPTCHA_DATASET);
|
|
||||||
$choosen = config::CAPTCHA_DATASET[random_int(0, $c - 1)];
|
|
||||||
$choices = [];
|
|
||||||
|
|
||||||
for($i=0; $i<$c; $i++){
|
|
||||||
|
|
||||||
if(config::CAPTCHA_DATASET[$i][0] == $choosen[0]){
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$choices[] = config::CAPTCHA_DATASET[$i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate grid data
|
|
||||||
$grid = [];
|
|
||||||
|
|
||||||
for($i=0; $i<16; $i++){
|
|
||||||
|
|
||||||
if(in_array($i, $answer_pos)){
|
|
||||||
|
|
||||||
$grid[] = $choosen;
|
|
||||||
}else{
|
|
||||||
|
|
||||||
$grid[] = $choices[random_int(0, count($choices) - 1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = "c." . apcu_inc("captcha_gen", 1) . "." . random_int(0, 100000000);
|
|
||||||
|
|
||||||
apcu_store(
|
|
||||||
$key,
|
|
||||||
[
|
|
||||||
$grid,
|
|
||||||
count($answer_pos),
|
|
||||||
$choosen[0],
|
|
||||||
false // has captcha been generated?
|
|
||||||
],
|
|
||||||
120 // we give user 2 minutes to get captcha, in case of network error
|
|
||||||
);
|
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
"left" =>
|
"left" =>
|
||||||
'<div class="infobox">' .
|
'<div class="infobox">' .
|
||||||
'<h1>IQ test</h1>' .
|
'<h1>IQ test</h1>' .
|
||||||
'Due to getting hit with 20,000 bot requests per day, I had to put this up. Sorry.<br><br>' .
|
'IQ test has been enabled due to bot abuse on the network.<br>' .
|
||||||
'Solving this captcha will allow you to make 100 searches today. I will add a way for legit users to bypass the captcha later. Sorry /g/tards!!' .
|
'Solving this IQ test will let you make 100 searches today. I will add an invite system to bypass this soon...' .
|
||||||
$error .
|
$error .
|
||||||
'<form method="POST" enctype="text/plain" autocomplete="off">' .
|
'<form method="POST" enctype="text/plain" autocomplete="off">' .
|
||||||
'<div class="captcha-wrapper">' .
|
'<div class="captcha-wrapper">' .
|
||||||
'<div class="captcha">' .
|
'<div class="captcha">' .
|
||||||
'<img src="captcha?k=' . $key . '" alt="Captcha image">' .
|
'<img src="captcha.php?v=' . $key . '" alt="Captcha image">' .
|
||||||
'<div class="captcha-controls">' .
|
'<div class="captcha-controls">' .
|
||||||
'<input type="checkbox" name="c[0]" id="c0">' .
|
'<input type="checkbox" name="c[0]" id="c0">' .
|
||||||
'<label for="c0"></label>' .
|
'<label for="c0"></label>' .
|
||||||
|
@ -317,13 +240,12 @@ class captcha{
|
||||||
'</div>' .
|
'</div>' .
|
||||||
'</div>' .
|
'</div>' .
|
||||||
'</div>' .
|
'</div>' .
|
||||||
'<input type="hidden" name="k" value="' . $key . '">' .
|
'<input type="hidden" name="v" value="' . $key . '">' .
|
||||||
'<input type="submit" value="Check IQ" class="captcha-submit">' .
|
'<input type="submit" value="Check IQ" class="captcha-submit">' .
|
||||||
'</form>' .
|
'</form>' .
|
||||||
'</div>'
|
'</div>'
|
||||||
];
|
];
|
||||||
|
|
||||||
http_response_code(429); // too many reqs
|
|
||||||
$frontend->loadheader(
|
$frontend->loadheader(
|
||||||
$get,
|
$get,
|
||||||
$filters,
|
$filters,
|
||||||
|
@ -333,4 +255,27 @@ class captcha{
|
||||||
echo $frontend->load("search.html", $payload);
|
echo $frontend->load("search.html", $payload);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function randomchars(){
|
||||||
|
|
||||||
|
$chars =
|
||||||
|
array_merge(
|
||||||
|
range("A", "Z"),
|
||||||
|
range("a", "z"),
|
||||||
|
range(0, 9)
|
||||||
|
);
|
||||||
|
|
||||||
|
$chars[] = "_";
|
||||||
|
|
||||||
|
$c = count($chars) - 1;
|
||||||
|
|
||||||
|
$key = "";
|
||||||
|
|
||||||
|
for($i=0; $i<20; $i++){
|
||||||
|
|
||||||
|
$key .= $chars[random_int(0, $c)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
}
|
}
|
BIN
lib/classic.png
BIN
lib/classic.png
Binary file not shown.
Before Width: | Height: | Size: 358 B |
|
@ -39,6 +39,14 @@ class frontend{
|
||||||
$replacements["ac"] = '';
|
$replacements["ac"] = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
isset($replacements["timetaken"]) &&
|
||||||
|
$replacements["timetaken"] !== null
|
||||||
|
){
|
||||||
|
|
||||||
|
$replacements["timetaken"] = '<div class="timetaken">Took ' . substr(microtime(true) - $replacements["timetaken"], 0, 4) . 's</div>';
|
||||||
|
}
|
||||||
|
|
||||||
$handle = fopen("template/{$template}", "r");
|
$handle = fopen("template/{$template}", "r");
|
||||||
$data = fread($handle, filesize("template/{$template}"));
|
$data = fread($handle, filesize("template/{$template}"));
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
@ -68,7 +76,7 @@ class frontend{
|
||||||
|
|
||||||
echo
|
echo
|
||||||
$this->load("header.html", [
|
$this->load("header.html", [
|
||||||
"title" => trim($get["s"] . " ({$page})"),
|
"title" => trim(htmlspecialchars($get["s"]) . " ({$page})"),
|
||||||
"description" => ucfirst($page) . ' search results for "' . htmlspecialchars($get["s"]) . '"',
|
"description" => ucfirst($page) . ' search results for "' . htmlspecialchars($get["s"]) . '"',
|
||||||
"index" => "no",
|
"index" => "no",
|
||||||
"search" => htmlspecialchars($get["s"]),
|
"search" => htmlspecialchars($get["s"]),
|
||||||
|
@ -88,7 +96,7 @@ class frontend{
|
||||||
|
|
||||||
$this->drawerror(
|
$this->drawerror(
|
||||||
"Tshh, blocked!",
|
"Tshh, blocked!",
|
||||||
'You were blocked from viewing this page. If you wish to scrape data from 4get, please consider running <a href="https://git.lolcat.ca/lolcat/4get" rel="noreferrer nofollow">your own 4get instance</a> or using <a href="/api.txt">the API</a>.',
|
'You were blocked from viewing this page. If you wish to scrape data from 4get, please consider running <a href="https://git.lolcat.ca/lolcat/4get" rel="noreferrer nofollow">your own 4get instance</a>.',
|
||||||
);
|
);
|
||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
|
@ -98,6 +106,7 @@ class frontend{
|
||||||
|
|
||||||
echo
|
echo
|
||||||
$this->load("search.html", [
|
$this->load("search.html", [
|
||||||
|
"timetaken" => null,
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
|
|
|
@ -466,19 +466,26 @@ class fuckhtml{
|
||||||
|
|
||||||
return
|
return
|
||||||
preg_replace_callback(
|
preg_replace_callback(
|
||||||
'/\\\u[A-Fa-f0-9]{4}|\\\x[A-Fa-f0-9]{2}/',
|
'/\\\u[A-Fa-f0-9]{4}|\\\x[A-Fa-f0-9]{2}|\\\n|\\\r/',
|
||||||
function($match){
|
function($match){
|
||||||
|
|
||||||
if($match[0][1] == "u"){
|
switch($match[0][1]){
|
||||||
|
|
||||||
|
case "u":
|
||||||
return json_decode('"' . $match[0] . '"');
|
return json_decode('"' . $match[0] . '"');
|
||||||
}else{
|
break;
|
||||||
|
|
||||||
|
case "x":
|
||||||
return mb_convert_encoding(
|
return mb_convert_encoding(
|
||||||
stripcslashes($match[0]),
|
stripcslashes($match[0]),
|
||||||
"utf-8",
|
"utf-8",
|
||||||
"windows-1252"
|
"windows-1252"
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return " ";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
$string
|
$string
|
||||||
|
|
45
music.php
45
music.php
|
@ -15,10 +15,11 @@ $get = $frontend->parsegetfilters($_GET, $filters);
|
||||||
/*
|
/*
|
||||||
Captcha
|
Captcha
|
||||||
*/
|
*/
|
||||||
include "lib/captcha_gen.php";
|
include "lib/bot_protection.php";
|
||||||
new captcha($frontend, $get, $filters, "music", true);
|
new bot_protection($frontend, $get, $filters, "music", true);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
|
@ -36,7 +37,10 @@ try{
|
||||||
$categories = [
|
$categories = [
|
||||||
"song" => "",
|
"song" => "",
|
||||||
"author" => "",
|
"author" => "",
|
||||||
"playlist" => ""
|
"playlist" => "",
|
||||||
|
"album" => "",
|
||||||
|
"podcast" => "",
|
||||||
|
"user" => ""
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -48,14 +52,26 @@ if(count($results["song"]) !== 0){
|
||||||
|
|
||||||
$main = "song";
|
$main = "song";
|
||||||
|
|
||||||
}elseif(count($results["author"]) !== 0){
|
}elseif(count($results["album"]) !== 0){
|
||||||
|
|
||||||
$main = "author";
|
$main = "album";
|
||||||
|
|
||||||
}elseif(count($results["playlist"]) !== 0){
|
}elseif(count($results["playlist"]) !== 0){
|
||||||
|
|
||||||
$main = "playlist";
|
$main = "playlist";
|
||||||
|
|
||||||
|
}elseif(count($results["podcast"]) !== 0){
|
||||||
|
|
||||||
|
$main = "podcast";
|
||||||
|
|
||||||
|
}elseif(count($results["author"]) !== 0){
|
||||||
|
|
||||||
|
$main = "author";
|
||||||
|
|
||||||
|
}elseif(count($results["user"]) !== 0){
|
||||||
|
|
||||||
|
$main = "user";
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
// No results found!
|
// No results found!
|
||||||
|
@ -133,12 +149,15 @@ foreach($categories as $name => $data){
|
||||||
$customhtml = null;
|
$customhtml = null;
|
||||||
|
|
||||||
if(
|
if(
|
||||||
$name == "song" &&
|
(
|
||||||
|
$name == "song" ||
|
||||||
|
$name == "podcast"
|
||||||
|
) &&
|
||||||
$item["stream"]["endpoint"] !== null
|
$item["stream"]["endpoint"] !== null
|
||||||
){
|
){
|
||||||
|
|
||||||
$customhtml =
|
$customhtml =
|
||||||
'<audio src="' . $item["stream"]["endpoint"] . '?s=' . urlencode($item["stream"]["url"]) . '" controls autostart="false" preload="none">';
|
'<audio src="/audio/' . $item["stream"]["endpoint"] . '?s=' . urlencode($item["stream"]["url"]) . '" controls autostart="false" preload="none">';
|
||||||
}
|
}
|
||||||
|
|
||||||
$categories[$name] .= $frontend->drawtextresult($item, $greentext, $duration, $get["s"], $tabindex, $customhtml);
|
$categories[$name] .= $frontend->drawtextresult($item, $greentext, $duration, $get["s"], $tabindex, $customhtml);
|
||||||
|
@ -177,18 +196,8 @@ foreach($categories as $name => $value){
|
||||||
'<div class="answer-title">' .
|
'<div class="answer-title">' .
|
||||||
'<a class="answer-title" href="?s=' . urlencode($get["s"]);
|
'<a class="answer-title" href="?s=' . urlencode($get["s"]);
|
||||||
|
|
||||||
switch($name){
|
|
||||||
|
|
||||||
case "playlist":
|
|
||||||
$payload[$write] .=
|
$payload[$write] .=
|
||||||
'&type=playlist"><h2>Playlists</h2></a>';
|
'&type=' . $name . '"><h2>' . ucfirst($name) . 's</h2></a>';
|
||||||
break;
|
|
||||||
|
|
||||||
case "author":
|
|
||||||
$payload[$write] .=
|
|
||||||
'&type=people"><h2>Authors</h2></a>';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payload[$write] .=
|
$payload[$write] .=
|
||||||
'</div>' .
|
'</div>' .
|
||||||
|
|
5
news.php
5
news.php
|
@ -15,10 +15,11 @@ $get = $frontend->parsegetfilters($_GET, $filters);
|
||||||
/*
|
/*
|
||||||
Captcha
|
Captcha
|
||||||
*/
|
*/
|
||||||
include "lib/captcha_gen.php";
|
include "lib/bot_protection.php";
|
||||||
new captcha($frontend, $get, $filters, "news", true);
|
new bot_protection($frontend, $get, $filters, "news", true);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
|
|
|
@ -5,7 +5,7 @@ include "data/config.php";
|
||||||
|
|
||||||
$domain =
|
$domain =
|
||||||
htmlspecialchars(
|
htmlspecialchars(
|
||||||
(strpos(strtolower($_SERVER['SERVER_PROTOCOL']), 'https') === false ? 'http' : 'https') .
|
((isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on" || $_SERVER["HTTPS"] === 1)) ? "https" : "http") .
|
||||||
'://' . $_SERVER["HTTP_HOST"]
|
'://' . $_SERVER["HTTP_HOST"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -602,20 +602,23 @@ class mojeek{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data["date"] =
|
$date =
|
||||||
explode(
|
|
||||||
" - ",
|
|
||||||
$this->fuckhtml
|
$this->fuckhtml
|
||||||
->getTextContent(
|
->getElementsByClassName(
|
||||||
$this->fuckhtml
|
"mdate",
|
||||||
->getElementsByClassName("i", "p")[0]
|
"span"
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if(count($date) !== 0){
|
||||||
|
|
||||||
$data["date"] =
|
$data["date"] =
|
||||||
strtotime(
|
strtotime(
|
||||||
$data["date"][count($data["date"]) - 1]
|
$this->fuckhtml
|
||||||
|
->getTextContent(
|
||||||
|
$date[0]
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$out["web"][] = $data;
|
$out["web"][] = $data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class sc{
|
||||||
"option" => [
|
"option" => [
|
||||||
"any" => "Any type",
|
"any" => "Any type",
|
||||||
"track" => "Tracks",
|
"track" => "Tracks",
|
||||||
"people" => "People",
|
"author" => "People",
|
||||||
"album" => "Albums",
|
"album" => "Albums",
|
||||||
"playlist" => "Playlists",
|
"playlist" => "Playlists",
|
||||||
"goplus" => "Go+ Tracks"
|
"goplus" => "Go+ Tracks"
|
||||||
|
@ -143,7 +143,7 @@ class sc{
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "people":
|
case "author":
|
||||||
$url = "https://api-v2.soundcloud.com/search/users";
|
$url = "https://api-v2.soundcloud.com/search/users";
|
||||||
$params = [
|
$params = [
|
||||||
"q" => $search,
|
"q" => $search,
|
||||||
|
@ -237,7 +237,10 @@ class sc{
|
||||||
"npt" => null,
|
"npt" => null,
|
||||||
"song" => [],
|
"song" => [],
|
||||||
"playlist" => [],
|
"playlist" => [],
|
||||||
"author" => []
|
"album" => [],
|
||||||
|
"podcast" => [],
|
||||||
|
"author" => [],
|
||||||
|
"user" => []
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -346,7 +349,7 @@ class sc{
|
||||||
if(stripos($item["monetization_model"], "TIER") === false){
|
if(stripos($item["monetization_model"], "TIER") === false){
|
||||||
|
|
||||||
$stream = [
|
$stream = [
|
||||||
"endpoint" => "audio_sc",
|
"endpoint" => "sc",
|
||||||
"url" =>
|
"url" =>
|
||||||
$item["media"]["transcodings"][0]["url"] .
|
$item["media"]["transcodings"][0]["url"] .
|
||||||
"?client_id=" . config::SC_CLIENT_TOKEN .
|
"?client_id=" . config::SC_CLIENT_TOKEN .
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,726 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class spotify{
|
||||||
|
|
||||||
|
private const req_web = 0;
|
||||||
|
private const req_api = 1;
|
||||||
|
private const req_clientid = 2;
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
include "lib/backend.php";
|
||||||
|
$this->backend = new backend("spotify");
|
||||||
|
|
||||||
|
include "lib/fuckhtml.php";
|
||||||
|
$this->fuckhtml = new fuckhtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getfilters($page){
|
||||||
|
|
||||||
|
return [
|
||||||
|
"category" => [
|
||||||
|
"display" => "Category",
|
||||||
|
"option" => [
|
||||||
|
"any" => "All (no pagination)",
|
||||||
|
"audiobooks" => "Audiobooks",
|
||||||
|
"tracks" => "Songs",
|
||||||
|
"artists" => "Artists",
|
||||||
|
"playlists" => "Playlists",
|
||||||
|
"albums" => "Albums",
|
||||||
|
"podcastAndEpisodes" => "Podcasts & Shows (no pagination)",
|
||||||
|
"episodes" => "Episodes",
|
||||||
|
"users" => "Profiles"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get($proxy, $url, $get = [], $reqtype = self::req_web, $bearer = null, $token = null){
|
||||||
|
|
||||||
|
$curlproc = curl_init();
|
||||||
|
|
||||||
|
switch($reqtype){
|
||||||
|
|
||||||
|
case self::req_api:
|
||||||
|
$headers = [
|
||||||
|
"User-Agent: " . config::USER_AGENT,
|
||||||
|
"Accept: application/json",
|
||||||
|
"Accept-Language: en",
|
||||||
|
"app-platform: WebPlayer",
|
||||||
|
"authorization: Bearer {$bearer}",
|
||||||
|
"client-token: {$token}",
|
||||||
|
"content-type: application/json;charset=UTF-8",
|
||||||
|
"Origin: https://open.spotify.com",
|
||||||
|
"Referer: https://open.spotify.com/",
|
||||||
|
"DNT: 1",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Sec-Fetch-Dest: empty",
|
||||||
|
"Sec-Fetch-Mode: cors",
|
||||||
|
"Sec-Fetch-Site: same-site",
|
||||||
|
"spotify-app-version: 1.2.27.93.g7aee53d4",
|
||||||
|
"TE: trailers"
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::req_web:
|
||||||
|
$headers = [
|
||||||
|
"User-Agent: " . config::USER_AGENT,
|
||||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||||
|
"Accept-Language: en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding: gzip",
|
||||||
|
"DNT: 1",
|
||||||
|
"Sec-GPC: 1",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Upgrade-Insecure-Requests: 1",
|
||||||
|
"Sec-Fetch-Dest: document",
|
||||||
|
"Sec-Fetch-Mode: navigate",
|
||||||
|
"Sec-Fetch-Site: cross-site"
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case self::req_clientid:
|
||||||
|
$get = json_encode($get);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_POST, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
"User-Agent:" . config::USER_AGENT,
|
||||||
|
"Accept: application/json",
|
||||||
|
"Accept-Language: en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding: gzip, deflate, br",
|
||||||
|
"Referer: https://open.spotify.com/",
|
||||||
|
"content-type: application/json",
|
||||||
|
"Content-Length: " . strlen($get),
|
||||||
|
"Origin: https://open.spotify.com",
|
||||||
|
"DNT: 1",
|
||||||
|
"Sec-GPC: 1",
|
||||||
|
"Connection: keep-alive",
|
||||||
|
"Sec-Fetch-Dest: empty",
|
||||||
|
"Sec-Fetch-Mode: cors",
|
||||||
|
"Sec-Fetch-Site: same-site",
|
||||||
|
"TE: trailers"
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($reqtype !== self::req_clientid){
|
||||||
|
if($get !== []){
|
||||||
|
$get = http_build_query($get);
|
||||||
|
$url .= "?" . $get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_URL, $url);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
|
||||||
|
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
|
||||||
|
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
|
||||||
|
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
|
||||||
|
|
||||||
|
$this->backend->assign_proxy($curlproc, $proxy);
|
||||||
|
|
||||||
|
$data = curl_exec($curlproc);
|
||||||
|
|
||||||
|
if(curl_errno($curlproc)){
|
||||||
|
throw new Exception(curl_error($curlproc));
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($curlproc);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function music($get){
|
||||||
|
|
||||||
|
$search = $get["s"];
|
||||||
|
$ip = $this->backend->get_ip();
|
||||||
|
$category = $get["category"];
|
||||||
|
|
||||||
|
/*
|
||||||
|
audiobooks first and second page decoded
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAudiobooks&variables={"searchTerm":"freddie+dredd","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"8758e540afdba5afa3c5246817f6bd31d86a15b3f5666c363dd017030f35d785"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAudiobooks&variables={"searchTerm":"freddie+dredd","offset":30,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"8758e540afdba5afa3c5246817f6bd31d86a15b3f5666c363dd017030f35d785"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
songs
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchTracks&variables={"searchTerm":"asmr","offset":0,"limit":100,"numberOfTopResults":20,"includeAudiobooks":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"16c02d6304f5f721fc2eb39dacf2361a4543815112506a9c05c9e0bc9733a679"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchTracks&variables={"searchTerm":"asmr","offset":100,"limit":100,"numberOfTopResults":20,"includeAudiobooks":false}&extensions={"persistedQuery":{"version":1,"sha256Hash":"16c02d6304f5f721fc2eb39dacf2361a4543815112506a9c05c9e0bc9733a679"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
artists
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":30,"limit":23,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchArtists&variables={"searchTerm":"asmr","offset":53,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"b8840daafdda9a9ceadb7c5774731f63f9eca100445d2d94665f2dc58b45e2b9"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
playlists
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchPlaylists&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"19b4143a0500ccec189ca0f4a0316bc2c615ecb51ce993ba4d7d08afd1d87aa4"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchPlaylists&variables={"searchTerm":"asmr","offset":30,"limit":3,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"19b4143a0500ccec189ca0f4a0316bc2c615ecb51ce993ba4d7d08afd1d87aa4"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
albums
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAlbums&variables={"searchTerm":"asmr","offset":33,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"e93b13cda461482da2940467eb2beed9368e9bb2fff37df3fb6633fc61271a27"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchAlbums&variables={"searchTerm":"asmr","offset":33,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"e93b13cda461482da2940467eb2beed9368e9bb2fff37df3fb6633fc61271a27"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
podcasts & shows (contains authors, no pagination)
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchFullEpisodes&variables={"searchTerm":"asmr","offset":0,"limit":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"9f996251c9781fabce63f1a9980b5287ea33bc5e8c8953d0c4689b09936067a1"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
episodes
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchDesktop&variables={"searchTerm":"asmr","offset":0,"limit":10,"numberOfTopResults":5,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"da03293d92a2cfc5e24597dcdc652c0ad135e1c64a78fddbf1478a7e096bea44"}}
|
||||||
|
??? https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchFullEpisodes&variables={"searchTerm":"asmr","offset":60,"limit":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"9f996251c9781fabce63f1a9980b5287ea33bc5e8c8953d0c4689b09936067a1"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
profiles
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchUsers&variables={"searchTerm":"asmr","offset":0,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"02026f48ab5001894e598904079b620ebc64f2d53b55ca20c3858abd3a46c5fb"}}
|
||||||
|
https://api-partner.spotify.com/pathfinder/v1/query?operationName=searchUsers&variables={"searchTerm":"asmr","offset":30,"limit":30,"numberOfTopResults":20,"includeAudiobooks":true}&extensions={"persistedQuery":{"version":1,"sha256Hash":"02026f48ab5001894e598904079b620ebc64f2d53b55ca20c3858abd3a46c5fb"}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// get HTML
|
||||||
|
try{
|
||||||
|
|
||||||
|
$html =
|
||||||
|
$this->get(
|
||||||
|
$ip,
|
||||||
|
"https://open.spotify.com/search/" .
|
||||||
|
rawurlencode($search) .
|
||||||
|
($category != "any" ? "/" . $category : ""),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
throw new Exception("Failed to get initial search page");
|
||||||
|
}
|
||||||
|
|
||||||
|
// grep bearer and client ID
|
||||||
|
$this->fuckhtml->load($html);
|
||||||
|
|
||||||
|
$script =
|
||||||
|
$this->fuckhtml
|
||||||
|
->getElementById(
|
||||||
|
"session",
|
||||||
|
"script"
|
||||||
|
);
|
||||||
|
|
||||||
|
if($script === null){
|
||||||
|
|
||||||
|
throw new Exception("Failed to grep bearer token");
|
||||||
|
}
|
||||||
|
|
||||||
|
$script =
|
||||||
|
json_decode(
|
||||||
|
$script["innerHTML"],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$bearer = $script["accessToken"];
|
||||||
|
$client_id = $script["clientId"];
|
||||||
|
|
||||||
|
// hit client ID endpoint
|
||||||
|
try{
|
||||||
|
|
||||||
|
$token =
|
||||||
|
json_decode(
|
||||||
|
$this->get(
|
||||||
|
$ip,
|
||||||
|
"https://clienttoken.spotify.com/v1/clienttoken",
|
||||||
|
[ // !! that shit must be sent as json data
|
||||||
|
"client_data" => [
|
||||||
|
"client_id" => $client_id,
|
||||||
|
"client_version" => "1.2.27.93.g7aee53d4",
|
||||||
|
"js_sdk_data" => [
|
||||||
|
"device_brand" => "unknown",
|
||||||
|
"device_id" => "4c7ca20117ca12288ea8fc7118a9118c",
|
||||||
|
"device_model" => "unknown",
|
||||||
|
"device_name" => "computer",
|
||||||
|
"os" => "windows",
|
||||||
|
"os_version" => "NT 10.0"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
self::req_clientid
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
throw new Exception("Failed to fetch token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($token === null){
|
||||||
|
|
||||||
|
throw new Exception("Failed to decode token");
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $token["granted_token"]["token"];
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
switch($get["option"]){
|
||||||
|
|
||||||
|
case "any":
|
||||||
|
$variables = [
|
||||||
|
"searchTerm" => $search,
|
||||||
|
"offset" => 0,
|
||||||
|
"limit" => 10,
|
||||||
|
"numberOfTopResults" => 5,
|
||||||
|
"includeAudiobooks" => true
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "audiobooks":
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload =
|
||||||
|
$this->get(
|
||||||
|
$ip,
|
||||||
|
"https://api-partner.spotify.com/pathfinder/v1/query",
|
||||||
|
[
|
||||||
|
"operationName" => "searchDesktop",
|
||||||
|
"variables" =>
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
"searchTerm" => $search,
|
||||||
|
"offset" => 0,
|
||||||
|
"limit" => 10,
|
||||||
|
"numberOfTopResults" => 5,
|
||||||
|
"includeAudiobooks" => true
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"extensions" =>
|
||||||
|
json_encode(
|
||||||
|
[
|
||||||
|
"persistedQuery" => [
|
||||||
|
"version" => 1,
|
||||||
|
"sha256Hash" => "21969b655b795601fb2d2204a4243188e75fdc6d3520e7b9cd3f4db2aff9591e" // ?
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
self::req_api,
|
||||||
|
$bearer,
|
||||||
|
$token
|
||||||
|
);
|
||||||
|
|
||||||
|
}catch(Exception $error){
|
||||||
|
|
||||||
|
throw new Exception("Failed to fetch JSON results");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($payload == "Token expired"){
|
||||||
|
|
||||||
|
throw new Exception("Grepped spotify token has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = json_decode($payload, true);
|
||||||
|
|
||||||
|
if($payload === null){
|
||||||
|
|
||||||
|
throw new Exception("Failed to decode JSON results");
|
||||||
|
}
|
||||||
|
|
||||||
|
//$payload = json_decode(file_get_contents("scraper/spotify.json"), true);
|
||||||
|
|
||||||
|
$out = [
|
||||||
|
"status" => "ok",
|
||||||
|
"npt" => null,
|
||||||
|
"song" => [],
|
||||||
|
"playlist" => [],
|
||||||
|
"album" => [],
|
||||||
|
"podcast" => [],
|
||||||
|
"author" => [],
|
||||||
|
"user" => []
|
||||||
|
];
|
||||||
|
|
||||||
|
// get songs
|
||||||
|
foreach($payload["data"]["searchV2"]["tracksV2"]["items"] as $result){
|
||||||
|
|
||||||
|
if(isset($result["item"])){
|
||||||
|
|
||||||
|
$result = $result["item"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($result["data"])){
|
||||||
|
|
||||||
|
$result = $result["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$artist, $artist_link] = $this->get_artists($result["artists"]);
|
||||||
|
|
||||||
|
$out["song"][] = [
|
||||||
|
"title" => $result["name"],
|
||||||
|
"description" => null,
|
||||||
|
"url" => "https://open.spotify.com/track/" . $result["id"],
|
||||||
|
"views" => null,
|
||||||
|
"author" => [
|
||||||
|
"name" => $artist,
|
||||||
|
"url" => $artist_link,
|
||||||
|
"avatar" => null
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($result["albumOfTrack"]["coverArt"]),
|
||||||
|
"date" => null,
|
||||||
|
"duration" => $result["duration"]["totalMilliseconds"] / 1000,
|
||||||
|
"stream" => [
|
||||||
|
"endpoint" => "spotify",
|
||||||
|
"url" => "track." . $result["id"]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get playlists
|
||||||
|
foreach($payload["data"]["searchV2"]["playlists"]["items"] as $playlist){
|
||||||
|
|
||||||
|
if(isset($playlist["data"])){
|
||||||
|
|
||||||
|
$playlist = $playlist["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatar = $this->get_thumb($playlist["ownerV2"]["data"]["avatar"]);
|
||||||
|
|
||||||
|
$out["playlist"][] = [
|
||||||
|
"title" => $playlist["name"],
|
||||||
|
"description" => null,
|
||||||
|
"author" => [
|
||||||
|
"name" => $playlist["ownerV2"]["data"]["name"],
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/user/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$playlist["ownerV2"]["data"]["uri"],
|
||||||
|
3
|
||||||
|
)[2],
|
||||||
|
"avatar" => $avatar["url"]
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($playlist["images"]["items"][0]),
|
||||||
|
"date" => null,
|
||||||
|
"duration" => null,
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/playlist/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$playlist["uri"],
|
||||||
|
3
|
||||||
|
)[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get albums
|
||||||
|
foreach($payload["data"]["searchV2"]["albums"]["items"] as $album){
|
||||||
|
|
||||||
|
if(isset($album["data"])){
|
||||||
|
|
||||||
|
$album = $album["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$artist, $artist_link] = $this->get_artists($album["artists"]);
|
||||||
|
|
||||||
|
$out["album"][] = [
|
||||||
|
"title" => $album["name"],
|
||||||
|
"description" => null,
|
||||||
|
"author" => [
|
||||||
|
"name" => $artist,
|
||||||
|
"url" => $artist_link,
|
||||||
|
"avatar" => null
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($album["coverArt"]),
|
||||||
|
"date" => mktime(0, 0, 0, 0, 32, $album["date"]["year"]),
|
||||||
|
"duration" => null,
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/album/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$album["uri"],
|
||||||
|
3
|
||||||
|
)[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get podcasts
|
||||||
|
foreach($payload["data"]["searchV2"]["podcasts"]["items"] as $podcast){
|
||||||
|
|
||||||
|
if(isset($podcast["data"])){
|
||||||
|
|
||||||
|
$podcast = $podcast["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = [];
|
||||||
|
foreach($podcast["topics"]["items"] as $subject){
|
||||||
|
|
||||||
|
$description[] = $subject["title"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = implode(", ", $description);
|
||||||
|
|
||||||
|
if($description == ""){
|
||||||
|
|
||||||
|
$description = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out["podcast"][] = [
|
||||||
|
"title" => $podcast["name"],
|
||||||
|
"description" => $description,
|
||||||
|
"author" => [
|
||||||
|
"name" => $podcast["publisher"]["name"],
|
||||||
|
"url" => null,
|
||||||
|
"avatar" => null
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($podcast["coverArt"]),
|
||||||
|
"date" => null,
|
||||||
|
"duration" => null,
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/show/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$podcast["uri"],
|
||||||
|
3
|
||||||
|
)[2],
|
||||||
|
"stream" => [
|
||||||
|
"endpoint" => null,
|
||||||
|
"url" => null
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get audio books (put in podcasts)
|
||||||
|
foreach($payload["data"]["searchV2"]["audiobooks"]["items"] as $podcast){
|
||||||
|
|
||||||
|
if(isset($podcast["data"])){
|
||||||
|
|
||||||
|
$podcast = $podcast["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = [];
|
||||||
|
foreach($podcast["topics"]["items"] as $subject){
|
||||||
|
|
||||||
|
$description[] = $subject["title"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = implode(", ", $description);
|
||||||
|
|
||||||
|
if($description == ""){
|
||||||
|
|
||||||
|
$description = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$authors = [];
|
||||||
|
foreach($podcast["authors"] as $author){
|
||||||
|
|
||||||
|
$authors[] = $author["name"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$authors = implode(", ", $authors);
|
||||||
|
|
||||||
|
if($authors == ""){
|
||||||
|
|
||||||
|
$authors = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uri =
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$podcast["uri"],
|
||||||
|
3
|
||||||
|
)[2];
|
||||||
|
|
||||||
|
$out["podcast"][] = [
|
||||||
|
"title" => $podcast["name"],
|
||||||
|
"description" => $description,
|
||||||
|
"author" => [
|
||||||
|
"name" => $authors,
|
||||||
|
"url" => null,
|
||||||
|
"avatar" => null
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($podcast["coverArt"]),
|
||||||
|
"date" => strtotime($podcast["publishDate"]["isoString"]),
|
||||||
|
"duration" => null,
|
||||||
|
"url" => "https://open.spotify.com/show/" . $uri,
|
||||||
|
"stream" => [
|
||||||
|
"endpoint" => "spotify",
|
||||||
|
"url" => "episode." . $uri
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get episodes (and place them in podcasts)
|
||||||
|
foreach($payload["data"]["searchV2"]["episodes"]["items"] as $podcast){
|
||||||
|
|
||||||
|
if(isset($podcast["data"])){
|
||||||
|
|
||||||
|
$podcast = $podcast["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$out["podcast"][] = [
|
||||||
|
"title" => $podcast["name"],
|
||||||
|
"description" => $this->limitstrlen($podcast["description"]),
|
||||||
|
"author" => [
|
||||||
|
"name" =>
|
||||||
|
isset(
|
||||||
|
$podcast["podcastV2"]["data"]["publisher"]["name"]
|
||||||
|
) ?
|
||||||
|
$podcast["podcastV2"]["data"]["publisher"]["name"]
|
||||||
|
: null,
|
||||||
|
"url" => null,
|
||||||
|
"avatar" => null
|
||||||
|
],
|
||||||
|
"thumb" => $this->get_thumb($podcast["coverArt"]),
|
||||||
|
"date" => strtotime($podcast["releaseDate"]["isoString"]),
|
||||||
|
"duration" => $podcast["duration"]["totalMilliseconds"] / 1000,
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/show/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$podcast["uri"],
|
||||||
|
3
|
||||||
|
)[2],
|
||||||
|
"stream" => [
|
||||||
|
"endpoint" => null,
|
||||||
|
"url" => null
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get authors
|
||||||
|
foreach($payload["data"]["searchV2"]["artists"]["items"] as $user){
|
||||||
|
|
||||||
|
if(isset($user["data"])){
|
||||||
|
|
||||||
|
$user = $user["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatar = $this->get_thumb($user["visuals"]["avatarImage"]);
|
||||||
|
|
||||||
|
$out["author"][] = [
|
||||||
|
"title" =>
|
||||||
|
(
|
||||||
|
$user["profile"]["verified"] === true ?
|
||||||
|
"✓ " : ""
|
||||||
|
) .
|
||||||
|
$user["profile"]["name"],
|
||||||
|
"followers" => null,
|
||||||
|
"description" => null,
|
||||||
|
"thumb" => $avatar,
|
||||||
|
"url" =>
|
||||||
|
"https://open.spotify.com/artist/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$user["uri"],
|
||||||
|
3
|
||||||
|
)[2]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get users
|
||||||
|
foreach($payload["data"]["searchV2"]["users"]["items"] as $user){
|
||||||
|
|
||||||
|
if(isset($user["data"])){
|
||||||
|
|
||||||
|
$user = $user["data"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatar = $this->get_thumb($user["avatar"]);
|
||||||
|
|
||||||
|
$out["user"][] = [
|
||||||
|
"title" => $user["displayName"] . " (@{$user["id"]})",
|
||||||
|
"followers" => null,
|
||||||
|
"description" => null,
|
||||||
|
"thumb" => $avatar,
|
||||||
|
"url" => "https://open.spotify.com/user/" . $user["id"]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_artists($artists){
|
||||||
|
|
||||||
|
$artist_out = [];
|
||||||
|
|
||||||
|
foreach($artists["items"] as $artist){
|
||||||
|
|
||||||
|
$artist_out[] = $artist["profile"]["name"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$artist_out =
|
||||||
|
implode(", ", $artist_out);
|
||||||
|
|
||||||
|
if($artist_out == ""){
|
||||||
|
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$artist_link =
|
||||||
|
$artist === null ?
|
||||||
|
null :
|
||||||
|
"https://open.spotify.com/artist/" .
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$artists["items"][0]["uri"]
|
||||||
|
)[2];
|
||||||
|
|
||||||
|
return [$artist_out, $artist_link];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_thumb($cover){
|
||||||
|
|
||||||
|
$thumb_out = null;
|
||||||
|
|
||||||
|
if($cover !== null){
|
||||||
|
foreach($cover["sources"] as $thumb){
|
||||||
|
|
||||||
|
if(
|
||||||
|
$thumb_out === null ||
|
||||||
|
(int)$thumb["width"] > $thumb_out["width"]
|
||||||
|
){
|
||||||
|
|
||||||
|
$thumb_out = $thumb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($thumb_out === null){
|
||||||
|
|
||||||
|
return [
|
||||||
|
"url" => null,
|
||||||
|
"ratio" => null
|
||||||
|
];
|
||||||
|
}else{
|
||||||
|
|
||||||
|
return [
|
||||||
|
"url" => $thumb_out["url"],
|
||||||
|
"ratio" => "1:1"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function limitstrlen($text){
|
||||||
|
|
||||||
|
return
|
||||||
|
explode(
|
||||||
|
"\n",
|
||||||
|
wordwrap(
|
||||||
|
str_replace(
|
||||||
|
["\n\r", "\r\n", "\n", "\r"],
|
||||||
|
" ",
|
||||||
|
$text
|
||||||
|
),
|
||||||
|
300,
|
||||||
|
"\n"
|
||||||
|
),
|
||||||
|
2
|
||||||
|
)[0];
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,7 +274,6 @@ foreach($themes as $theme){
|
||||||
/*
|
/*
|
||||||
Set cookies
|
Set cookies
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if($_POST){
|
if($_POST){
|
||||||
|
|
||||||
$loop = &$_POST;
|
$loop = &$_POST;
|
||||||
|
@ -473,6 +472,7 @@ if(count($_GET) === 0){
|
||||||
$frontend->load(
|
$frontend->load(
|
||||||
"search.html",
|
"search.html",
|
||||||
[
|
[
|
||||||
|
"timetaken" => null,
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" =>
|
"right-left" =>
|
||||||
'<div class="infobox"><h2>Preference link</h2>Following this link will re-apply all cookies configured here and will redirect you to the front page. Useful if your browser clears out cookies after a browsing session.<br><br>' .
|
'<div class="infobox"><h2>Preference link</h2>Following this link will re-apply all cookies configured here and will redirect you to the front page. Useful if your browser clears out cookies after a browsing session.<br><br>' .
|
||||||
|
|
|
@ -48,10 +48,35 @@ body{
|
||||||
font-size:16px;
|
font-size:16px;
|
||||||
box-sizing:border-box;
|
box-sizing:border-box;
|
||||||
font-family:sans-serif;
|
font-family:sans-serif;
|
||||||
padding:15px 7% 45px;
|
margin:15px 7% 45px;
|
||||||
word-wrap:anywhere;
|
word-wrap:anywhere;
|
||||||
line-height:22px;
|
line-height:22px;
|
||||||
max-width:2000px;
|
max-width:2000px;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation{
|
||||||
|
position:absolute;
|
||||||
|
top:0;
|
||||||
|
right:0;
|
||||||
|
font-size:14px;
|
||||||
|
line-height:40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation a{
|
||||||
|
color:var(--bdae93);
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation a:hover{
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation a:not(:last-child)::after{
|
||||||
|
content:"|";
|
||||||
|
padding:0 7px;
|
||||||
|
display:inline-block;
|
||||||
|
color:var(--504945);
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,h2,h3,h4,h5,h6{
|
h1,h2,h3,h4,h5,h6{
|
||||||
|
@ -176,7 +201,6 @@ h3,h4,h5,h6{
|
||||||
|
|
||||||
/* Filters */
|
/* Filters */
|
||||||
.filters{
|
.filters{
|
||||||
padding-bottom:15px;
|
|
||||||
margin-bottom:7px;
|
margin-bottom:7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +227,12 @@ h3,h4,h5,h6{
|
||||||
height:24px;
|
height:24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timetaken{
|
||||||
|
font-size:14px;
|
||||||
|
font-weight:bold;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
HOME
|
HOME
|
||||||
|
@ -1288,6 +1318,15 @@ table tr a:last-child{
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 1000px){
|
@media only screen and (max-width: 1000px){
|
||||||
|
form{
|
||||||
|
padding-top:27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation{
|
||||||
|
left:0;
|
||||||
|
right:unset;
|
||||||
|
line-height:22px;
|
||||||
|
}
|
||||||
|
|
||||||
.nextpage.img{
|
.nextpage.img{
|
||||||
width:initial;
|
width:initial;
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
<meta name="description" content="{%server_name%}: {%description%}">
|
<meta name="description" content="{%server_name%}: {%description%}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="navigation">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/settings">Settings</a>
|
||||||
|
<a href="https://git.lolcat.ca/lolcat/4get_news" target="_BLANK">News</a>
|
||||||
|
</div>
|
||||||
<form method="GET" autocomplete="off">
|
<form method="GET" autocomplete="off">
|
||||||
<div class="searchbox">
|
<div class="searchbox">
|
||||||
<input type="submit" value="Search" tabindex="-1">
|
<input type="submit" value="Search" tabindex="-1">
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<div class="autocomplete"></div>
|
<div class="autocomplete"></div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<a href="settings">Settings</a> • <a href="instances">Instances</a> • <a href="api.txt">API</a> • <a href="about">About</a> • <a href="https://git.lolcat.ca/lolcat/4get">Source</a> • <a href="https://ko-fi.com/lolcat" rel="noreferrer" target="BLANK">Donate</a>
|
<a href="settings">Settings</a> • <a href="instances">Instances</a> • <a href="https://git.lolcat.ca/lolcat/4get_news">News</a> • <a href="api.txt">API</a> • <a href="about">About</a> • <a href="https://git.lolcat.ca/lolcat/4get">Source</a> • <a href="https://ko-fi.com/lolcat" rel="noreferrer" target="BLANK">Donate</a>
|
||||||
<div class="subtext">
|
<div class="subtext">
|
||||||
<a href="https://4get.ca">Clearnet</a> • <a href="http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion">Tor</a> • <a href="https://lolcat.ca">Report a problem</a><br>
|
<a href="https://4get.ca">Clearnet</a> • <a href="http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion">Tor</a> • <a href="https://lolcat.ca">Report a problem</a><br>
|
||||||
Running on <b>v{%version%}</b>!!
|
Running on <b>v{%version%}</b>!!
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<div class="timetaken">Took {%timetaken%}s</div>
|
||||||
<div id="images">
|
<div id="images">
|
||||||
{%images%}
|
{%images%}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{%timetaken%}
|
||||||
<div id="overflow" class="web{%class%}">
|
<div id="overflow" class="web{%class%}">
|
||||||
<div class="right-wrapper">
|
<div class="right-wrapper">
|
||||||
<div class="right-right">
|
<div class="right-right">
|
||||||
|
|
|
@ -15,10 +15,11 @@ $get = $frontend->parsegetfilters($_GET, $filters);
|
||||||
/*
|
/*
|
||||||
Captcha
|
Captcha
|
||||||
*/
|
*/
|
||||||
include "lib/captcha_gen.php";
|
include "lib/bot_protection.php";
|
||||||
new captcha($frontend, $get, $filters, "videos", true);
|
new bot_protection($frontend, $get, $filters, "videos", true);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
|
|
7
web.php
7
web.php
|
@ -15,10 +15,11 @@ $get = $frontend->parsegetfilters($_GET, $filters);
|
||||||
/*
|
/*
|
||||||
Captcha
|
Captcha
|
||||||
*/
|
*/
|
||||||
include "lib/captcha_gen.php";
|
include "lib/bot_protection.php";
|
||||||
new captcha($frontend, $get, $filters, "web", true);
|
new bot_protection($frontend, $get, $filters, "web", true);
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
|
"timetaken" => microtime(true),
|
||||||
"class" => "",
|
"class" => "",
|
||||||
"right-left" => "",
|
"right-left" => "",
|
||||||
"right-right" => "",
|
"right-right" => "",
|
||||||
|
@ -359,7 +360,7 @@ if(count($results["answer"]) !== 0){
|
||||||
|
|
||||||
case "audio":
|
case "audio":
|
||||||
$right["answer"] .=
|
$right["answer"] .=
|
||||||
'<audio src="/audio?s=' . urlencode($description["url"]) . '" controls><a href="/audio.php?s=' . urlencode($description["url"]) . '">Listen to the pronunciation audio</a></audio>';
|
'<audio src="/audio/linear?s=' . urlencode($description["url"]) . '" controls><a href="/audio/linear?s=' . urlencode($description["url"]) . '">Listen to the pronunciation audio</a></audio>';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue