The official website for the open-source compatibility layer fpPS4
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make the comp list use the new backend, remove the old yucky php ๐ŸŽ‰

MrSn0wy ad2d8c2d 5df0f66a

+291 -831
+2 -1
.gitignore
··· 4 4 /public_html/_images/HB 5 5 /public_html/_images/TEST 6 6 /public_html/.htaccess 7 - /public_html/.user.ini 7 + /public_html/.user.ini 8 + .idea
+4 -2
backend/src/images.rs
··· 93 93 94 94 95 95 let mut cropped_image = Image::new( 96 - 128, 97 - 128, 96 + 256, 97 + 256, 98 98 PixelType::U8x4, // RGBA8 99 99 ); 100 100 ··· 113 113 let path = format!("{}/game/{}.avif", images_path, issue.code); 114 114 115 115 if Path::new(&path).exists() { 116 + issue.image = true; 116 117 return Ok(()); 117 118 } 118 119 ··· 133 134 let path = format!("{}/homebrew/{}.avif", images_path, issue.title); 134 135 135 136 if Path::new(&path).exists() { 137 + issue.image = true; 136 138 return Ok(()); 137 139 } 138 140
+4 -8
backend/src/main.rs
··· 30 30 // update the homebrew database and get a connection 31 31 homebrew_database_updater(HB_DATABASE, &homebrew_token).polar_unwrap("", true); 32 32 let homebrew_db = Connection::open(HB_DATABASE).polar_unwrap("Failed to open SQLite connection to homebrew database!", true); 33 - let homebrew_db = Arc::new( Mutex::new(homebrew_db)); 33 + let homebrew_db = Arc::new( Mutex::new(homebrew_db) ); 34 34 35 35 let (page_count, issues_count): (u64, u64) = networking::github_get_repo_info(&github_token); 36 36 ··· 44 44 45 45 let old_game_skips: Arc< Vec<u64> > = { 46 46 match fs::read_to_string(GAME_SKIPS_DATABASE) { 47 - Ok(string) => { 48 - 49 - Arc::new(serde_json::from_str(&string).unwrap_or_default()) 50 - 51 - } 47 + Ok(string) => Arc::new(serde_json::from_str(&string).unwrap_or_default()), 52 48 Err(_) => Arc::new(vec![]) 53 49 } 54 50 }; ··· 125 121 126 122 127 123 // saves databases 128 - let temp_string = serde_json::to_string_pretty(&issues).polar_unwrap("error making issues into a json!", true); 124 + let temp_string = serde_json::to_string(&issues).polar_unwrap("error making issues into a json!", true); 129 125 let temp_path = format!("{database_path}/database.json"); 130 126 fs::write(temp_path, temp_string).polar_unwrap("Error saving the \"database\" file!", true); 131 127 132 - let temp_string = serde_json::to_string_pretty(&game_skips).polar_unwrap("error making game_skips into a json!", true); 128 + let temp_string = serde_json::to_string(&game_skips).polar_unwrap("error making game_skips into a json!", true); 133 129 fs::write(GAME_SKIPS_DATABASE, temp_string).polar_unwrap("Error saving the \"game_skips\" file!", true); 134 130 135 131 println_green!("dun! {} issues processed", issues.len());
+2 -2
public_html/_parts/navbar.html
··· 23 23 text-decoration: none; 24 24 font-size: 1.5rem; 25 25 color: var(--text); 26 - line-height: 0%; 26 + line-height: 0; 27 27 filter: invert(1); 28 28 } 29 29 .logo span {filter: invert(1);} /* fix text color */ ··· 32 32 display: flex; 33 33 justify-content: flex-end; 34 34 align-items: center; 35 - line-height: 0%; 35 + line-height: 0; 36 36 margin: 0 3.5vw; 37 37 gap: 0.5vw; 38 38 filter: invert(1);
+20
public_html/_parts/required.js
··· 101 101 document.documentElement.style.zoom = "1"; 102 102 document.documentElement.style.fontSize = fontSize + 'px'; 103 103 } 104 + } 105 + 106 + // Fetch html for footer and header 107 + async function fetchHtml(path, target){ 108 + return new Promise((resolve, reject) => { 109 + fetch(path).then(response => response.text()).then(data => { 110 + document.querySelector(target).innerHTML = data; 111 + resolve("done!") 112 + }).catch(() => { 113 + reject("error occurred while fetching html!") 114 + }); 115 + }) 116 + } 117 + 118 + async function init() { 119 + // Header & Footer Load 120 + await fetchHtml("/_parts/navbar.html", "#header"); 121 + await fetchHtml("/_parts/footer.html", "#footer"); 122 + 123 + LightModeIconChange(); 104 124 }
-88
public_html/_scripts/api.php
··· 1 - <?php 2 - $serverUsername = getenv('USERNAME'); 3 - require_once "/home/{$serverUsername}/domains/fpps4.net/config/config.php"; // import config file 4 - $validSecret = API_ACCESS_SECRET; 5 - $validArgument = API_ARGUMENT_SECRET; 6 - $headers = apache_request_headers(); 7 - 8 - /// makes it so the api is accessible by: headers, browser query and by command line arguments :3 9 - if ((isset($_GET[$validArgument]) && $_GET[$validArgument] === $validSecret) || (isset($argv[1]) && $argv[1] === $validSecret) || isset($_SERVER['HTTP_AUTHORIZATION']) === $validSecret) { 10 - /// processing user input 11 - $cusaCodes = isset($_GET['cusa']) ? $_GET['cusa'] : ''; 12 - $homebrews = isset($_GET['homebrew']) ? $_GET['homebrew'] : ''; 13 - $cusaCodes = filter_var($cusaCodes, FILTER_SANITIZE_STRING); 14 - $homebrews = filter_var($homebrews, FILTER_SANITIZE_STRING); 15 - 16 - if ($cusaCodes || $homebrews) { 17 - /// make connection to database 18 - $host = DATABASE_HOST; 19 - $database = DATABASE_NAME; 20 - $username = DATABASE_USERNAME; 21 - $password = DATABASE_PASSWORD; 22 - try { 23 - $conn = new PDO("mysql:host=$host;dbname=$database", $username, $password); 24 - } catch (PDOException $e) { 25 - die("The server got itself into trouble, sorry for that."); 26 - } 27 - 28 - /// variables 29 - $cusacodeArray = array(); 30 - $homebrewArray = array(); 31 - $cusacode = explode(',', $cusaCodes); 32 - $homebrew = explode(',', $homebrews); 33 - /// handles cusa codes and gets status 34 - if ($cusaCodes) { 35 - foreach ($cusacode as $code) { 36 - $query = "SELECT tags FROM issues WHERE cusacode = :cusacode"; 37 - $stmt = $conn->prepare($query); 38 - $stmt->bindParam(':cusacode', $code, PDO::PARAM_STR); 39 - $stmt->execute(); 40 - $result = $stmt->fetchAll(PDO::FETCH_ASSOC); 41 - 42 - if ($result) { 43 - $tag = $result["0"]["tags"]; 44 - } else { 45 - $tag = "N/A"; 46 - } 47 - 48 - $cusacodeArray[] = array( 49 - "id" => $code, 50 - "tag" => $tag 51 - ); 52 - } 53 - } 54 - if ($homebrews) { 55 - foreach ($homebrew as $code) { 56 - $query = "SELECT tags FROM issues WHERE title = :homebrew"; 57 - $stmt = $conn->prepare($query); 58 - $stmt->bindParam(':homebrew', $code, PDO::PARAM_STR); 59 - $stmt->execute(); 60 - $HBresult = $stmt->fetchAll(PDO::FETCH_ASSOC); 61 - 62 - if ($HBresult) { 63 - $tag = $HBresult["0"]["tags"]; 64 - } else { 65 - $tag = "N/A"; 66 - } 67 - $homebrewArray[] = array( 68 - "title" => $code, 69 - "tag" => $tag 70 - ); 71 - } 72 - } 73 - $data = array( 74 - "cusacode" => $cusacodeArray, 75 - "homebrew" => $homebrewArray 76 - ); 77 - /// end 78 - header('Content-Type: application/json'); 79 - $jsonData = json_encode($data); 80 - //$jsonData = json_encode($data, JSON_PRETTY_PRINT); 81 - 82 - echo $jsonData; 83 - $conn = null; //exit connection 84 - } 85 - } else { 86 - http_response_code(404); 87 - include("/home/{$serverUsername}/domains/fpps4.net/public_html/404.php"); 88 - }?>
-189
public_html/_scripts/search.php
··· 1 - <?php 2 - // V2 or something idk 3 - 4 - $startTime = microtime(true); // timer 5 - 6 - /// connecting to database 7 - $dir = dirname(__DIR__, 2); 8 - require_once "$dir/_config/config.php"; // import config file 9 - 10 - $host = DATABASE_HOST; 11 - $database = DATABASE_NAME; 12 - $username = DATABASE_USERNAME; 13 - $password = DATABASE_PASSWORD; 14 - try { 15 - $conn = new PDO("mysql:host=$host;dbname=$database", $username, $password); 16 - } catch (PDOException $e) { 17 - echo "The server got itself into trouble, please try to refresh.<br>"; 18 - } 19 - 20 - /// processing user input 21 - $searchQuery = htmlspecialchars(isset($_GET['q']) ? $_GET['q'] : ""); 22 - $tags = htmlspecialchars(isset($_GET['tag']) ? $_GET['tag'] : ""); 23 - $page = filter_var(isset($_GET['page']) ? $_GET['page'] : 1, FILTER_VALIDATE_INT); 24 - $oldest = filter_var(isset($_GET['oldest']) ? $_GET['oldest'] : false, FILTER_VALIDATE_BOOLEAN); 25 - $giveStats = filter_var(isset($_GET['stats']) ? true : false, FILTER_VALIDATE_BOOLEAN); 26 - 27 - if ($page < 0 || $page === 0) { 28 - $page = 1; 29 - } 30 - 31 - /// defining stuff lmao 32 - $maxResults = (!empty($searchQuery)) ? 10 : 20; 33 - $pageNumber = ($page - 1) * $maxResults; 34 - $sqlQuery = "SELECT * FROM issues"; 35 - $params = array(); 36 - $conditions = array(); 37 - 38 - 39 - /// if there is an search query in the request 40 - if (!empty($searchQuery)) { 41 - $searchQuery = strtolower($searchQuery); 42 - if (strpos($searchQuery, 'cusa') !== false) { 43 - $conditions[] = "code LIKE :searchQuery"; 44 - } else { 45 - $conditions[] = "title LIKE :searchQuery"; 46 - } 47 - $params[':searchQuery'] = $searchQuery . '%'; 48 - } 49 - 50 - 51 - /// if there are tags in the request 52 - if (!empty($tags)) { 53 - $tagFilters = explode(',', $tags); 54 - $tagConditions = []; 55 - $tagParams = []; 56 - 57 - foreach ($tagFilters as $index => $tag) { 58 - $tagParam = ":tagFilter{$index}"; 59 - $tagConditions[] = "(CONCAT(',', tags, ',') LIKE CONCAT('%,', {$tagParam}, ',%'))"; 60 - $tagParams[$tagParam] = "{$tag}"; 61 - } 62 - 63 - if (!empty($tagConditions)) { 64 - $conditions[] = "(" . implode(" OR ", $tagConditions) . ")"; 65 - $params = array_merge($params, $tagParams); 66 - } 67 - } 68 - 69 - if (!empty($conditions)) { 70 - $sqlQuery .= " WHERE " . implode(" AND ", $conditions); 71 - } 72 - 73 - 74 - /// gets the total amount of issues based on the search query 75 - $sqlQueryForTotal = "SELECT COUNT(*) AS total FROM ($sqlQuery) AS totalIssues"; 76 - $stmtTotal = $conn->prepare($sqlQueryForTotal); 77 - 78 - foreach ($params as $param => &$value) { 79 - $stmtTotal->bindParam($param, $value, PDO::PARAM_STR); 80 - } 81 - 82 - $stmtTotal->execute(); 83 - $totalIssuesAmount = $stmtTotal->fetch(PDO::FETCH_ASSOC)['total']; 84 - $totalPages = ceil($totalIssuesAmount / $maxResults); 85 - 86 - 87 - /// forming and executing sql query 88 - $sqlQuery .= " ORDER BY id " . ($oldest ? "ASC" : "DESC"); 89 - $sqlQuery .= " LIMIT :offset, :limit"; 90 - $params[':offset'] = $pageNumber; 91 - $params[':limit'] = $maxResults; 92 - 93 - $stmt = $conn->prepare($sqlQuery); 94 - 95 - foreach ($params as $param => &$value) { 96 - if (is_int($value)) { 97 - $stmt->bindParam($param, $value, PDO::PARAM_INT); 98 - } else { 99 - $stmt->bindParam($param, $value, PDO::PARAM_STR); 100 - } 101 - } 102 - 103 - $stmt->execute(); 104 - $result = $stmt->fetchAll(PDO::FETCH_ASSOC); 105 - 106 - 107 - /// Outputing results 108 - header('Content-Type: application/json'); 109 - 110 - $games = array(); 111 - $stats = array(); 112 - 113 - foreach ($result as $game) { 114 - $cusaCode = $game['code']; 115 - $image = $game['type'] === "HB" ? (file_exists("$dir/public_html/_images/HB/{$game['title']}.avif") ? true : false) : (file_exists("$dir/public_html/_images/CUSA/{$cusaCode}.avif") ? true : false); 116 - $games[] = array( 117 - "id" => $game['id'], 118 - "title" => $game['title'], 119 - "code" => $game['code'], 120 - "type" => $game['type'], 121 - "tag" => $game['tags'], 122 - "upDate" => $game['updatedDate'], 123 - "image" => $image 124 - ); 125 - } 126 - 127 - /// stats on the compatibility list 128 - if ($giveStats === true) { 129 - $availableTags = ['Nothing', 'Boots', 'Menus', 'Ingame', 'Playable']; 130 - $tagPercentages = []; 131 - 132 - $naTag = "N/A"; 133 - $stmt = $conn->prepare("SELECT COUNT(*) AS count FROM issues WHERE tags = :tag"); 134 - $stmt->bindParam(':tag', $naTag, PDO::PARAM_STR); 135 - $stmt->execute(); 136 - $naCount = $stmt->fetch(PDO::FETCH_ASSOC)['count']; 137 - 138 - $result = $conn->query("SELECT COUNT(*) FROM issues"); 139 - $total = $result->fetchColumn(); 140 - 141 - $totalIssuesWithoutNA = $total - $naCount; 142 - 143 - foreach ($availableTags as $tag) { 144 - $stmt = $conn->prepare("SELECT COUNT(*) AS count FROM issues WHERE tags = :tag"); 145 - $stmt->bindParam(':tag', $tag, PDO::PARAM_STR); 146 - $stmt->execute(); 147 - 148 - $count = $stmt->fetch(PDO::FETCH_ASSOC)['count']; 149 - $percentage = ($total > 0) ? ($count / $total) * 100 : 0; 150 - $tagPercentages[$tag] = $percentage; 151 - $tagCount[$tag] = $count; 152 - } 153 - 154 - $amount = (100 - array_sum($tagPercentages)) / count($tagPercentages); 155 - 156 - foreach ($availableTags as $tag) { 157 - $percentage = $tagPercentages[$tag]; 158 - $count = $tagCount[$tag]; 159 - $percentage += $amount; 160 - $percentage = round($percentage, 2); 161 - $stats[] = array( 162 - "tag" => $tag, 163 - "percent" => $percentage, 164 - "count" => $count 165 - ); 166 - } 167 - } 168 - 169 - $executionTime = round((microtime(true) - $startTime) * 1000, 2); // Convert to milliseconds 170 - 171 - $info = array( 172 - "issues" => $totalIssuesAmount, 173 - "pages" => $totalPages, 174 - "time" => $executionTime 175 - ); 176 - 177 - $data = array( 178 - "info" => $info, 179 - "games" => $games, 180 - "stats" => $stats, 181 - ); 182 - 183 - 184 - /// end 185 - $jsonData = json_encode($data); 186 - // $jsonData = json_encode($data, JSON_PRETTY_PRINT); 187 - echo $jsonData; 188 - $conn = null; //exit connection 189 - ?>
-336
public_html/_scripts/updater.php
··· 1 - <?php 2 - $startTime = microtime(true); 3 - echo "Started on: " . date("H:i d/m/Y") . "<br><br>"; 4 - $dir = dirname(__DIR__, 2); 5 - require_once "$dir/_config/config.php"; // import config file 6 - 7 - if ((isset($_GET[ARGUMENT_SECRET]) && $_GET[ARGUMENT_SECRET] === ACCESS_SECRET) || (isset($argv[1]) && $argv[1] === ARGUMENT_SECRET)) { 8 - // echo"<style>@import url('https://fonts.googleapis.com/css2?family=Rubik:wght@400;500&display=swap'); * {font-family: 'Rubik', sans-serif;font-weight: 400;}</style>"; 9 - function ErrorHandler($errLvl, $errMesg, $errFile, $errLine) { 10 - $position = strpos($errMesg, "/"); 11 - $errMesg = $position !== false ? substr($errMesg,0, $position) : $errMesg; // check for an "/" and remove stuff 12 - 13 - switch ($errLvl) { 14 - case E_ERROR || E_USER_ERROR: 15 - $error = "Error: $errMesg [line: $errLine]"; 16 - break; 17 - case E_WARNING || E_USER_WARNING: 18 - $error = "Warning: $errMesg [line: $errLine]"; 19 - break; 20 - case E_NOTICE || E_USER_NOTICE: 21 - $error = "Notice: $errMesg [line: $errLine]"; 22 - break; 23 - default: 24 - $error = "Unknown error: $errMesg [line: $errLine]"; 25 - break; 26 - } 27 - $fullError = "<span style='font-weight: 500;'>Something went wrong => $error </span><br>"; 28 - throw new Exception("$fullError"); 29 - } 30 - set_error_handler("ErrorHandler"); 31 - 32 - $host = DATABASE_HOST; 33 - $database = DATABASE_NAME; 34 - $username = DATABASE_USERNAME; 35 - $password = DATABASE_PASSWORD; 36 - 37 - $githubToken = GITHUB_TOKEN; 38 - $tmdbHash = TMDB_HASH; 39 - $HBheader = stream_context_create(["http" => ["header" => "User-Agent: " . HB_USERAGENT ."\r\n"]]); 40 - 41 - // get homebrew db for images 42 - try { 43 - if (date("i") > 58 || date("i") < 3) { 44 - $response = file_get_contents("https://api.pkg-zone.com/api.php?db_check_hash=true", false, $HBheader); 45 - $hash = json_decode($response)->hash; //get hash from json 46 - $md5Hash = file_exists("$dir/_config/HBstore.db") ? md5_file("$dir/_config/HBstore.db") : "0"; 47 - 48 - if ($hash !== $md5Hash) { 49 - $fileContent = file_get_contents("https://api.pkg-zone.com/store.db", false, $HBheader); 50 - file_put_contents("$dir/_config/HBstore.db", $fileContent); 51 - } 52 - } 53 - } catch (Exception $e) { 54 - print($e); 55 - file_exists("$dir/_config/HBstore.db") ? print("Ignoring for now <br>") : die("Critical error, stopping <br>"); 56 - 57 - } finally {print("Homebrew database download task "); date("i") > 58 || date("i") < 3 ? print("completed! <br>") : print("skipped! <br>");} 58 - 59 - 60 - // database connection and table creation 61 - try { 62 - $homebrewDB = new PDO("sqlite:$dir/_config/HBstore.db"); 63 - $conn = new PDO("mysql:host=$host;dbname=$database", $username, $password); 64 - $conn->query("DROP TABLE IF EXISTS newIssues"); 65 - 66 - // creating tables 67 - $sqlQuerys = [ 68 - "CREATE TABLE IF NOT EXISTS issues ( 69 - id INT(5) PRIMARY KEY, 70 - code VARCHAR(10), 71 - title VARCHAR(130), 72 - tags VARCHAR(200), 73 - type VARCHAR(6), 74 - updatedDate VARCHAR(12), 75 - createdDate VARCHAR(12) 76 - )", 77 - "CREATE TABLE IF NOT EXISTS newIssues ( 78 - id INT(5) PRIMARY KEY, 79 - code VARCHAR(10), 80 - title VARCHAR(130), 81 - tags VARCHAR(200), 82 - type VARCHAR(6), 83 - updatedDate VARCHAR(12), 84 - createdDate VARCHAR(12) 85 - )", 86 - "CREATE TABLE IF NOT EXISTS GameSkips ( 87 - code VARCHAR(130) PRIMARY KEY 88 - )" 89 - ]; 90 - 91 - foreach ($sqlQuerys as $sql) { 92 - $conn->exec($sql); 93 - } 94 - 95 - } catch (Exception $e) { 96 - print($e); 97 - die("Critical error, stopping <br>"); 98 - } finally {echo"Database connection and table creation task completed! <br>";} 99 - 100 - 101 - function githubRequest(string $token, int $count, string $url) { 102 - $curl_handle = curl_init(); 103 - curl_setopt($curl_handle, CURLOPT_URL, $url); 104 - curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Accept: application/vnd.github+json', 'Authorization: Bearer '.$token)); 105 - curl_setopt($curl_handle, CURLOPT_USERAGENT, "fpPS4.net"); 106 - curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1); 107 - $data = curl_exec($curl_handle); 108 - curl_close($curl_handle); 109 - 110 - $count++; 111 - return json_decode($data, true); 112 - } 113 - 114 - function imageDownloader(string $url, $header, string $location) { 115 - $httpsUrl = str_replace('http://', 'https://', $url); 116 - $imageData = file_get_contents($httpsUrl, false, $header); 117 - 118 - $imagick = new Imagick(); 119 - if ($imagick->readImageBlob($imageData)) { 120 - $imageFormat = $imagick->getImageFormat(); 121 - if ($imageFormat == 'JPEG' || $imageFormat == 'PNG') { 122 - $imagick->setImageFormat('avif'); 123 - $imagick->setCompressionQuality(75); 124 - $imagick->setImageProperty('avif:effort', '10'); 125 - $imagick->setImageProperty('avif:speed', '1'); 126 - $imagick->thumbnailImage(256, 256, true); 127 - $imagick->stripImage(); 128 - $imagick->writeImage($location); // save the image 129 - } 130 - } 131 - $imagick->clear(); 132 - $imagick->destroy(); 133 - } 134 - 135 - try { 136 - $ghRequests = 0; 137 - $issueCount = githubRequest($githubToken, $ghRequests, "https://api.github.com/repos/red-prig/fpps4-game-compatibility")['open_issues_count']; 138 - $pageCount = ceil($issueCount / 100); 139 - echo "Total Issues: " . $issueCount . "<br>"; 140 - echo "Total Pages: " . $pageCount . "<br><br>"; 141 - $issuesProcessed = 0; 142 - $noImage = 0; 143 - 144 - for ($page = 1; $page <= $pageCount; $page++) { 145 - $issues = githubRequest($githubToken, $ghRequests, "https://api.github.com/repos/red-prig/fpps4-game-compatibility/issues?page=$page&per_page=100&state=open&direction=ASC"); 146 - 147 - foreach ($issues as $issue) { 148 - $id = filter_var($issue['number'], FILTER_VALIDATE_INT); 149 - $title = htmlspecialchars($issue['title']); 150 - $tags = htmlspecialchars(implode(', ', array_column($issue['labels'], 'name'))); 151 - $createdDate = htmlspecialchars(date('d/m/Y', strtotime($issue['created_at']))); 152 - $updatedDate = htmlspecialchars(date('d/m/Y', strtotime($issue['updated_at']))); 153 - $code = ""; 154 - $type = ""; 155 - 156 - // skip invalid issues 157 - if (str_contains($tags,"question") || str_contains($tags,"invalid")) { 158 - continue; 159 - } 160 - 161 - // look for an code in the title, for example: CUSA12345 or NPXS00000 and do some processing 162 - preg_match('/[a-zA-Z]{4}[0-9]{5}/', $title, $matches); 163 - if ($matches) { 164 - str_starts_with($matches[0], "CUSA") ? ($code = $matches[0]) && ($type = "CUSA") : ""; 165 - str_contains($tags, "app-homebrew") ? ($code = $matches[0]) && ($type = "HB") : ""; 166 - str_contains($tags, "app-system-fw505") ? ($code = $matches[0]) && ($type = "SYS") : ""; 167 - str_contains($tags, "app-ps2game") ? ($code = "PS2 GAME") : ""; 168 - $title = str_replace(["- " . $matches[0], "-" . $matches[0], $matches[0]], "", $title); 169 - } else { 170 - str_contains($tags, "app-homebrew") ? ($code = "HOMEBREW") && ($type = "HB") : ""; 171 - } 172 - 173 - $title = str_ireplace(["(Homebrew)", "- HOMEBREW", "Homebrew", "[]"], "", $title); 174 - $title = rtrim($title, " -"); 175 - 176 - // if (empty($code)) { 177 - // echo "skipping: $title <br>"; 178 - // continue; 179 - // } 180 - $issuesProcessed++; 181 - 182 - // Now put it into a database :3 183 - try { 184 - 185 - // tag bullshit AHHHHH 186 - if ($tags) { 187 - $tags = str_ireplace(["status-", "app-homebrew", "app-system-fw505", "app-ps2game"], '', $tags); 188 - 189 - $priority = [ 190 - 'playable', 191 - 'ingame', 192 - 'menus', 193 - 'boots', 194 - 'nothing' 195 - ]; 196 - 197 - $tagArray = array_map('trim', explode(', ', $tags)); 198 - 199 - // headache, prioritizes tags 200 - usort($tagArray, function ($a, $b) use ($priority) { 201 - $priorityA = array_search($a, $priority); 202 - $priorityB = array_search($b, $priority); 203 - 204 - $priorityA = ($priorityA === false) ? 0 : $priorityA; 205 - $priorityB = ($priorityB === false) ? 0 : $priorityB; 206 - 207 - return $priorityB <=> $priorityA; 208 - }); 209 - 210 - // Take the best tag and capitalize it 211 - // $highestPriority = ucfirst($tagArray[0]); 212 - $tagArray = array_slice($tagArray, 0, 1); 213 - 214 - // // Remove tags that are in the priority array 215 - // $tagArray = array_filter($tagArray, function ($tag) use ($priority) { 216 - // return !in_array($tag, $priority); 217 - // }); 218 - 219 - // // Add the best tag to the beginning of the array 220 - // array_unshift($tagArray, $highestPriority); 221 - 222 - $tags = ucfirst(implode(', ', $tagArray)); 223 - 224 - } else {$tags = "N/A";} 225 - 226 - 227 - $insertQuery = "INSERT INTO newIssues (id, code, title, tags, type, updatedDate, createdDate) VALUES (?, ?, ?, ?, ?, ?, ?)"; 228 - $stmt = $conn->prepare($insertQuery); 229 - 230 - $stmt->execute([$id, $code, $title, $tags, $type, $updatedDate, $createdDate]); 231 - } catch (Exception $e) { 232 - echo("Error inserting issue: $e"); 233 - } 234 - 235 - 236 - // Now download images :3 237 - try { 238 - $cusaDir = "$dir/public_html/_images/CUSA/$code.avif"; 239 - $hbDir = "$dir/public_html/_images/HB/$title.avif"; 240 - 241 - if ($type === "CUSA" && !file_exists($cusaDir)) { 242 - $stmt = $conn->prepare('SELECT * FROM GameSkips WHERE code = :code COLLATE utf8mb4_general_ci'); // check if it needs to be skipped 243 - $stmt->execute(['code' => $code]); 244 - $result = $stmt->fetch(PDO::FETCH_ASSOC); 245 - 246 - if(!$result) 247 - { 248 - $key = hex2bin($tmdbHash); 249 - $hash = strtoupper(hash_hmac("sha1", "{$code}_00", $key)); 250 - $url = "https://tmdb.np.dl.playstation.net/tmdb2/{$code}_00_{$hash}/{$code}_00.json"; 251 - $userAgent = "Mozilla/5.0 (PlayStation; PlayStation 4/11.00) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15"; 252 - $PS4header = stream_context_create(["http" => ["header" => "User-Agent: $userAgent\r\n"]]); // ps4 useragent cuz im silly 253 - $response = file_get_contents($url, false, $PS4header); 254 - $data = json_decode($response, true); 255 - $url = $data['icons'][0]['icon']; 256 - 257 - usleep(rand(200000, 500000)); // delay between 200 - 500 ms 258 - imageDownloader($url, $PS4header, $cusaDir); 259 - } 260 - } else if ($type === "HB" && !file_exists($hbDir)) { 261 - $stmt = $conn->prepare('SELECT * FROM GameSkips WHERE code = :title COLLATE utf8mb4_general_ci'); // check if it needs to be skipped 262 - $stmt->execute(['title' => $title]); 263 - $result = $stmt->fetch(PDO::FETCH_ASSOC); 264 - 265 - if(!$result) { 266 - $stmt = $homebrewDB->prepare('SELECT image FROM homebrews WHERE name = :title COLLATE NOCASE'); 267 - $stmt->execute(['title' => $title]); 268 - $url = $stmt->fetch()[0]; 269 - 270 - usleep(rand(200000, 500000)); // delay between 200 - 500 ms 271 - imageDownloader($url, $HBheader, $hbDir); 272 - } 273 - } 274 - } catch (Exception $e) { 275 - $noImage++; 276 - echo "No image found for: $title <br>"; 277 - 278 - // insert issue in database to be skipped. 279 - $insertQuery = "INSERT INTO GameSkips (code) VALUES (:value1)"; 280 - $stmt = $conn->prepare($insertQuery); 281 - 282 - if ($type === "CUSA") { 283 - $stmt->execute(['value1' => $code]); 284 - } else if ($type === "HB") { 285 - $stmt->execute(['value1' => $title]); 286 - } 287 - } 288 - } 289 - } 290 - } catch (Exception $e) { 291 - echo $e; 292 - } finally { 293 - print("<br>$issuesProcessed issues processed! <br>"); 294 - print("$noImage images not found! <br>"); 295 - print("Issue processing, Image downloading and Database insertion task completed! <br>"); 296 - } 297 - 298 - // Move newIssues table to normal issues table 299 - try { 300 - // Check the newIssues table 301 - $result = $conn->query("SELECT COUNT(*) FROM newIssues"); 302 - $newTotal = $result->fetchColumn(); 303 - 304 - $result2 = $conn->query("SELECT COUNT(*) FROM issues"); 305 - $oldTotal = $result2->fetchColumn(); 306 - 307 - // remove 5% from old issue cound 308 - $minNumber = $oldTotal - (0.05 * $oldTotal); 309 - 310 - if ($newTotal > $minNumber) { 311 - // empty issues table 312 - $conn->query("DELETE FROM issues"); 313 - 314 - // copy newIssues to issues table 315 - $conn->query("INSERT INTO issues (id, code, title, tags, type, updatedDate, createdDate) SELECT id, code, title, tags, type, updatedDate, createdDate FROM newIssues"); 316 - 317 - // drop newIssues table 318 - $conn->query("DROP TABLE IF EXISTS newIssues"); 319 - } else { 320 - print "<br>Something is wrong with newIssues, skipping update."; 321 - } 322 - 323 - } catch (Exception $e) { 324 - echo $e; 325 - } finally {print("Database transfer task completed! <br>");} 326 - 327 - 328 - $conn = null; 329 - $homebrewDB = null; 330 - echo "<br>" . round((microtime(true) - $startTime) * 1000, 2) . "ms"; 331 - 332 - } else { 333 - http_response_code(404); 334 - include("$dir/document_errors/404.html"); 335 - } 336 - ?>
+186 -130
public_html/compatibility/app.js
··· 3 3 let imageLoading = true; 4 4 let oldestFilter = false; 5 5 let datesButton = false; 6 - let tagFilter = []; 7 - let pageNumber = 1; 6 + let statusFilter = []; 7 + let currentPage = 1; 8 + 9 + let issuesPerPage = 20; 10 + const codeRegex = /[a-zA-Z]{4}[0-9]{5}/; 8 11 9 12 let Timer; 10 13 let totalPages; 11 - let totalTime; 12 14 let totalIssues; 13 15 let fancyJsonData; // :3 14 16 15 - 16 - // Fetch html for footer and header 17 - async function fetchHtml(path, target){ 18 - fetch(path).then(response => response.text()).then(data => { 19 - document.querySelector(target).innerHTML = data; 20 - }).catch(console.error); 21 - } 22 - 23 - 24 17 // Check Avif Support 25 18 console.log(`Hey there! I've implemented an avif support check to spare browsers like Edge from having a stroke :D!`); 26 19 const avif = new Image(); ··· 32 25 avif.onerror = function() { 33 26 console.log('AVIF IS NOT SUPPORTED D:'); 34 27 avifSupport = false; 35 - // window.alert("Hey! Your browser doesnt support avif, avif is an image format that has low file sizes while having high quality images. You won't get any game images"); 28 + // window.alert("Hey! Your browser doesn't support avif, avif is an image format that has low file sizes while having high quality images. You won't get any game images"); 36 29 const iButton = document.getElementById('imageButton'); 37 30 console.log(iButton); 38 31 iButton.classList.add('selected'); ··· 42 35 43 36 44 37 45 - // game cards and stats handler and other onload stuff 46 - document.addEventListener('DOMContentLoaded', function() { 38 + // game cards and stats handler and other on-load stuff 39 + document.addEventListener('DOMContentLoaded', async function () { 40 + await init() 41 + 47 42 // + check cookies 48 - const imageLoadingCookie = checkCookies("imageLoadingSetting", "true"); 43 + const imageLoadingCookie = checkCookies("imageLoadingSetting", "true"); 49 44 const datesSetting = checkCookies("datesSetting", "false"); 50 45 51 46 if (imageLoadingCookie === "false") { ··· 62 57 window.addEventListener('load', adjustScreenSize); 63 58 window.addEventListener('resize', adjustScreenSize); 64 59 65 - 60 + 66 61 // + fetch issues and set the tag bars 67 - fetch('/_scripts/search.php?q=&stats') 68 - .then(response => response.json()) 69 - .then(jsonData => { 70 - gameCardHandler(jsonData); 71 - fancyJsonData = jsonData; 72 - pageButtonHandler(); 62 + fetch('https://api.fpps4.net/database.json') 63 + .then(response => response.json()) 64 + .then(jsonData => { 65 + fancyJsonData = jsonData; 73 66 74 - totalPages = jsonData.info.pages; 75 - totalTime = jsonData.info.time; 76 - totalIssues = jsonData.info.issues; 77 - console.log("\nCOMPATIBILITY STATS"); 67 + totalIssues = jsonData.length; 68 + totalPages = Math.ceil(totalIssues / issuesPerPage); 78 69 79 - // stats & tag filter 80 - jsonData.stats.forEach(stat => { 81 - var tag = stat.tag; 82 - var percent = stat.percent; 83 - var element = document.getElementById(tag + 'Bar'); 84 - var textElement = document.getElementById(tag + 'Info') 85 - var parentElement = element.parentElement; 86 - console.log(`${tag} = ${percent}% [${stat.count}]`); 87 - element.style.width = percent + '%'; 88 - textElement.textContent = percent + '% - ' + stat.count; 89 - 90 - parentElement.addEventListener('click', function() { 91 - parentElement.classList.toggle('selected'); 92 - tagFilter.includes(tag) ? tagFilter.splice(tagFilter.indexOf(tag), 1) : tagFilter.push(tag); 93 - pageNumber = 1; 94 - updateSearchResults(); 95 - }); 96 - }); 97 - console.log("\n"); 98 - }) 99 - .catch(console.error); 100 - }); 70 + console.log("\nCOMPATIBILITY STATS"); 101 71 72 + let availableStatus = ['Nothing', 'Boots', 'Menus', 'Ingame', 'Playable']; 73 + let totalPercentage = 0; 102 74 103 - // Header & Footer Load 104 - fetchHtml("/_parts/navbar.html", "#header"); 105 - fetchHtml("/_parts/footer.html", "#footer"); 75 + let statusPercentages = []; 76 + let statusCount = []; 77 + 78 + availableStatus.forEach(status => { 79 + statusCount[status] = 0; // init tag count 80 + 81 + jsonData.forEach(issue => { 82 + if (issue.status === status) { 83 + statusCount[status]++; 84 + } 85 + }); 86 + 87 + let rawPercentage = parseFloat((statusCount[status] / totalIssues * 100).toFixed(2)); 88 + 89 + statusPercentages[status] = rawPercentage; 90 + totalPercentage += rawPercentage; 91 + }); 92 + 93 + // stats & tag filter 94 + availableStatus.forEach(status => { 95 + let percent = statusPercentages[status]; 96 + let count = statusCount[status]; 97 + let element = document.getElementById(status + 'Bar'); 98 + let textElement = document.getElementById(status + 'Info') 99 + let parentElement = element.parentElement; 100 + console.log(`${status} = ${percent}% [${count}]`); 101 + element.style.width = percent + '%'; 102 + textElement.textContent = percent + '% - ' + count; 103 + 104 + parentElement.addEventListener('click', function () { 105 + parentElement.classList.toggle('selected'); 106 + statusFilter.includes(status) ? statusFilter.splice(statusFilter.indexOf(status), 1) : statusFilter.push(status); 107 + currentPage = 1; 108 + updateSearchResults(); 109 + }); 110 + }); 111 + console.log("\n"); 112 + 113 + gameCardHandler(jsonData.slice(0, issuesPerPage)); //first 20 114 + pageButtonHandler(); 115 + 116 + }) 117 + .catch(console.error); 118 + }); 106 119 107 120 108 121 // Searching 109 122 document.querySelector('#search').addEventListener('input', function() { 110 - pageNumber = 1; 123 + currentPage = 1; 111 124 updateSearchResults(); 112 125 }); 113 126 114 - async function updateSearchResults() { 127 + function updateSearchResults() { 115 128 clearTimeout(Timer); 116 129 const gameWrapper = document.querySelector('#gameWrapper'); 117 - const searchQuery = document.querySelector('#search').value; 118 - pageButtonHandler(); 130 + const searchQuery = document.querySelector('#search').value.toLowerCase(); 119 131 120 132 gameWrapper.querySelectorAll('.gameContainer').forEach(container => { 121 133 const skeletonDiv = document.createElement('div'); 122 134 skeletonDiv.classList.add('gameContainer', 'skeletonAnimation'); 123 135 gameWrapper.replaceChild(skeletonDiv, container); 124 136 }); 125 - 137 + 126 138 Timer = setTimeout(() => { 127 - fetch('/_scripts/search.php?q=' + searchQuery + '&tag=' + tagFilter + '&page=' + pageNumber + '&oldest=' + oldestFilter) 128 - .then(response => response.json()) 129 - .then(jsonData => { 130 - fancyJsonData = jsonData; 131 - gameCardHandler(jsonData); 132 - }) 133 - .catch(console.error); 139 + let jsonData = []; 140 + let isCodeSearch = codeRegex.test(searchQuery); 141 + 142 + fancyJsonData.forEach(issue => { 143 + // id based searching 144 + if (isCodeSearch && (issue.code.toLowerCase() !== searchQuery)) { 145 + return; 146 + 147 + // title based searching 148 + } else if (!isCodeSearch && !issue.title.toLowerCase().includes(searchQuery)) { 149 + return; 150 + } 151 + 152 + // filter tags 153 + if (statusFilter.length > 0) { 154 + let isGood = false; 155 + 156 + for (const status of statusFilter) { 157 + if (status === issue.status) { 158 + isGood = true; 159 + break; 160 + } 161 + } 162 + 163 + if (isGood === false) { 164 + return; 165 + } 166 + } 167 + 168 + jsonData.push(issue); 169 + }); 170 + 171 + 172 + let startSlice = (currentPage - 1) * issuesPerPage; // makes it start on 0 173 + let endSlice = startSlice + issuesPerPage; 174 + 175 + totalPages = Math.ceil(jsonData.length / issuesPerPage); 176 + totalIssues = jsonData.length; 177 + 178 + let tempJsonData; 179 + 180 + if (oldestFilter === true) { 181 + tempJsonData = jsonData.reverse(); 182 + } else { 183 + tempJsonData = jsonData; 184 + } 185 + 186 + gameCardHandler(tempJsonData.slice(startSlice, endSlice)); 187 + pageButtonHandler(); 188 + 134 189 }, 300); 135 190 } 136 191 137 192 // Game Card handler 138 - async function gameCardHandler(jsonData) { 193 + function gameCardHandler(jsonData) { 139 194 const gameWrapper = document.getElementById("gameWrapper"); 140 195 gameWrapper.innerHTML = ""; 141 196 142 - jsonData.games.forEach(game => { 197 + jsonData.forEach(issue => { 143 198 // game image URL 144 199 let imageSource = ""; 145 - let imageText = "N/A"; 146 - let imageTextSize = 1.38; 200 + let imageText = "GAME"; 201 + let imageTextSize = 1.25; 147 202 203 + if (issue.title === "Sonic Time Twisted") { 204 + console.log(issue.issue_type) 205 + } 206 + 148 207 switch(true) { 149 - case game.image && imageLoading && avifSupport && game.type === "HB": 150 - imageSource = "/_images/HB/" + game.title + ".avif"; 208 + case issue.image && imageLoading && avifSupport && issue.issue_type === "Homebrew": 209 + imageSource = "https://api.fpps4.net/images/homebrew/" + issue.title + ".avif"; 151 210 break; 152 - case game.image && imageLoading && avifSupport: 153 - imageSource = "/_images/CUSA/" + game.code +".avif"; 211 + case issue.image && imageLoading && avifSupport: 212 + imageSource = "https://api.fpps4.net/images/game/" + issue.code +".avif"; 154 213 break; 155 - case game.type === "SYS": 214 + case issue.issue_type === "SystemFwUnknown" || issue.issue_type === "SystemFw505": 156 215 imageText = "SYSTEM"; 157 216 imageTextSize = 1.13; 158 217 break 159 218 } 160 219 161 - if (game.type === "HB") { // needs to be applied to all homebrews 220 + if (issue.issue_type === "Homebrew") { // needs to be applied to all homebrews 162 221 imageText = "HOME<br>BREW"; 163 222 imageTextSize = 1.25; 223 + if (issue.code === "") { 224 + issue.code = "HOMEBREW"; 225 + } 164 226 } 165 227 166 - let imageTextEnabled = game.image && imageLoading && avifSupport ? "none" : "flex"; 167 - 228 + let imageTextEnabled = issue.image && imageLoading && avifSupport ? "none" : "flex"; 229 + let updated = new Date(issue.updated).toLocaleDateString() 230 + 231 + 168 232 // game cards 169 233 const gameElementHTML = ` 170 234 <div class="gameContainer"> 171 - <a class="gameImageLink" target="_blank" href="https://github.com/red-prig/fpps4-game-compatibility/issues/${game.id}"> 235 + <a class="gameImageLink" target="_blank" href="https://github.com/red-prig/fpps4-game-compatibility/issues/${issue.id}"> 172 236 <p class="gameImageText" style="font-size: ${imageTextSize}rem; display: ${imageTextEnabled};">${imageText}</p> 173 - ${imageSource ? `<img class="gameImage" loading="lazy" alt="${game.title} - ${game.code} game image" src="${imageSource}">` : "" } 237 + ${imageSource ? `<img class="gameImage" loading="lazy" alt="${issue.title} - ${issue.code} game image" src="${imageSource}">` : "" } 174 238 </a> 175 - <div class="gameSeparator ${game.tag}"></div> 239 + <div class="gameSeparator ${issue.status}"></div> 176 240 <div class="gameDetails"> 177 - <p class="gameName">${game.title}</p> 178 - <p class="gameCusa" data-date="${game.upDate}" data-cusa="${game.code}">${game.code}</p> 179 - <p class="gameStatus ${game.tag}">${game.tag}</p> 241 + <p class="gameName">${issue.title}</p> 242 + <p class="gameCusa" data-date="${updated}" data-cusa="${issue.code}">${issue.code}</p> 243 + <p class="gameStatus ${issue.status}">${issue.status}</p> 180 244 </div> 181 245 </div>`; 182 - 246 + 183 247 const tempContainer = document.createElement('div'); 184 248 tempContainer.innerHTML = gameElementHTML; 185 249 const gameContainer = tempContainer.querySelector('.gameContainer'); ··· 187 251 }); 188 252 189 253 const statContainer = document.createElement('h4'); 190 - const stat = `<h4 class="totalTimeText">${jsonData.info.issues} results in ${jsonData.info.time}ms </h4>`; 191 - statContainer.innerHTML = stat; 254 + statContainer.innerHTML = `<h4 class="totalTimeText">${totalIssues} results </h4>`; 192 255 gameWrapper.appendChild(statContainer); 256 + 193 257 if (datesButton) { 194 258 dateButtonHandler(); 195 259 } ··· 207 271 image.style.transform = 'scale(1)'; 208 272 }); 209 273 }); 274 + 210 275 // functions in required.js 211 276 updateFooter(); 212 - LightModeIconChange(); 213 277 } 214 278 215 279 ··· 239 303 } 240 304 241 305 242 - async function imageButtonHandler() { 306 + function imageButtonHandler() { 243 307 if (imageLoading === false) { 244 308 document.querySelectorAll(".gameImageText").forEach(imageText => { 245 309 imageText.style.display = "flex"; 246 310 }); 247 311 } else { 248 - gameCardHandler(fancyJsonData); 312 + updateSearchResults(); 249 313 } 250 314 } 251 315 252 316 253 - async function dateButtonHandler() { 317 + function dateButtonHandler() { 254 318 document.querySelectorAll(".gameCusa").forEach(element => { 255 319 if (datesButton === true) { 256 320 element.textContent = element.dataset.date; ··· 262 326 263 327 function pageButtonHandler(type) { 264 328 // maybe make it handle everything in once? 265 - const maxNumber = fancyJsonData.info.pages; 329 + const maxNumber = totalPages; 266 330 const searchBar = document.getElementById("search3"); 267 331 const minButton = document.getElementById("pageBarMin"); 268 332 const maxButton = document.getElementById("pageBarMax"); 269 333 270 - if (pageNumber != maxNumber) { 334 + maxButton.textContent = maxNumber; 335 + 336 + if (currentPage != maxNumber) { 271 337 if (type === "forward") { 272 - pageNumber += 1; 338 + currentPage += 1; 273 339 updateSearchResults(); 274 340 } else if (type === "max") { 275 - pageNumber = maxNumber; 341 + currentPage = maxNumber; 276 342 updateSearchResults(); 277 343 } 278 344 } 279 345 280 - if (pageNumber != 1) { 346 + if (currentPage != 1) { 281 347 if (type === "back") { 282 - pageNumber -= 1; 348 + currentPage -= 1; 283 349 updateSearchResults(); 284 350 } else if (type === "min") { 285 - pageNumber = 1; 351 + currentPage = 1; 286 352 updateSearchResults(); 287 353 } 288 354 } 289 355 290 356 if (type === "input") { 291 357 clearTimeout(Timer); 292 - inputValue = parseInt(search3.value, 10); 358 + 293 359 Timer = setTimeout(() => { 294 - 360 + inputValue = parseInt(searchBar.value); 295 361 console.log(inputValue); 296 - pageNumber = inputValue; 297 - updateSearchResults(); 298 - searchBar.value = ""; 299 - // if (inputValue > maxNumber) { 300 - // pageNumber = inputValue; 301 - // updateSearchResults(); 302 - // searchBar.value = ""; 303 - // } else { 304 - // pageNumber = maxNumber; 305 - // updateSearchResults(); 306 - // searchBar.value = ""; 307 - // } 308 - }, 600); 362 + 363 + if (inputValue > maxNumber) { 364 + inputValue = maxNumber; 365 + } else if (inputValue < 1) { 366 + inputValue = 1; 367 + } 368 + 369 + currentPage = inputValue; 370 + searchBar.placeholder = currentPage; 371 + searchBar.value = ""; 372 + updateSearchResults(); 373 + }, 400); 309 374 } 310 375 311 - maxButton.textContent = maxNumber; 312 - // searchBar.value = ""; 376 + 313 377 314 - if (pageNumber != 1 && pageNumber != maxNumber) { 315 - searchBar.placeholder = pageNumber; 378 + if (currentPage !== 1 && currentPage !== maxNumber) { 379 + searchBar.placeholder = currentPage; 316 380 searchBar.classList.add("selected"); 317 381 maxButton.classList.remove("selected"); 318 382 minButton.classList.remove("selected"); 319 - } else if (pageNumber === 1) { 383 + } else if (currentPage === 1) { 320 384 minButton.classList.add("selected"); 321 385 maxButton.classList.remove("selected"); 322 386 searchBar.classList.remove("selected"); 323 387 searchBar.placeholder = "..."; 324 - searchBar.value = ""; 325 - } else if (pageNumber === maxNumber) { 388 + } else if (currentPage === maxNumber) { 326 389 minButton.classList.remove("selected"); 327 390 maxButton.classList.add("selected"); 328 391 searchBar.classList.remove("selected"); 329 392 searchBar.placeholder = "..."; 330 - searchBar.value = ""; 331 393 } 332 394 } 333 395 334 - search3.addEventListener("click", async function() { 396 + search3.addEventListener("click", function() { 335 397 if (search3.placeholder == '...') { 336 - search3.placeholder = ''; 398 + search3.placeholder = ''; 337 399 } 338 400 }); 339 401 340 - search3.addEventListener("blur", async function() { 402 + search3.addEventListener("blur", function() { 341 403 if (search3.placeholder == '') { 342 - search3.placeholder = '...'; 404 + search3.placeholder = '...'; 343 405 } 344 - }); 345 - 346 - // search3.addEventListener('input', async function() { 347 - // clearTimeout(Timer); 348 - // inputValue = parseInt(search3.value, 10); 349 - // console.log(inputValue); 350 - // }); 406 + });
+16 -20
public_html/compatibility/index.html
··· 8 8 <meta property="title" content="Game Compatibility List"> 9 9 <meta property="og:title" content="fpPS4 - Game Compatibility List"> 10 10 <meta property="og:description" content="These are the games that have been tested with fpPS4"> 11 - <meta property="og:image" content="https://fpps4.net/images/fpPS4Logo.png"> 11 + <meta property="og:image" content="https://fpps4.net/_images/fpPS4Logo512.png"> 12 12 <meta property="og:url" content="https://fpps4.net/compatibility"> 13 13 <meta name="description" content="These are the games that have been tested with fpPS4"> 14 14 <meta name="theme-color" content="#4C566A"> ··· 16 16 <meta name="keywords" content="fpPS4, playstation, ps4, playstation 4, emulator, windows, open source, free pascal, compatibility layer, red-prig, compatibility list, compatibility, fpps4 compatibility list, fpps4 compatibility, fpps4 game compatibility"> 17 17 <meta name="robots" content="index,follow"> 18 18 <meta name="author" content="Mr. Snowy"> 19 - <link rel="stylesheet" href="./styles.css"> 20 - <link rel="icon" href="/_images/fpPS4Logo.png" type="image/png"> 21 - </head> 22 - <style> 23 - /* fixes page load stuff */ 24 - .progressBarInfo {opacity: 0;} 25 - </style> 26 - <body> 27 - <header id="header"> 28 19 <style> 29 20 header { 30 21 position: fixed; 31 22 height: 3rem; 32 23 } 24 + footer { 25 + height: 3.65rem; 26 + margin-top: 3rem; 27 + } 28 + /* fixes page load stuff */ 29 + .progressBarInfo {opacity: 0;} 33 30 </style> 31 + <link rel="stylesheet" href="./styles.css"> 32 + <link rel="icon" href="/_images/fpPS4Logo.png" type="image/png"> 33 + <script defer src="/_parts/required.js"></script> 34 + <script defer src="./app.js"></script> 35 + </head> 36 + <body> 37 + <header id="header"> 34 38 </header> 35 39 <main class="main"> 36 40 <h1 class="mainText">Game Compatibility List</h1> 37 - <h3 class="smolMainText">These are the games that have been tested with <span style="color: #f0e74a;">fp</span>PS4.<br>Click on an image to view the game's GitHub issue.<br>The database is updated every 20 minutes.</h3> 41 + <h3 class="smolMainText">These are the games that have been tested with <span style="color: #f0e74a;">fp</span>PS4.<br>Click on an image to view the game's GitHub issue.<br>The database is updated every 10 minutes.</h3> 38 42 <div class=progressContainer> 39 43 <div class="progressWrap"><span class="progressBarInfo" id="PlayableInfo"></span><span class="progressBarText">Playable</span><div class="progressBar" id="PlayableBar" style="background:#54A396;"></div></div> 40 44 <div class="progressRow"> ··· 77 81 <div class="gameContainer skeletonAnimation"></div> 78 82 <div class="gameContainer skeletonAnimation"></div> 79 83 <div class="gameContainer skeletonAnimation"></div> 80 - <h4 class="totalTimeText">0 results in 0ms </h4> 84 + <h4 class="totalTimeText">0 results</h4> 81 85 </div> 82 86 <div class="pageBarContainer"> 83 87 <img class="pageBarImage" id="pageBarBack" onclick="pageButtonHandler('back')" src="/_images/arrow.svg" alt=""> ··· 90 94 <br> 91 95 </main> 92 96 <footer id="footer"> 93 - <style> 94 - footer { 95 - height: 3.65rem; 96 - margin-top: 3rem; 97 - } 98 - </style> 99 97 </footer> 100 - <script defer src="/_parts/required.js"></script> 101 - <script defer src="./app.js"></script> 102 98 </body> 103 99 </html>
+6 -4
public_html/compatibility/styles.css
··· 232 232 } 233 233 234 234 /*+ GAME CARDS*/ 235 - 236 235 .gameWrapper {width: 95%;} 237 236 238 237 .gameContainer { ··· 274 273 max-width: 5.3rem; 275 274 border-radius: 0.5rem 0.4rem 0.4rem 0.5rem; 276 275 transition: transform 0.2s ease-in-out; 277 - image-rendering: -webkit-optimize-contrast; 276 + image-rendering: high-quality; 278 277 } 279 278 280 279 .gameImageText { ··· 328 327 margin-top: 0.94rem; 329 328 background-color: var(--status-color); 330 329 border-radius: 0.25rem; 331 - text-align: center; 332 - line-height: 1.5rem; 333 330 user-select: none; 334 331 color: var(--whiteText); 332 + font-weight: 500; 333 + display: flex; 334 + align-items: center; 335 + text-align: center; 336 + justify-content: center; 335 337 } 336 338 /*- GAME CARDS*/ 337 339
+50 -50
public_html/index.html
··· 13 13 --> 14 14 <!DOCTYPE html> 15 15 <html lang="en"> 16 - <head> 17 - <meta charset="UTF-8"> 18 - <meta http-equiv="X-UA-Compatible" content="IE=edge"> 19 - <meta name="viewport" content="width=device-width, initial-scale=1"> 20 - <title>fpPS4</title> 21 - <meta name="description" content="fpPS4 is an open source PS4 compatibility layer (emulator) written with Free Pascal for Windows."> 22 - <meta name="keywords" content="fpPS4, playstation, ps4, playstation 4, emulator, windows, open source, free pascal, compatibility layer, red-prig, fpps4 github, fpps4 discord"> 23 - <meta name="robots" content="index,follow"> 24 - <meta name="author" content="Mr. Snowy"> 25 - <meta name="copyright" content="fpPS4"> 26 - <meta name="theme-color" content="#4C566A"> 27 - <meta property="og:title" content="fpPS4 - An open source PS4 compatibility layer"> 28 - <meta property="og:description" content="fpPS4 is an open source PS4 compatibility layer written with Free Pascal for Windows."> 29 - <meta property="og:image" content="https://fpps4.net/images/fpPS4Logo.png"> 30 - <meta property="og:url" content="https://fpps4.net"> 31 - <link rel="stylesheet" href="./style.css?v=01"> 32 - <link rel="stylesheet" href="./sizes.css"> 33 - <link rel="icon" href="/_images/fpPS4Logo.png" type="image/png"> 34 - </head> 35 - <body> 36 - <div class="top"> 37 - <div class="top-text-container"> 38 - <h1 class="coming-soon">Work In Progress</h1> 39 - <h1 class="main-text"><span style="color: #f0e74a;">fp</span>PS4</h1> 40 - <p class="progress-text">The offical website for the open-source<br>compatibility layer fpPS4</p> 41 - <div class="topButtonContainer"> 42 - <a href="#learnmore" onclick="scrollToElement(event, 'learnmore')" class="button">Learn more</a> 43 - <a href="https://fpps4.net/compatibility" class="button">Compatibility</a> 44 - </div> 45 - </div> 16 + <head> 17 + <meta charset="UTF-8"> 18 + <meta http-equiv="X-UA-Compatible" content="IE=edge"> 19 + <meta name="viewport" content="width=device-width, initial-scale=1"> 20 + <title>fpPS4</title> 21 + <meta name="description" content="fpPS4 is an open source PS4 compatibility layer (emulator) written with Free Pascal for Windows."> 22 + <meta name="keywords" content="fpPS4, playstation, ps4, playstation 4, emulator, windows, open source, free pascal, compatibility layer, red-prig, fpps4 github, fpps4 discord"> 23 + <meta name="robots" content="index,follow"> 24 + <meta name="author" content="Mr. Snowy"> 25 + <meta name="copyright" content="fpPS4"> 26 + <meta name="theme-color" content="#4C566A"> 27 + <meta property="og:title" content="fpPS4 - An open source PS4 compatibility layer"> 28 + <meta property="og:description" content="fpPS4 is an open source PS4 compatibility layer written with Free Pascal for Windows."> 29 + <meta property="og:image" content="https://fpps4.net/_images/fpPS4Logo512.png"> 30 + <meta property="og:url" content="https://fpps4.net"> 31 + <link rel="stylesheet" href="./style.css?v=01"> 32 + <link rel="stylesheet" href="./sizes.css"> 33 + <link rel="icon" href="/_images/fpPS4Logo.png" type="image/png"> 34 + </head> 35 + <body> 36 + <div class="top"> 37 + <div class="top-text-container"> 38 + <h1 class="coming-soon">Work In Progress</h1> 39 + <h1 class="main-text"><span style="color: #f0e74a;">fp</span>PS4</h1> 40 + <p class="progress-text">The offical website for the open-source<br>compatibility layer fpPS4</p> 41 + <div class="topButtonContainer"> 42 + <a href="#learnmore" onclick="scrollToElement(event, 'learnmore')" class="button">Learn more</a> 43 + <a href="https://fpps4.net/compatibility/" class="button">Compatibility</a> 46 44 </div> 47 - <div class="bottom-container" id="learnmore"> 48 - <div class="bottom-left"> 49 - <h1 class="bottom-top-text"><span style="color: #f0e74a;">fp</span>PS4</h1> 50 - <p class="bottom-desc-text">An open-source PS4 compatibility layer (emulator) written with Free Pascal</p> 51 - <div class="buttons-container"> 52 - <a href="https://github.com/red-prig/fpPS4" target="_blank" class="button">Github</a> 53 - <a href="https://discord.gg/up9qatpX7M" target="_blank" class="button">Discord</a> 54 - </div> 55 - </div> 56 - <div class="bottom-right"> 57 - <h1 class="bottom-top-text">Special thanks to:</h1> 58 - <div class="thank-user-container"> 59 - <a href="https://github.com/red-prig" target="_blank"><span style="font-weight: 500;">Red-Prig</span> - For developing <span style="color: #f0e74a;">fp</span>PS4</a> 60 - <a href="https://github.com/georgemoralis" target="_blank"><span style="font-weight: 500;">georgemoralis</span> - For donating the domain</a> 61 - <a href="https://github.com/KimieStar" target="_blank"><span style="font-weight: 500;">KimieStar</span> - For helping with the website</a> 62 - <a href="https://github.com/MrSn0wy" target="_blank"><span style="font-weight: 500;">Mr. Snowy</span> - For making the website :3</a> 63 - </div> 64 - </div> 45 + </div> 46 + </div> 47 + <div class="bottom-container" id="learnmore"> 48 + <div class="bottom-left"> 49 + <h1 class="bottom-top-text"><span style="color: #f0e74a;">fp</span>PS4</h1> 50 + <p class="bottom-desc-text">An open-source PS4 compatibility layer (emulator) written with Free Pascal</p> 51 + <div class="buttons-container"> 52 + <a href="https://github.com/red-prig/fpPS4" target="_blank" class="button">Github</a> 53 + <a href="https://discord.gg/up9qatpX7M" target="_blank" class="button">Discord</a> 54 + </div> 55 + </div> 56 + <div class="bottom-right"> 57 + <h1 class="bottom-top-text">Special thanks to:</h1> 58 + <div class="thank-user-container"> 59 + <a href="https://github.com/red-prig" target="_blank"><span style="font-weight: 500;">Red-Prig</span> - For developing <span style="color: #f0e74a;">fp</span>PS4</a> 60 + <a href="https://github.com/georgemoralis" target="_blank"><span style="font-weight: 500;">georgemoralis</span> - For donating the domain</a> 61 + <a href="https://github.com/KimieStar" target="_blank"><span style="font-weight: 500;">KimieStar</span> - For helping with the website</a> 62 + <a href="https://github.com/MrSn0wy" target="_blank"><span style="font-weight: 500;">Mr. Snowy</span> - For making the website :3</a> 65 63 </div> 66 - </body> 67 - <script src="./app.js"></script> 64 + </div> 65 + </div> 66 + </body> 67 + <script src="./app.js"></script> 68 68 </html>
+1 -1
public_html/style.css
··· 4 4 width: 0.57rem;} 5 5 6 6 ::-webkit-scrollbar-thumb { 7 - background: var(--hover); 7 + background: var(--hover); 8 8 border-radius: 0.13rem;} 9 9 10 10 ::-webkit-scrollbar-thumb:hover {