soundcloud lolllllll
This commit is contained in:
		
							parent
							
								
									cfd44438ae
								
							
						
					
					
						commit
						c8ab934b10
					
				
							
								
								
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| lib/test.html | ||||
| lib/postdata.json | ||||
| lib/nextpage.json | ||||
| scraper/brave.html | ||||
| scraper/yandex.json | ||||
| scraper/marginalia.json | ||||
| banner_og/ | ||||
| scraper/mojeek.html | ||||
| scraper/google.html | ||||
| scraper/google-img.html | ||||
| scraper/google-video.html | ||||
| scraper/google-news.html | ||||
| scraper/google-img-nextpage.html | ||||
| scraper/brave-image.html | ||||
| scraper/brave-video.html | ||||
| scraper/facebook.html | ||||
| scraper/facebook-nextpage.json | ||||
| scraper/yandex-video.json | ||||
| scraper/yandex.html | ||||
| scraper/soundcloud.json | ||||
| scraper/mp3-pm.html | ||||
| banner/* | ||||
| !banner/*default* | ||||
							
								
								
									
										29
									
								
								api.txt
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								api.txt
									
									
									
									
									
								
							| @ -242,6 +242,21 @@ | ||||
|     the endpoint above. | ||||
| 
 | ||||
| 
 | ||||
| + /api/v1/music | ||||
|     Each entry under "song" contains a array index called "stream" that | ||||
|     looks like this :: | ||||
|      | ||||
|         endpoint: audio_sc | ||||
|         url: https://api-v2.soundcloud <...> | ||||
|      | ||||
|      | ||||
|     When the endpoint is "audio_sc", you MUST use 4get's audio_sc | ||||
|     endpoint, for example, if you want an audio stream back. Otherwise, | ||||
|     you are free to handle the json+m3u8 crap yourself. If the endpoint | ||||
|     is equal to "audio", that URL SHOULD return a valid HTTP audio | ||||
|     stream, and using the "audio" endpoint becomes optional again. | ||||
| 
 | ||||
| 
 | ||||
| + /favicon | ||||
|     Get the favicon for a website. The only parameter is "s", and must | ||||
|     include the protocol. | ||||
| @ -284,6 +299,20 @@ | ||||
|     The parameter is "s" for the audio link. | ||||
| 
 | ||||
| 
 | ||||
| + /audio_sc | ||||
|     Get a proxied audio file for SoundCloud. Does not support downloads | ||||
|     trough WGET or CURL, since it returns 30kb~160kb "206 Partial | ||||
|     Content" parts, due to technical limitations that comes with | ||||
|     converting m3u8 playlists to seekable audio files. If you use this | ||||
|     endpoint, you must support these 206 codes and also handle the | ||||
|     initial 302 HTTP redirect. I used this method as I didn't want to | ||||
|     store information about your request needlessly. This method also | ||||
|     allows noJS users to access the files. | ||||
|      | ||||
|     The parameter is "s" for the SoundCloud JSON m3u8 abomination. It | ||||
|     does not support "normal" SoundCloud URLs at this time. | ||||
| 
 | ||||
| 
 | ||||
| + Appendix | ||||
|     If you have any questions or need clarifications, please send an | ||||
|     email my way to will at lolcat.ca | ||||
|  | ||||
							
								
								
									
										26
									
								
								api/v1/music.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								api/v1/music.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| <?php | ||||
| 
 | ||||
| header("Content-Type: application/json"); | ||||
| 
 | ||||
| chdir("../../"); | ||||
| 
 | ||||
| include "lib/frontend.php"; | ||||
| $frontend = new frontend(); | ||||
| 
 | ||||
| [$scraper, $filters] = $frontend->getscraperfilters( | ||||
| 	"music", | ||||
| 	isset($_GET["scraper"]) ? $_GET["scraper"] : null | ||||
| ); | ||||
| 
 | ||||
| $get = $frontend->parsegetfilters($_GET, $filters); | ||||
| 
 | ||||
| try{ | ||||
| 	echo json_encode( | ||||
| 		$scraper->music($get), | ||||
| 		JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | ||||
| 	); | ||||
| 	 | ||||
| }catch(Exception $e){ | ||||
| 	 | ||||
| 	echo json_encode(["status" => $e->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(); | ||||
| 	} | ||||
| } | ||||
| @ -150,7 +150,7 @@ class proxy{ | ||||
| 					$curl, | ||||
| 					CURLOPT_HTTPHEADER, | ||||
| 					[ | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0", | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", | ||||
| 						"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, deflate", | ||||
| @ -178,7 +178,7 @@ class proxy{ | ||||
| 					$curl, | ||||
| 					CURLOPT_HTTPHEADER, | ||||
| 					[ | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0", | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", | ||||
| 						"Accept: image/avif,image/webp,*/*", | ||||
| 						"Accept-Language: en-US,en;q=0.5", | ||||
| 						"Accept-Encoding: gzip, deflate", | ||||
| @ -380,7 +380,7 @@ class proxy{ | ||||
| 					$curl, | ||||
| 					CURLOPT_HTTPHEADER, | ||||
| 					[ | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0", | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", | ||||
| 						"Accept: image/avif,image/webp,*/*", | ||||
| 						"Accept-Language: en-US,en;q=0.5", | ||||
| 						"Accept-Encoding: gzip, deflate, br", | ||||
| @ -396,7 +396,7 @@ class proxy{ | ||||
| 					$curl, | ||||
| 					CURLOPT_HTTPHEADER, | ||||
| 					[ | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0", | ||||
| 						"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", | ||||
| 						"Accept: audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5", | ||||
| 						"Accept-Language: en-US,en;q=0.5", | ||||
| 						"Accept-Encoding: gzip, deflate, br", | ||||
|  | ||||
| @ -98,7 +98,7 @@ class frontend{ | ||||
| 			]); | ||||
| 	} | ||||
| 	 | ||||
| 	public function drawtextresult($site, $greentext = null, $duration = null, $keywords, $tabindex = true){ | ||||
| 	public function drawtextresult($site, $greentext = null, $duration = null, $keywords, $tabindex = true, $customhtml = null){ | ||||
| 		 | ||||
| 		$payload = | ||||
| 			'<div class="text-result">'; | ||||
| @ -188,6 +188,8 @@ class frontend{ | ||||
| 				'</div>'; | ||||
| 		} | ||||
| 		 | ||||
| 		$payload .= $customhtml; | ||||
| 		 | ||||
| 		$payload .= '</a>'; | ||||
| 		 | ||||
| 		/* | ||||
| @ -764,6 +766,7 @@ class frontend{ | ||||
| 				'<a href="https://webcache.googleusercontent.com/search?q=cache:' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://google.com" alt="go">Google cache</a>' . | ||||
| 				'<a href="https://web.archive.org/web/' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.org" alt="ar">Archive.org</a>' . | ||||
| 				'<a href="https://archive.is/newest/' . htmlspecialchars($link) . '" class="list" target="_BLANK"><img src="/favicon?s=https://archive.is" alt="ar">Archive.is</a>' . | ||||
| 				'<a href="https://ghostarchive.org/search?term=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://ghostarchive.org" alt="gh">Ghostarchive</a>' . | ||||
| 				'<a href="https://www.bing.com/search?q=url%3A' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://bing.com" alt="bi">Bing cache</a>' . | ||||
| 				'<a href="https://megalodon.jp/?url=' . $urlencode . '" class="list" target="_BLANK"><img src="/favicon?s=https://megalodon.jp" alt="me">Megalodon</a>' . | ||||
| 			'</div>'; | ||||
| @ -835,6 +838,10 @@ class frontend{ | ||||
| 			case "news": | ||||
| 				$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null; | ||||
| 				break; | ||||
| 			 | ||||
| 			case "music": | ||||
| 				$get_scraper = isset($_COOKIE["scraper_news"]) ? $_COOKIE["scraper_news"] : null; | ||||
| 				break; | ||||
| 		} | ||||
| 		 | ||||
| 		if( | ||||
| @ -923,6 +930,14 @@ class frontend{ | ||||
| 						"mojeek" => "Mojeek" | ||||
| 					] | ||||
| 				]; | ||||
| 			 | ||||
| 			case "music": | ||||
| 				$filters["scraper"] = [ | ||||
| 					"display" => "Scraper", | ||||
| 					"option" => [ | ||||
| 						"sc" => "SoundCloud" | ||||
| 					] | ||||
| 				]; | ||||
| 				break; | ||||
| 		} | ||||
| 		 | ||||
| @ -994,6 +1009,11 @@ class frontend{ | ||||
| 				include "scraper/wiby.php"; | ||||
| 				$lib = new wiby(); | ||||
| 				break; | ||||
| 			 | ||||
| 			case "sc": | ||||
| 				include "scraper/sc.php"; | ||||
| 				$lib = new sc(); | ||||
| 				break; | ||||
| 		} | ||||
| 		 | ||||
| 		// set scraper on $_GET
 | ||||
| @ -1169,7 +1189,7 @@ class frontend{ | ||||
| 		 | ||||
| 		$html = null; | ||||
| 		 | ||||
| 		foreach(["web", "images", "videos", "news"] as $type){ | ||||
| 		foreach(["web", "images", "videos", "news", "music"] as $type){ | ||||
| 			 | ||||
| 			$html .= '<a href="/' . $type . '?s=' . urlencode($query); | ||||
| 			 | ||||
| @ -1303,7 +1323,7 @@ class frontend{ | ||||
| 			return htmlspecialchars($image); | ||||
| 		} | ||||
| 		 | ||||
| 		return "/proxy?i=" . urlencode($image) . "&s=" . $format; | ||||
| 		return "/proxy.php?i=" . urlencode($image) . "&s=" . $format; | ||||
| 	} | ||||
| 	 | ||||
| 	public function htmlnextpage($gets, $npt, $page){ | ||||
|  | ||||
							
								
								
									
										224
									
								
								music.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								music.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| <?php | ||||
| 
 | ||||
| /* | ||||
| 	Initialize random shit | ||||
| */ | ||||
| include "lib/frontend.php"; | ||||
| $frontend = new frontend(); | ||||
| 
 | ||||
| [$scraper, $filters] = $frontend->getscraperfilters("music"); | ||||
| 
 | ||||
| $get = $frontend->parsegetfilters($_GET, $filters); | ||||
| 
 | ||||
| $frontend->loadheader( | ||||
| 	$get, | ||||
| 	$filters, | ||||
| 	"music" | ||||
| ); | ||||
| 
 | ||||
| $payload = [ | ||||
| 	"class" => "", | ||||
| 	"right-left" => "", | ||||
| 	"right-right" => "", | ||||
| 	"left" => "" | ||||
| ]; | ||||
| 
 | ||||
| try{ | ||||
| 	$results = $scraper->music($get); | ||||
| 	 | ||||
| }catch(Exception $error){ | ||||
| 	 | ||||
| 	echo | ||||
| 		$frontend->drawerror( | ||||
| 			"Shit", | ||||
| 			'This scraper returned an error:' . | ||||
| 			'<div class="code">' . htmlspecialchars($error->getMessage()) . '</div>' . | ||||
| 			'Things you can try:' . | ||||
| 			'<ul>' .  | ||||
| 				'<li>Use a different scraper</li>' . | ||||
| 				'<li>Remove keywords that could cause errors</li>' . | ||||
| 				'<li>Use another 4get instance</li>' . | ||||
| 			'</ul><br>' . | ||||
| 			'If the error persists, please <a href="/about">contact the administrator</a>.' | ||||
| 		); | ||||
| 	die(); | ||||
| } | ||||
| 
 | ||||
| $categories = [ | ||||
| 	"song" => "", | ||||
| 	"author" => "", | ||||
| 	"playlist" => "" | ||||
| ]; | ||||
| 
 | ||||
| /* | ||||
| 	Set the main container | ||||
| */ | ||||
| $main = null; | ||||
| 
 | ||||
| if(count($results["song"]) !== 0){ | ||||
| 	 | ||||
| 	$main = "song"; | ||||
| 	 | ||||
| }elseif(count($results["author"]) !== 0){ | ||||
| 	 | ||||
| 	$main = "author"; | ||||
| 	 | ||||
| }elseif(count($results["playlist"]) !== 0){ | ||||
| 	 | ||||
| 	$main = "playlist"; | ||||
| 	 | ||||
| }else{ | ||||
| 	 | ||||
| 	// No results found!
 | ||||
| 	echo | ||||
| 		$frontend->drawerror( | ||||
| 			"Nobody here but us chickens!", | ||||
| 			'Have you tried:' . | ||||
| 				'<ul>' . | ||||
| 					'<li>Using a different scraper</li>' . | ||||
| 					'<li>Using fewer keywords</li>' . | ||||
| 					'<li>Defining broader filters (Is NSFW turned off?)</li>' . | ||||
| 				'</ul>' . | ||||
| 			'</div>' | ||||
| 		); | ||||
| 	die(); | ||||
| } | ||||
| 
 | ||||
| /* | ||||
| 	Generate list of songs | ||||
| */ | ||||
| foreach($categories as $name => $data){ | ||||
| 	 | ||||
| 	foreach($results[$name] as $item){ | ||||
| 		 | ||||
| 		$greentext = []; | ||||
| 		 | ||||
| 		if( | ||||
| 			isset($item["date"]) && | ||||
| 			$item["date"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$greentext[] = date("jS M y @ g:ia", $item["date"]); | ||||
| 		} | ||||
| 		 | ||||
| 		if( | ||||
| 			isset($item["views"]) && | ||||
| 			$item["views"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$views = number_format($item["views"]) . " views"; | ||||
| 			$greentext[] = $views; | ||||
| 		} | ||||
| 		 | ||||
| 		if( | ||||
| 			isset($item["followers"]) && | ||||
| 			$item["followers"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$greentext[] = number_format($item["followers"]) . " followers"; | ||||
| 		} | ||||
| 		 | ||||
| 		if( | ||||
| 			isset($item["author"]["name"]) && | ||||
| 			$item["author"]["name"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$greentext[] = $item["author"]["name"]; | ||||
| 		} | ||||
| 		 | ||||
| 		$greentext = implode(" • ", $greentext); | ||||
| 		 | ||||
| 		if( | ||||
| 			isset($item["duration"]) && | ||||
| 			$item["duration"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$duration = $frontend->s_to_timestamp($item["duration"]); | ||||
| 		}else{ | ||||
| 			 | ||||
| 			$duration = null; | ||||
| 		} | ||||
| 		 | ||||
| 		$tabindex = $name == $main ? true : false; | ||||
| 		 | ||||
| 		$customhtml = null; | ||||
| 		 | ||||
| 		if( | ||||
| 			$name == "song" && | ||||
| 			$item["stream"]["endpoint"] !== null | ||||
| 		){ | ||||
| 			 | ||||
| 			$customhtml = | ||||
| 				'<audio src="' . $item["stream"]["endpoint"] . '?s=' . urlencode($item["stream"]["url"]) . '" controls autostart="false" preload="none">'; | ||||
| 		} | ||||
| 		 | ||||
| 		$categories[$name] .= $frontend->drawtextresult($item, $greentext, $duration, $get["s"], $tabindex, $customhtml); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| $payload["left"] = $categories[$main]; | ||||
| 
 | ||||
| // dont re-draw the category
 | ||||
| unset($categories[$main]); | ||||
| 
 | ||||
| /* | ||||
| 	Populate right handside | ||||
| */ | ||||
| 
 | ||||
| $i = 1; | ||||
| foreach($categories as $name => $value){ | ||||
| 	 | ||||
| 	if($value == ""){ | ||||
| 		 | ||||
| 		continue; | ||||
| 	} | ||||
| 	 | ||||
| 	if($i % 2 === 1){ | ||||
| 		 | ||||
| 		$write = "right-left"; | ||||
| 	}else{ | ||||
| 		 | ||||
| 		$write = "right-right"; | ||||
| 	} | ||||
| 	 | ||||
| 	$payload[$write] .= | ||||
| 		'<div class="answer-wrapper">' . | ||||
| 		'<input id="answer' . $i . '" class="spoiler" type="checkbox">' . | ||||
| 		'<div class="answer">' . | ||||
| 			'<div class="answer-title">' . | ||||
| 				'<a class="answer-title" href="?s=' . urlencode($get["s"]); | ||||
| 	 | ||||
| 	switch($name){ | ||||
| 		 | ||||
| 		case "playlist": | ||||
| 			$payload[$write] .= | ||||
| 				'&type=playlist"><h2>Playlists</h2></a>'; | ||||
| 			break; | ||||
| 		 | ||||
| 		case "author": | ||||
| 			$payload[$write] .= | ||||
| 				'&type=people"><h2>Authors</h2></a>'; | ||||
| 			break; | ||||
| 	} | ||||
| 	 | ||||
| 	$payload[$write] .= | ||||
| 			'</div>' . | ||||
| 			$categories[$name] . | ||||
| 		'</div>' . | ||||
| 		'<label class="spoiler-button" for="answer' . $i . '"></label></div>'; | ||||
| 	 | ||||
| 	$i++; | ||||
| } | ||||
| 
 | ||||
| if($i !== 1){ | ||||
| 	 | ||||
| 	$payload["class"] = " has-answer"; | ||||
| } | ||||
| 
 | ||||
| if($results["npt"] !== null){ | ||||
| 	 | ||||
| 	$payload["left"] .= | ||||
| 		'<a href="' . $frontend->htmlnextpage($get, $results["npt"], "music") . '" class="nextpage">Next page ></a>'; | ||||
| } | ||||
| 
 | ||||
| echo $frontend->load("search.html", $payload); | ||||
							
								
								
									
										397
									
								
								scraper/sc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								scraper/sc.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,397 @@ | ||||
| <?php | ||||
| 
 | ||||
| class sc{ | ||||
| 	 | ||||
| 	public function __construct(){ | ||||
| 		 | ||||
| 		include "lib/nextpage.php"; | ||||
| 		$this->nextpage = new nextpage("sc"); | ||||
| 	} | ||||
| 	 | ||||
| 	public function getfilters($page){ | ||||
| 		 | ||||
| 		return [ | ||||
| 			"type" => [ | ||||
| 				"display" => "Type", | ||||
| 				"option" => [ | ||||
| 					"any" => "Any type", | ||||
| 					"track" => "Tracks", | ||||
| 					"people" => "People", | ||||
| 					"album" => "Albums", | ||||
| 					"playlist" => "Playlists", | ||||
| 					"goplus" => "Go+ Tracks" | ||||
| 				] | ||||
| 			] | ||||
| 		]; | ||||
| 	} | ||||
| 	 | ||||
| 	private function get($url, $get = []){ | ||||
| 		 | ||||
| 		$curlproc = curl_init(); | ||||
| 		 | ||||
| 		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, | ||||
| 			["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0", | ||||
| 			"Accept: application/json, text/javascript, */*; q=0.01", | ||||
| 			"Accept-Language: en-US,en;q=0.5", | ||||
| 			"Accept-Encoding: gzip", | ||||
| 			"Referer: https://soundcloud.com/", | ||||
| 			"Origin: https://soundcloud.com", | ||||
| 			"DNT: 1", | ||||
| 			"Connection: keep-alive", | ||||
| 			"Sec-Fetch-Dest: empty", | ||||
| 			"Sec-Fetch-Mode: cors", | ||||
| 			"Sec-Fetch-Site: same-site"] | ||||
| 		); | ||||
| 		 | ||||
| 		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; | ||||
| 	} | ||||
| 	 | ||||
| 	public function music($get){ | ||||
| 		 | ||||
| 		if($get["npt"]){ | ||||
| 			 | ||||
| 			$params = $this->nextpage->get($get["npt"], "music"); | ||||
| 			$params = json_decode($params, true); | ||||
| 			 | ||||
| 			$url = $params["url"]; | ||||
| 			unset($params["url"]); | ||||
| 			 | ||||
| 		}else{ | ||||
| 			 | ||||
| 			// normal search:
 | ||||
| 			// https://api-v2.soundcloud.com/search?q=freddie%20dredd&variant_ids=&facet=model&user_id=351062-302234-707916-795081&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			// soundcloud go+ search:
 | ||||
| 			// https://api-v2.soundcloud.com/search/tracks?q=freddie%20dredd&variant_ids=&filter.content_tier=SUB_HIGH_TIER&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			// tracks search:
 | ||||
| 			// https://api-v2.soundcloud.com/search/tracks?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			// users search:
 | ||||
| 			// https://api-v2.soundcloud.com/search/users?q=freddie%20dredd&variant_ids=&facet=place&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			// albums search:
 | ||||
| 			// https://api-v2.soundcloud.com/search/albums?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			// playlists search:
 | ||||
| 			// https://api-v2.soundcloud.com/search/playlists_without_albums?q=freddie%20dredd&variant_ids=&facet=genre&user_id=630591-269800-703400-765403&client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E&limit=20&offset=0&linked_partitioning=1&app_version=1693487844&app_locale=en
 | ||||
| 			 | ||||
| 			$search = $get["s"]; | ||||
| 			$type = $get["type"]; | ||||
| 			 | ||||
| 			switch($type){ | ||||
| 				 | ||||
| 				case "any": | ||||
| 					$url = "https://api-v2.soundcloud.com/search"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"facet" => "model", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "track": | ||||
| 					$url = "https://api-v2.soundcloud.com/search/tracks"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"facet_genre" => "", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "people": | ||||
| 					$url = "https://api-v2.soundcloud.com/search/users"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"facet" => "place", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "album": | ||||
| 					$url = "https://api-v2.soundcloud.com/search/albums"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"facet" => "genre", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "playlist": | ||||
| 					$url = "https://api-v2.soundcloud.com/search/playlists_without_albums"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"facet" => "genre", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "goplus": | ||||
| 					$url = "https://api-v2.soundcloud.com/search/tracks"; | ||||
| 					$params = [ | ||||
| 						"q" => $search, | ||||
| 						"variant_ids" => "", | ||||
| 						"filter.content_tier" => "SUB_HIGH_TIER", | ||||
| 						"facet" => "genre", | ||||
| 						"user_id" => "351062-302234-707916-795081", | ||||
| 						"client_id" => "iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E", | ||||
| 						"limit" => 20, | ||||
| 						"offset" => 0, | ||||
| 						"linked_partitioning" => 1, | ||||
| 						"app_version" => 1693487844, | ||||
| 						"app_locale" => "en" | ||||
| 					]; | ||||
| 					break; | ||||
| 			} | ||||
| 		} | ||||
| 			 | ||||
| 		try{ | ||||
| 			 | ||||
| 			$json = $this->get($url, $params); | ||||
| 			 | ||||
| 		}catch(Exception $error){ | ||||
| 			 | ||||
| 			throw new Exception("Failed to fetch JSON"); | ||||
| 		} | ||||
| 		 | ||||
| 		/* | ||||
| 		$handle = fopen("scraper/soundcloud.json", "r"); | ||||
| 		$json = fread($handle, filesize("scraper/soundcloud.json")); | ||||
| 		fclose($handle); | ||||
| 		*/ | ||||
| 		 | ||||
| 		$json = json_decode($json, true); | ||||
| 		 | ||||
| 		if($json === null){ | ||||
| 			 | ||||
| 			throw new Exception("Failed to decode JSON"); | ||||
| 		} | ||||
| 		 | ||||
| 		$out = [ | ||||
| 			"status" => "ok", | ||||
| 			"npt" => null, | ||||
| 			"song" => [], | ||||
| 			"playlist" => [], | ||||
| 			"author" => [] | ||||
| 		]; | ||||
| 		 | ||||
| 		/* | ||||
| 			Get next page | ||||
| 		*/ | ||||
| 		if(isset($json["next_href"])){ | ||||
| 			 | ||||
| 			$params["query_urn"] = $json["query_urn"]; | ||||
| 			$params["offset"] = $params["offset"] + 20; | ||||
| 			$params["url"] = $url; // we will remove this later
 | ||||
| 			 | ||||
| 			$out["npt"] = | ||||
| 				$this->nextpage->store( | ||||
| 					json_encode($params), | ||||
| 					"music" | ||||
| 				); | ||||
| 		} | ||||
| 		 | ||||
| 		/* | ||||
| 			Scrape items | ||||
| 		*/ | ||||
| 		foreach($json["collection"] as $item){ | ||||
| 			 | ||||
| 			switch($item["kind"]){ | ||||
| 				 | ||||
| 				case "user": | ||||
| 					// parse author
 | ||||
| 					$out["author"][] = [ | ||||
| 						"title" => $item["username"], | ||||
| 						"followers" => $item["followers_count"], | ||||
| 						"description" => $item["track_count"] . " songs. " . $this->limitstrlen($item["description"]), | ||||
| 						"thumb" => [ | ||||
| 							"url" => $item["avatar_url"], | ||||
| 							"ratio" => "1:1" | ||||
| 						], | ||||
| 						"url" => $item["permalink_url"] | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "playlist": | ||||
| 					// parse playlist
 | ||||
| 					$description = []; | ||||
| 					$count = 0; | ||||
| 					 | ||||
| 					foreach($item["tracks"] as $song){ | ||||
| 						 | ||||
| 						$count++; | ||||
| 						 | ||||
| 						if(!isset($song["title"])){ | ||||
| 							 | ||||
| 							continue; | ||||
| 						} | ||||
| 						 | ||||
| 						$description[] = $song["title"]; | ||||
| 					} | ||||
| 					 | ||||
| 					if(count($description) != 0){ | ||||
| 						 | ||||
| 						$description = $count . " songs. " . implode(", ", $description); | ||||
| 					} | ||||
| 					 | ||||
| 					if( | ||||
| 						isset($item["artwork_url"]) && | ||||
| 						!empty($item["artwork_url"]) | ||||
| 					){ | ||||
| 						 | ||||
| 						$thumb = [ | ||||
| 							"ratio" => "1:1", | ||||
| 							"url" => $item["artwork_url"] | ||||
| 						]; | ||||
| 						 | ||||
| 					}elseif( | ||||
| 						isset($item["tracks"][0]["artwork_url"]) && | ||||
| 						!empty($item["tracks"][0]["artwork_url"]) | ||||
| 					){ | ||||
| 						 | ||||
| 						$thumb = [ | ||||
| 							"ratio" => "1:1", | ||||
| 							"url" => $item["tracks"][0]["artwork_url"] | ||||
| 						]; | ||||
| 					}else{ | ||||
| 						 | ||||
| 						$thumb = [ | ||||
| 							"ratio" => null, | ||||
| 							"url" => null | ||||
| 						]; | ||||
| 					} | ||||
| 					 | ||||
| 					$out["playlist"][] = [ | ||||
| 						"title" => $item["title"], | ||||
| 						"description" => $description, | ||||
| 						"author" => [ | ||||
| 							"name" => $item["user"]["username"], | ||||
| 							"url" => $item["user"]["permalink_url"], | ||||
| 							"avatar" => $item["user"]["avatar_url"] | ||||
| 						], | ||||
| 						"thumb" => $thumb, | ||||
| 						"date" => strtotime($item["created_at"]), | ||||
| 						"duration" => $item["duration"] / 1000, | ||||
| 						"url" => $item["permalink_url"] | ||||
| 					]; | ||||
| 					break; | ||||
| 				 | ||||
| 				case "track": | ||||
| 					if(stripos($item["monetization_model"], "TIER") === false){ | ||||
| 						 | ||||
| 						$stream = [ | ||||
| 							"endpoint" => "audio_sc", | ||||
| 							"url" => | ||||
| 								$item["media"]["transcodings"][0]["url"] . | ||||
| 								"?client_id=iMxZgT5mfGstBj8GWJbYMvpzelS8ne0E" . | ||||
| 								"&track_authorization=" . | ||||
| 								$item["track_authorization"] | ||||
| 						]; | ||||
| 					}else{ | ||||
| 						 | ||||
| 						$stream = [ | ||||
| 							"endpoint" => null, | ||||
| 							"url" => null | ||||
| 						]; | ||||
| 					} | ||||
| 					 | ||||
| 					// parse track
 | ||||
| 					$out["song"][] = [ | ||||
| 						"title" => $item["title"], | ||||
| 						"description" => $item["description"] == "" ? null : $this->limitstrlen($item["description"]), | ||||
| 						"url" => $item["permalink_url"], | ||||
| 						"views" => $item["playback_count"], | ||||
| 						"author" => [ | ||||
| 							"name" => $item["user"]["username"], | ||||
| 							"url" => $item["user"]["permalink_url"], | ||||
| 							"avatar" => $item["user"]["avatar_url"] | ||||
| 						], | ||||
| 						"thumb" => [ | ||||
| 							"ratio" => "1:1", | ||||
| 							"url" => $item["artwork_url"] | ||||
| 						], | ||||
| 						"date" => strtotime($item["created_at"]), | ||||
| 						"duration" => (int)$item["full_duration"] / 1000, | ||||
| 						"stream" => $stream | ||||
| 					]; | ||||
| 					break; | ||||
| 			} | ||||
| 		} | ||||
| 		 | ||||
| 		return $out; | ||||
| 	} | ||||
| 	 | ||||
| 	private function limitstrlen($text){ | ||||
| 		 | ||||
| 		return | ||||
| 			explode( | ||||
| 				"\n", | ||||
| 				wordwrap( | ||||
| 					str_replace( | ||||
| 						"\n", | ||||
| 						" ", | ||||
| 						$text | ||||
| 					), | ||||
| 					300, | ||||
| 					"\n" | ||||
| 				) | ||||
| 			)[0]; | ||||
| 	} | ||||
| } | ||||
| @ -959,6 +959,7 @@ class yandex{ | ||||
| 					"img" | ||||
| 				); | ||||
| 			 | ||||
| 			$c = 1; | ||||
| 			if(count($thumb) === 0){ | ||||
| 				 | ||||
| 				$thumb = [ | ||||
| @ -967,7 +968,6 @@ class yandex{ | ||||
| 				]; | ||||
| 			}else{ | ||||
| 				 | ||||
| 				$c = 1; | ||||
| 				$thumb = [ | ||||
| 					"url" => | ||||
| 						str_replace( | ||||
| @ -1065,12 +1065,17 @@ class yandex{ | ||||
| 				"views" => $views, | ||||
| 				"thumb" => $thumb, | ||||
| 				"url" => | ||||
| 					$this->fuckhtml | ||||
| 					->getTextContent( | ||||
| 						$data["counters"] | ||||
| 						["toHostingLoaded"] | ||||
| 						["postfix"] | ||||
| 						["href"] | ||||
| 					str_replace( | ||||
| 						"http://", | ||||
| 						"https://", | ||||
| 						$this->fuckhtml | ||||
| 						->getTextContent( | ||||
| 							$data["counters"] | ||||
| 							["toHostingLoaded"] | ||||
| 							["postfix"] | ||||
| 							["href"] | ||||
| 						), | ||||
| 						$c | ||||
| 					) | ||||
| 			]; | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										12
									
								
								settings.php
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								settings.php
									
									
									
									
									
								
							| @ -161,6 +161,16 @@ $settings = [ | ||||
| 						"text" => "Mojeek" | ||||
| 					] | ||||
| 				] | ||||
| 			], | ||||
| 			[ | ||||
| 				"description" => "Music", | ||||
| 				"parameter" => "scraper_music", | ||||
| 				"options" => [ | ||||
| 					[ | ||||
| 						"value" => "sc", | ||||
| 						"text" => "SoundCloud" | ||||
| 					] | ||||
| 				] | ||||
| 			] | ||||
| 		] | ||||
| 	] | ||||
| @ -235,7 +245,7 @@ echo | ||||
| 		'<head>' . | ||||
| 			'<meta http-equiv="Content-Type" content="text/html;charset=utf-8">' . | ||||
| 			'<title>Settings</title>' . | ||||
| 			'<link rel="stylesheet" href="/static/style.css">' . | ||||
| 			'<link rel="stylesheet" href="/static/style.css?v3">' . | ||||
| 			'<meta name="viewport" content="width=device-width,initial-scale=1">' . | ||||
| 			'<meta name="robots" content="index,follow">' . | ||||
| 			'<link rel="icon" type="image/x-icon" href="/favicon.ico">' . | ||||
|  | ||||
| @ -51,6 +51,13 @@ | ||||
| audio{ | ||||
| 	max-width:100%; | ||||
| 	display:block; | ||||
| } | ||||
| 
 | ||||
| .left audio{ | ||||
| 	margin-top:7px; | ||||
| } | ||||
| 
 | ||||
| .right-wrapper audio{ | ||||
| 	margin-bottom:17px; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| 	<head> | ||||
| 		<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> | ||||
| 		<title>{%title%}</title> | ||||
| 		<link rel="stylesheet" href="/static/style.css?v2"> | ||||
| 		<link rel="stylesheet" href="/static/style.css?v3"> | ||||
| 		<meta name="viewport" content="width=device-width,initial-scale=1"> | ||||
| 		<meta name="robots" content="{%index%}index,{%index%}follow"> | ||||
| 		<link rel="icon" type="image/x-icon" href="/favicon.ico"> | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
| 		<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> | ||||
| 		<title>4get</title> | ||||
| 		<meta name="viewport" content="width=device-width,initial-scale=1"> | ||||
| 		<link rel="stylesheet" href="/static/style.css?v2"> | ||||
| 		<link rel="stylesheet" href="/static/style.css?v3"> | ||||
| 		<meta name="robots" content="index,follow"> | ||||
| 		<link rel="icon" type="image/x-icon" href="/favicon.ico"> | ||||
| 		<meta name="description" content="4get.ca: They live in our walls!"> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 lolcat
						lolcat