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 | ||||||
|  | |||||||
							
								
								
									
										20
									
								
								audio/linear.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								audio/linear.php
									
									
									
									
									
										Normal file
									
								
							| @ -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()); | ||||||
|  | } | ||||||
							
								
								
									
										223
									
								
								audio/sc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								audio/sc.php
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								audio/seekable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								audio/seekable.php
									
									
									
									
									
										Normal file
									
								
							| @ -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()); | ||||||
|  | } | ||||||
							
								
								
									
										214
									
								
								audio/spotify.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								audio/spotify.php
									
									
									
									
									
										Normal file
									
								
							| @ -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(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										128
									
								
								captcha.php
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								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( |  | ||||||
| 	$grid === false || |  | ||||||
| 	$grid[3] === true // has already been generated
 |  | ||||||
| ){ |  | ||||||
| 	 | 	 | ||||||
|  | 	header("Content-Type: text/plain"); | ||||||
|  | 	echo "The IQ test is disabled"; | ||||||
|  | 	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]); | ||||||
| @ -129,27 +132,21 @@ class captcha{ | |||||||
| 			 | 			 | ||||||
| 			$answers[] = $regex; | 			$answers[] = $regex; | ||||||
| 		} | 		} | ||||||
| 
 | 		 | ||||||
| 		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]){ | ||||||
| 						 | 						 | ||||||
| 						return json_decode('"' . $match[0] . '"'); | 						case "u": | ||||||
| 					}else{ | 							return json_decode('"' . $match[0] . '"'); | ||||||
|  | 							break; | ||||||
| 						 | 						 | ||||||
| 						return mb_convert_encoding( | 						case "x": | ||||||
| 							stripcslashes($match[0]), | 							return mb_convert_encoding( | ||||||
| 							"utf-8", | 								stripcslashes($match[0]), | ||||||
| 							"windows-1252" | 								"utf-8", | ||||||
| 						); | 								"windows-1252" | ||||||
|  | 							); | ||||||
|  | 							break; | ||||||
|  | 						 | ||||||
|  | 						default: | ||||||
|  | 							return " "; | ||||||
|  | 							break; | ||||||
| 					} | 					} | ||||||
| 				}, | 				}, | ||||||
| 				$string | 				$string | ||||||
|  | |||||||
							
								
								
									
										47
									
								
								music.php
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								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){ | 	$payload[$write] .= | ||||||
| 		 | 		'&type=' . $name . '"><h2>' . ucfirst($name) . 's</h2></a>'; | ||||||
| 		case "playlist": |  | ||||||
| 			$payload[$write] .= |  | ||||||
| 				'&type=playlist"><h2>Playlists</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 | ||||||
| 						" - ", | 					->getElementsByClassName( | ||||||
| 						$this->fuckhtml | 						"mdate", | ||||||
| 						->getTextContent( | 						"span" | ||||||
| 							$this->fuckhtml |  | ||||||
| 							->getElementsByClassName("i", "p")[0] |  | ||||||
| 						) |  | ||||||
| 					); | 					); | ||||||
| 				 | 				 | ||||||
| 				$data["date"] = | 				if(count($date) !== 0){ | ||||||
| 					strtotime( | 										 | ||||||
| 						$data["date"][count($data["date"]) - 1] | 					$data["date"] = | ||||||
| 					); | 						strtotime( | ||||||
|  | 							$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 . | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								scraper/spotify.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scraper/spotify.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										726
									
								
								scraper/spotify.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										726
									
								
								scraper/spotify.php
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 lolcat
						lolcat