├── _src ├── base │ ├── gotham │ │ ├── css │ │ │ ├── README.md │ │ │ ├── psdle.min.css │ │ │ └── psdle.css │ │ ├── lang │ │ │ ├── README.md │ │ │ ├── lang.min.json │ │ │ └── all │ │ │ │ └── en.json │ │ ├── psdle.gotham.min.js │ │ ├── psdle.base.js │ │ └── psdle.gotham.includes.js │ └── valkyrie │ │ ├── lang │ │ ├── all │ │ │ ├── pl.json │ │ │ ├── fr.json │ │ │ ├── ja.json │ │ │ ├── es.json │ │ │ ├── pt.json │ │ │ ├── ar.json │ │ │ ├── ru.json │ │ │ ├── nl.json │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── zh.json │ │ └── lang.min.json │ │ └── css │ │ ├── psdle.min.css │ │ └── psdle.css ├── chrome │ ├── psdle │ │ ├── js │ │ │ ├── psdleChromium.js │ │ │ └── chrome.js │ │ ├── icons │ │ │ ├── icon128.png │ │ │ ├── icon32.png │ │ │ └── icon64.png │ │ ├── _locales │ │ │ └── en │ │ │ │ └── messages.json │ │ ├── popup.html │ │ └── manifest.json │ ├── deploy.bat │ ├── 7-Zip.bat │ └── README.md ├── README.md └── psdle.user.txt ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── bug-report--importing-and-exporting.md ├── logo ├── 4_psdle.png ├── 5_psdle_big.png ├── 2_psdle_tiny.png ├── 3_psdle_mini.png ├── 6_psdle_64px.png ├── 7_psdle_128px.png ├── 8_psdle_3000.png ├── 1_psdle_do_not_even.png ├── mockups │ ├── mockup_avatar.png │ ├── mockup_firefox.png │ └── mockup_header.svg ├── PSDLE.svg └── README.md ├── deploy-sync.bat ├── .gitignore ├── package.json ├── LICENSE ├── README.md └── Gruntfile.js /_src/base/gotham/css/README.md: -------------------------------------------------------------------------------- 1 | `grunt cssmin` 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: http://repod.github.io/psdle/#support 2 | -------------------------------------------------------------------------------- /logo/4_psdle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/4_psdle.png -------------------------------------------------------------------------------- /logo/5_psdle_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/5_psdle_big.png -------------------------------------------------------------------------------- /logo/2_psdle_tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/2_psdle_tiny.png -------------------------------------------------------------------------------- /logo/3_psdle_mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/3_psdle_mini.png -------------------------------------------------------------------------------- /logo/6_psdle_64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/6_psdle_64px.png -------------------------------------------------------------------------------- /logo/7_psdle_128px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/7_psdle_128px.png -------------------------------------------------------------------------------- /logo/8_psdle_3000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/8_psdle_3000.png -------------------------------------------------------------------------------- /_src/chrome/psdle/js/psdleChromium.js: -------------------------------------------------------------------------------- 1 | var psdleChromium=true; 2 | var psdleSkip=true; // Valkyrie / 3.x -------------------------------------------------------------------------------- /logo/1_psdle_do_not_even.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/1_psdle_do_not_even.png -------------------------------------------------------------------------------- /logo/mockups/mockup_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/mockups/mockup_avatar.png -------------------------------------------------------------------------------- /logo/mockups/mockup_firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/logo/mockups/mockup_firefox.png -------------------------------------------------------------------------------- /_src/chrome/psdle/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/_src/chrome/psdle/icons/icon128.png -------------------------------------------------------------------------------- /_src/chrome/psdle/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/_src/chrome/psdle/icons/icon32.png -------------------------------------------------------------------------------- /_src/chrome/psdle/icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RePod/psdle/HEAD/_src/chrome/psdle/icons/icon64.png -------------------------------------------------------------------------------- /deploy-sync.bat: -------------------------------------------------------------------------------- 1 | git init 2 | git checkout master && git commit -a -m "Deployment" && git push 3 | 4 | git checkout gh-pages && sync.bat -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Chrome Extension 2 | _src/chrome/psdle/js/psdle.*.js 3 | *.pem 4 | *.crx 5 | # Firefox Add-on 6 | *.xpi 7 | !psdle.xpi 8 | 9 | # Etc. 10 | Thumbs.db 11 | .DS_Store 12 | images/* 13 | *.zip 14 | 15 | # Node 16 | node_modules/* -------------------------------------------------------------------------------- /_src/chrome/deploy.bat: -------------------------------------------------------------------------------- 1 | ::Open the chrome extension editing page on webstore. 2 | explorer "https://chrome.google.com/webstore/developer/edit/jdjhhapoddhnimgdemnpbfagndcnmhii" 3 | explorer "https://addons.mozilla.org/en-US/developers/addon/psdleforfirefox/versions/submit/" -------------------------------------------------------------------------------- /_src/chrome/7-Zip.bat: -------------------------------------------------------------------------------- 1 | ::Old method, pull in min then rename it post-zip. 2 | ::"C:\Program Files\7-Zip\7z.exe" a -uq0 -mx9 %~dp0/psdle.zip -mtc- %~dp0/psdle/* %~dp0../../psdle.min.js -r -x!*.db -x!*.ini 3 | ::"C:\Program Files\7-Zip\7z.exe" rn %~dp0/psdle.zip psdle.min.js js\psdle.js 4 | 5 | ::Assuming symlink: mklink /H "_src/chrome/psdle/js/psdle.js" "psdle.min.js" 6 | "%PROGRAMFILES%\7-Zip\7z.exe" a -uq0 -mx9 %~dp0/psdle.zip -mtc- %~dp0/psdle/* -r -x!*.db -x!*.ini -------------------------------------------------------------------------------- /_src/base/gotham/lang/README.md: -------------------------------------------------------------------------------- 1 | Language files go into the ***all/*** directory. 2 | 3 | - Use existing languages as a template 4 | - Place new language in folder or update existing in place 5 | - File name must be the language code (English = `en.json`) 6 | - Manually run **`grunt minlang`** to compile them into ***lang.min.json*** 7 | - ***lang.min.json*** is ready to be used in other Grunt tasks 8 | 9 | Do not manually update ***lang.min.json***. 10 | 11 | If submitting a language via PR ignore the Grunt tasks, just send the PR with the langauge file in the right place. 12 | -------------------------------------------------------------------------------- /_src/base/gotham/lang/lang.min.json: -------------------------------------------------------------------------------- 1 | {"en":{"def":"us","us":{"author":"","local":"English","labels":{"exportView":"Export View","exportEmpty":"..?","deleteData":"Delete user data","catalogEnable":"Enable Catalog","website":"Website"},"messages":{"catalogFirstRun":"Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.","exportNoProps":"The following properties may not currently exist and could export as nothing, continue?\nThese may require running Catalog first."}}}} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Browser:** (Firefox 57/Chrome #/etc.) 11 | 12 | **Method:** (Direct/Userscript/Bookmarklet/Extension) 13 | 14 | **Online store version and language:** (Desktop/mobile) (en-us/de-de/etc) 15 | 16 | **PSDLE version:** x.x.x 17 | 18 | **Monkey variant:** (Greasemonkey/Violentmonkey/etc.) (if applicable) 19 | 20 | **Is Catalog enabled:** (No, by default/Yes) 21 | 22 | --- 23 | 24 | **Problem:** 25 | 26 | **How to replicate:** 27 | 28 | **Other notes:** 29 | -------------------------------------------------------------------------------- /_src/README.md: -------------------------------------------------------------------------------- 1 | - ***psdle.base.js*** is the main version of PSDLE where edits should occur. 2 | - After making the needed edits, run `grunt compile` to generate ***psdle.includes.js***. 3 | - Run `grunt includes:build` to skip CSS/lang minify, ensure they're already minified first! 4 | - ***psdle.includes.js*** is the resulting complete PSDLE before uglifying. 5 | - ***psdle.user.txt*** is the Userscript header appended to the ***includes*** version for `grunt release`. 6 | 7 | Versioning is done automatically to all versions both in banners and version properties (chrome/userscript, `grunt string-replace:release`) based on ***package.json***. 8 | -------------------------------------------------------------------------------- /_src/chrome/psdle/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "PSDLE", 4 | "description": "PSDLE, simple." 5 | }, 6 | "appDesc": { 7 | "message": "Improving everyone's favorite online download list, one loop at a time.", 8 | "description": "Product description." 9 | }, 10 | "website": { 11 | "message": "Website", 12 | "description": "Website link text in the popup." 13 | }, 14 | "repo": { 15 | "message": "Repository", 16 | "description": "Repository link text in the popup." 17 | }, 18 | "pageActionRun": { 19 | "message": "PSDLE", 20 | "description": "Text for the page action's tooltip indicating to run (start/execute/launch) PSDLE." 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /_src/base/gotham/lang/all/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "us", 3 | "us": { 4 | "author": "", 5 | "local": "English", 6 | "labels": { 7 | "exportView": "Export View", 8 | "exportEmpty": "..?", 9 | "deleteData": "Delete user data", 10 | "catalogEnable": "Enable Catalog", 11 | "website": "Website" 12 | }, 13 | "messages": { 14 | "catalogFirstRun": "Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.", 15 | "exportNoProps": "The following properties may not currently exist and could export as nothing, continue?\nThese may require running Catalog first." 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "psdle", 3 | "version": "4.2.2", 4 | "description": "Improving everyone's favorite online download list, one loop at a time.", 5 | "scripts": {}, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/RePod/psdle.git" 9 | }, 10 | "author": "RePod", 11 | "license": "(c) RePod, MIT https://github.com/RePod/psdle/blob/master/LICENSE", 12 | "bugs": { 13 | "url": "https://github.com/RePod/psdle/issues" 14 | }, 15 | "homepage": "https://github.com/RePod/psdle#readme", 16 | "devDependencies": { 17 | "grunt": "^1.6.1", 18 | "grunt-concat-json": "0.0.10", 19 | "grunt-contrib-concat": "^1.0.1", 20 | "grunt-contrib-copy": "^1.0.0", 21 | "grunt-contrib-cssmin": "^2.2.1", 22 | "grunt-contrib-uglify-es": "^3.3.0", 23 | "grunt-exec": "^3.0.0", 24 | "grunt-includes": "^1.1.0", 25 | "grunt-string-replace": "^1.3.1", 26 | "install": "^0.10.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /logo/PSDLE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /_src/chrome/psdle/js/chrome.js: -------------------------------------------------------------------------------- 1 | console.info("PSDLE Chrome | *radio static* I'm in",window,document) 2 | 3 | const URLS = { 4 | VALKYRIE: "transact.playstation.com", 5 | GOTHAM: "library.playstation.com" 6 | } 7 | var isValkyrie = !!location.hostname.match(URLS.VALKYRIE) 8 | var variant = isValkyrie ? "valkyrie" : "gotham" 9 | 10 | function injectScript (src) { 11 | const s = document.createElement('script'); 12 | s.src = chrome.runtime.getURL(src); 13 | s.onload = () => s.remove(); 14 | (document.head || document.documentElement).append(s); 15 | } 16 | 17 | if (!isValkyrie) { 18 | var qualityElementDetector5000 = setInterval(function() { 19 | if (document.querySelector(".psw-drawer")) { 20 | clearInterval(qualityElementDetector5000) 21 | document.querySelector(".psw-drawer").style['max-width'] = '95%' 22 | document.querySelector(".psw-drawer").style['min-width'] = '95%' 23 | document.querySelector(".psw-drawer").style['width'] = '100%' 24 | } 25 | }, 1000) 26 | } 27 | 28 | injectScript(`js/psdle.${variant}.js`) -------------------------------------------------------------------------------- /_src/psdle.user.txt: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @author RePod 3 | // @name PSDLE for Greasemonkey ({variant}) 4 | // @description Improving everyone's favorite online download list, one loop at a time. 5 | // @namespace https://github.com/RePod/psdle 6 | // @homepage https://repod.github.io/psdle/ 7 | // @version 4.2.2 8 | // @include /https://store.playstation.com/*/ 9 | // @include /https://library.playstation.com/*/ 10 | // @exclude /https://store.playstation.com/(cam|liquid)/*/ 11 | // @updateURL https://repod.github.io/psdle/psdle.{variant}.user.js 12 | // @downloadURL https://repod.github.io/psdle/psdle.{variant}.user.js 13 | // @icon https://repod.github.io/psdle/logo/6_psdle_64px.png 14 | // @grant none 15 | // @noframes 16 | // ==/UserScript== 17 | 18 | /* 19 | 20 | NOTE: The Valkyrie and Gotham (old/new) store Userscripts are separate (psdle.valkyrie.user.js, psdle.gotham.user.js). 21 | 22 | To keep this from updating remove the @updateURL (for automatic updates) and @downloadURL (for manual updates) above. 23 | Alternatively, reconfigure the updating settings in your Userscript manager. 24 | 25 | */ 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2024 RePod 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /_src/chrome/README.md: -------------------------------------------------------------------------------- 1 | Looking for the official versions? 2 | - [Chrome extension](https://chrome.google.com/webstore/detail/psdle/jdjhhapoddhnimgdemnpbfagndcnmhii) 3 | - [Firefox extension](https://addons.mozilla.org/firefox/addon/psdleforfirefox/) 4 | 5 | ---- 6 | 7 | *All instances of file/folder structure are relative to this README.* 8 | 9 | Requisites 10 | ---- 11 | - A local [clone of the repository.](//github.com/RePod/psdle/archive/master.zip) 12 | - Chrome (latest **stable** preferred), or Firefox 57 or newer. 13 | 14 | Accessing Extensions 15 | ---- 16 | - **≡** ➤ More tools ➤ Extensions 17 | - [chrome://extensions/](chrome://extensions/) 18 | 19 | Loading the unpacked Extension in Chrome 20 | ---- 21 | 1. Place "*psdle.js*" in "*psdle/js/*". 22 | 2. [Access Extensions.](#accessing-extensions) 23 | 3. Enable "*Developer mode*", if not already enabled. (upper-right corner) 24 | 4. Click "*Load unpacked extension...*" 25 | 5. Locate and select the "*psdle*" folder. 26 | 27 | Loading the unpacked Extension in Firefox 28 | ---- 29 | 1. Place "*psdle.js*" in "*psdle/js/*". 30 | 2. Navigate to `about:debugging`. 31 | 3. Locate and load the `manifest.json` in the "*psdle*" extension folder. 32 | 33 | 34 | Packing the Extension 35 | ---- 36 | 37 | 1. Don't pack it. 38 | 39 | **The CRX and XPI files are no longer provided**. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report--importing-and-exporting.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Bug report: Importing and Exporting' 3 | about: Problems importing/exporting to third party software. 4 | title: I submitted an issue without erasing the default title 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | If you are a user having trouble with third party software importing PSDLE's list, contact the developer (and send them here). 11 | If you are said developer, read on. 12 | If you are neither, please erase this box and elaborate. 13 | 14 | --- 15 | 16 | As of PSDLE version 3.3.13 (May 31st 2019) the export's configuration has been updated. 17 | Prior versions were arrays with children containing the target property and column title as strings: 18 | 19 | ['prettyDate','Date']. 20 | 21 | The children are now objects with property and title: 22 | 23 | {property:"prettyDate",title:"Date"} 24 | 25 | All JSON exports will also now include a version property on the top level of PSDLE's version. 26 | Regular/CSV exports will now include the same version number in the last row's last column (defined by separator). 27 | 28 | These are intended to allow version-aware import handling in the event of futures changes, although it's highly recommended you and your users are always using the latest. For pre-3.3.13 handling, detect the absence of a version string or regular array children. 29 | -------------------------------------------------------------------------------- /_src/chrome/psdle/popup.html: -------------------------------------------------------------------------------- 1 | 23 |
24 |
25 | Library (PS4/PS5) 26 | 39 |
-------------------------------------------------------------------------------- /logo/README.md: -------------------------------------------------------------------------------- 1 | [![](1_psdle_do_not_even.png) 22x8](1_psdle_do_not_even.png) 2 | 3 | [![](2_psdle_tiny.png) 42x16](2_psdle_tiny.png) 4 | 5 | [![](3_psdle_mini.png) 84x61](3_psdle_mini.png) (common) 6 | 7 | [![](4_psdle.png) 167x62](4_psdle.png) (native resolution) 8 | 9 | Larger: 10 | - [1670x260](5_psdle_big.png) 11 | - [3000x1114](8_psdle_3000.png) 12 | - [SVG](PSDLE.svg) 13 | 14 | ---- 15 | 16 | Interacting with the SVG 17 | ---- 18 | - [Inkscape](https://inkscape.org/) 19 | - ... 20 | 21 | Contributing to the SVG 22 | ---- 23 | Those wanting to improve it (since the current one is a rough first attempt) are encouraged to. 24 | However, SVGs must be optimized to only contain the minimum amount of information to display the image. 25 | 26 | *If using Inkscape:* 27 | - File 28 | - Save as... 29 | - *Save as type:* Optimized SVG (*.svg) 30 | 31 | Match the following options below: 32 | 33 | - [x] Shorten color values 34 | - [x] Convert CSS attributes to XML attributes 35 | - [x] Group collapsing 36 | - [x] Create groups for similar attributes 37 | - [ ] Embed rasters (should not be using any) 38 | - [ ] Keep editor data 39 | - [x] Remove metadata 40 | - [x] Remove comments 41 | - [x] Work around renderer bugs 42 | - [ ] Enable viewboxing 43 | - [ ] Remove the xml declaration 44 | 45 | *Number of significant digits for coords:* 5 46 | *XML indentation (pretty-printing):* Space 47 | -------------------------------------------------------------------------------- /_src/base/gotham/css/psdle.min.css: -------------------------------------------------------------------------------- 1 | .psdle{--blue:#2185f4;--darker-blue:#063f7e;--bg-hover-filters:#e8e8e8;--psdle-logo-clear:url("data:image/webp;base64,UklGRnIAAABXRUJQVlA4TGUAAAAvU4AHEC9ApG1T/27Hzm6DINum/pwjuMAFBEX/R0OQbTOk+dMM4QYP8H8MbVsBRZEkNXMOAiAACUjAv6wMr3tG9H8Ckn3bhE1lUwEFhE0Nr2LzH4TNGLN5v5VtPgibV5YaT27fVgA=");--thin-padding:8px 16px}.psdle-logo{margin:0 auto;display:block;width:84px;height:31px;background-image:var(--psdle-logo-clear);background-color:var(--blue)}.psdle-filter-button{cursor:pointer}#psdle-configurator button{text-align:left;width:100%;padding:var(--thin-padding);font-weight:400!important;font-size:1rem}#psdle-configurator button:not(:last-child){border-bottom:.0625rem solid #dedede}#psdle-configurator,#psdle-filter-section-export{text-align:center;overflow:hidden;background-color:var(--bg-1)}#psdle-filter-section-export input{cursor:text}#psdle-filter-section-export select{border-style:solid;border:none;background-color:var(--bg-1);border-bottom:.0625rem solid #dedede}#psdle-filter-section-export select option{background-color:#fff}#psdle-filter-section-export input,#psdle-filter-section-export select{width:100%;padding:var(--thin-padding)}#psdle-configurator button:hover,#psdle-filter-section-export input:hover,#psdle-filter-section-export select:hover{background-color:var(--bg-hover-filters)}#psdle-filter-section-export button{padding:.2rem .3rem;margin:.3rem}#psdle-filter-section-export button:hover{color:var(--blue);background-color:var(--bg-hover-filters)} -------------------------------------------------------------------------------- /_src/chrome/psdle/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | 4 | "name": "PSDLE", 5 | "short_name": "__MSG_appName__", 6 | "description": "__MSG_appDesc__", 7 | "default_locale": "en", 8 | "homepage_url": "https://repod.github.io/psdle", 9 | 10 | "version": "4.2.2", 11 | 12 | "action": { 13 | "default_icon": "icons/icon32.png", 14 | "default_title": "__MSG_pageActionRun__", 15 | "default_popup": "popup.html" 16 | }, 17 | "permissions": [], 18 | "content_scripts": [ 19 | { 20 | "js": ["js/chrome.js"], 21 | "all_frames": true, 22 | "matches": [ 23 | "https://store.playstation.com/*", 24 | "https://transact.playstation.com/*", 25 | "https://library.playstation.com/*" 26 | ] 27 | } 28 | ], 29 | "web_accessible_resources": [ 30 | { 31 | "resources": ["js/psdle.valkyrie.js"], 32 | "matches": [ 33 | "https://store.playstation.com/*", 34 | "https://transact.playstation.com/*" 35 | ] 36 | }, 37 | { 38 | "resources": ["js/psdle.gotham.js"], 39 | "matches": ["https://library.playstation.com/*"] 40 | } 41 | ], 42 | "icons": { 43 | "16": "icons/icon32.png", 44 | "48": "icons/icon64.png", 45 | "128": "icons/icon128.png" 46 | }, 47 | "browser_specific_settings": { 48 | "gecko": { 49 | "id": "{90d4b3e6-6c8a-46a4-87c7-3bf30d6fa325}", 50 | "strict_min_version": "109.0" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /_src/base/gotham/css/psdle.css: -------------------------------------------------------------------------------- 1 | .psdle { 2 | --blue: #2185f4; /* Iconic. */ 3 | --darker-blue: #063f7e; 4 | --bg-hover-filters: #e8e8e8; /* .collection-filter__label */ 5 | --psdle-logo-clear: url("data:image/webp;base64,UklGRnIAAABXRUJQVlA4TGUAAAAvU4AHEC9ApG1T/27Hzm6DINum/pwjuMAFBEX/R0OQbTOk+dMM4QYP8H8MbVsBRZEkNXMOAiAACUjAv6wMr3tG9H8Ckn3bhE1lUwEFhE0Nr2LzH4TNGLN5v5VtPgibV5YaT27fVgA="); 6 | --thin-padding: 8px 16px; 7 | } 8 | 9 | .psdle-logo { 10 | margin: 0 auto; 11 | display: block; 12 | width: 84px; 13 | height: 31px; 14 | background-image: var(--psdle-logo-clear); 15 | background-color: var(--blue); 16 | } 17 | 18 | .psdle-filter-button { 19 | cursor: pointer 20 | } 21 | 22 | #psdle-configurator button { 23 | text-align: left; 24 | width: 100%; 25 | padding: var(--thin-padding); 26 | font-weight: normal !important; 27 | font-size: 1rem; 28 | } 29 | #psdle-configurator button:not(:last-child) { 30 | border-bottom:.0625rem solid #dedede 31 | } 32 | 33 | #psdle-configurator, 34 | #psdle-filter-section-export { 35 | text-align: center; 36 | overflow: hidden; 37 | background-color: var(--bg-1); 38 | } 39 | 40 | #psdle-filter-section-export input { cursor:text } 41 | #psdle-filter-section-export select { 42 | border-style: solid; 43 | border: none; 44 | background-color: var(--bg-1); 45 | border-bottom: 0.0625rem solid #dedede; 46 | } 47 | #psdle-filter-section-export select option { background-color: white } 48 | 49 | #psdle-filter-section-export select, 50 | #psdle-filter-section-export input { 51 | width: 100%; 52 | padding: var(--thin-padding); 53 | } 54 | 55 | #psdle-configurator button:hover, 56 | #psdle-filter-section-export select:hover, 57 | #psdle-filter-section-export input:hover { 58 | background-color: var(--bg-hover-filters) 59 | } 60 | #psdle-filter-section-export button { 61 | padding: 0.2rem 0.3rem; 62 | margin: 0.3rem 63 | } 64 | #psdle-filter-section-export button:hover { 65 | color: var(--blue); 66 | background-color: var(--bg-hover-filters) 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # As of 2025-03-21 development of this project has ceased. 2 | [![psdle](logo/4_psdle.png?raw=true)](//repod.github.io/psdle "To the website!") 3 | ===== 4 | 5 | 6 | Improving everyone's favorite online download list, one loop at a time. 7 | 8 | This is the PSDLE **repository**! Most of the information you'll be looking for is on **[the website.](//repod.github.io/psdle)** 9 | Those with no interest of the inner workings of PSDLE **will not find anything new here**! 10 | However, there's a lot of stuff to read and learn about on **[the wiki!](//github.com/RePod/psdle/wiki)** 11 | 12 | :warning: Third party developers please read [this wiki page.](https://github.com/RePod/psdle/wiki/Third-Party%3A-Exports) 13 | 14 | Usage 15 | ===== 16 | **Normal users:** **[The website](//repod.github.io/psdle)** has instructions on how to get started. 17 | 18 | **Everyone else:** PSDLE uses Node, NPM, and Grunt to compile versions. 19 | 20 | The latest versions (except chrome zip) end up in `_src/base/variant/` and should be the same or newer than the files available from the website (they may even be broken!). 21 | 22 | Notable Grunt tasks: 23 | - `grunt compile` compiles the base versions in `_src/base/variant/` including the min CSS and languages. 24 | - `grunt` (default) is the same as `grunt compile`. 25 | - `grunt release` generates all the release versions (base, min, user, chrome, firefox) into `_src/base/variant` (except chrome). 26 | - `grunt deploy` as a follow up to clean firefox add-on files, open chrome webstore, then sync to *gh-pages*. 27 | 28 | What Needs To Be Done 29 | ===== 30 | * **[Translations!](https://github.com/RePod/psdle/wiki/Submit-a-Bug-or-Translation)** 31 | * General performance and stability improvements. 32 | * ~~Improve various API accuracy and functionality, including Download Queue.~~ ~~Thanks Valkyrie.~~ Thanks Gotham. 33 | 34 | License 35 | ===== 36 | MIT, have fun. 37 | 38 | **PSDLE is not sponsored, endorsed, or created by Sony, SIE, SNEI, SCEA, SCEE, SCEI, PlayStation, or affiliates.** 39 | **Third-party content (icons, names) re-rendered by PSDLE is provided from the Entitlements API.** 40 | -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/pl.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "pl", 3 | "pl": { 4 | "local": "Polski", 5 | "startup": { 6 | "apis": "Zaznacz wybrane opcje; najedź kursorem, aby uzyskać więcej szczegółów.
Niektóre opcje nie mogą być odznaczone.", 7 | "wait": "Proszę czekać.", 8 | "start": "Start" 9 | }, 10 | "columns": { 11 | "icon": "Ikona", 12 | "name": "Nazwa", 13 | "platform": "Platforma", 14 | "size": "Rozmiar", 15 | "date": "Data" 16 | }, 17 | "labels": { 18 | "exportView": "Eksportuj", 19 | "page": "Strona" 20 | }, 21 | "categories": { 22 | "downloadable_game": "Gry", 23 | "demo": "Dema", 24 | "add_on": "Dodatki", 25 | "unlock": "Odblokowane", 26 | "unlock_key": "Kody", 27 | "avatar": "Awatary", 28 | "theme": "Motywy", 29 | "other": "inne", 30 | "other_game_related": "inne_związane_z_grami", 31 | "game_content": "zawartość_gry", 32 | "tumbler_index": "znacznik", 33 | "home": "dom", 34 | "ungrouped_game": "nieprzypisana_gra", 35 | "promo_content": "zawartość_promocyjna", 36 | "beta": "Bety", 37 | "application": "Aplikacje", 38 | "extras": "Zawartość dodatkowa", 39 | "unknown": "Nieznane" 40 | }, 41 | "strings": { 42 | "delimiter": "Wstaw separator:", 43 | "yes": "Tak", 44 | "no": "Nie", 45 | "search": "Szukaj", 46 | "dlQueue": "Kolejka", 47 | "dlList": "Lista", 48 | "plus": "Zmień widoczność tytułów PS+.", 49 | "queueAll": "Wszystko", 50 | "queueTo": "Pobierz do $SYS$", 51 | "noTarget": "Nie ma dostępnej konsoli, do której można by wysłać zawartość.", 52 | "exportColumnName": "Nazwa Kolumny", 53 | "exportProperty": "Własność", 54 | "exportImport": "Importuj" 55 | }, 56 | "apis": [{ 57 | "internalID": "api_entitle", 58 | "name": "Historia Zakupów", 59 | "desc": "Nie może być odznaczona. Historia zakupów zostanie użyta do stworzenia listy objektów do pobrania i określenia, czy jest to zawartość z PS+." 60 | }, { 61 | "internalID": "api_game", 62 | "name": "Katalog", 63 | "desc": "Zaznaczenie umożliwi podanie dodatkowych informacji, w tym kategorii. Zwiększa czas potrzebny na utworzenie listy." 64 | }, { 65 | "internalID": "api_queue", 66 | "name": "Kolejka pobierania", 67 | "desc": "Umożliwia dodawanie i usuwanie objektów z kolejki pobierania. Odczytuje informacje o stanie kolejki i sprawdza, czy kosola jest aktywna." 68 | }, { 69 | "internalID": "api_pstv", 70 | "name": "PS TV", 71 | "desc": "Wykrywanie tytułów kompatybilnych z PS TV. Dostępne wyłącznie na stronie w języku \"en - us\" (nie w języku PSDLE).", 72 | "disabled": true 73 | }] 74 | } 75 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "fr", 3 | "fr": { 4 | "author": "cramoisan (#9)", 5 | "local": "Français", 6 | "startup": { 7 | "apis": "Sélectionner l'API à utiliser; Survoler pour plus de détails.
Certaines APIs ne peuvent pas être désactivées.", 8 | "wait": "Merci de patienter.", 9 | "start": "Commencer" 10 | }, 11 | "columns": { 12 | "icon": "Icône", 13 | "name": "Nom", 14 | "platform": "Plate-forme", 15 | "size": "Taille", 16 | "date": "Date" 17 | }, 18 | "labels": { 19 | "exportView": "Exporter la vue", 20 | "page": "Page" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Jeux", 24 | "demo": "Démos", 25 | "add_on": "DLCs", 26 | "unlock": "Codes de déverouillage", 27 | "avatar": "Avatars", 28 | "theme": "Thèmes", 29 | "application": "Applications", 30 | "unknown": "Inconnu" 31 | }, 32 | "strings": { 33 | "delimiter": "Entrer le délimiteur:", 34 | "yes": "Oui", 35 | "no": "Non", 36 | "search": "Rechercher", 37 | "dlQueue": "Queue", 38 | "dlList": "Liste", 39 | "plus": "Afficher/cacher les titres PS+.", 40 | "queueAll": "Tous", 41 | "queueTo": "Télécharger sur $SYS$" 42 | }, 43 | "apis": [{ 44 | "internalID": "api_entitle", 45 | "name": "Droits", 46 | "desc": "Ne peut pas être désactivée. Accède aux informations d'achat afin de créer la liste de téléchargement, et déterminer le statut PS+, ainsi que d'autres choses." 47 | }, { 48 | "internalID": "api_game", 49 | "name": "Catalogue", 50 | "desc": "Accède aux informations supplémentaires des jeux pour déterminer la plate-forme, corriger les liens d'images cassés, et plus." 51 | }, { 52 | "internalID": "api_queue", 53 | "name": "Liste de téléchargement", 54 | "desc": "Permet d'ajouter ou de retirer des articles de la liste de téléchargement. Lit les informations de la liste de téléchargement et le nombre de consoles activées sur le compte." 55 | }, { 56 | "internalID": "api_pstv", 57 | "name": "PS TV", 58 | "desc": "Détecte les titres compatibles PS TV. Ne marche que sur le store \"en-us\" (différent de la langue choisie pour PSDLE).", 59 | "disabled": true 60 | }] 61 | } 62 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "jp", 3 | "jp": { 4 | "author": "k0ta0uchi (#36)", 5 | "local": "日本語", 6 | "startup": { 7 | "apis": "使用したいAPIを選択してください。ホバーすることによって詳細を確認することができます。
特定のAPIは無効化することが出来ない可能性があります。", 8 | "wait": "お待ちください。", 9 | "start": "開始" 10 | }, 11 | "columns": { 12 | "icon": "アイコン", 13 | "name": "ゲーム名", 14 | "platform": "プラットフォーム", 15 | "size": "サイズ", 16 | "date": "日付" 17 | }, 18 | "labels": { 19 | "exportView": "ビューをエクスポート", 20 | "page": "ページ" 21 | }, 22 | "categories": { 23 | "downloadable_game": "ゲーム", 24 | "demo": "デモ", 25 | "add_on": "アドオン", 26 | "unlock": "アンロック", 27 | "unlock_key": "アンロックキー", 28 | "avatar": "アバター", 29 | "theme": "テーマ", 30 | "other": "その他", 31 | "other_game_related": "その他ゲーム関連", 32 | "game_content": "ゲームコンテンツ", 33 | "tumbler_index": "タンブラーインデックス", 34 | "home": "ホーム", 35 | "ungrouped_game": "未分類のゲーム", 36 | "promo_content": "プロモコンテンツ", 37 | "beta": "ベータ", 38 | "application": "アプリケーション", 39 | "extras": "エキストラ", 40 | "unknown": "不明" 41 | }, 42 | "strings": { 43 | "delimiter": "区切り文字を入力してください:", 44 | "yes": "はい", 45 | "no": "いいえ", 46 | "search": "検索", 47 | "dlQueue": "待機リスト", 48 | "dlList": "リスト", 49 | "plus": "PS+タイトルの表示を切り替える。", 50 | "queueAll": "全て", 51 | "queueTo": "$SYS$にダウンロード", 52 | "noTarget": "送信可能なコンソールが存在しません。", 53 | "exportColumnName": "カラム名", 54 | "exportProperty": "プロパティ" 55 | }, 56 | "apis": [{ 57 | "internalID": "api_entitle", 58 | "name": "エンタイトルメント", 59 | "desc": "無効化することは出来ません。購入情報にアクセスし、ダウンロードリストを作成、PS+の状態を確認、その他を行います。" 60 | }, { 61 | "internalID": "api_game", 62 | "name": "カタログ", 63 | "desc": "ゲームの追加情報にアクセスし、正確なコンソールの把握、壊れたイメージの修正、その他を行います。" 64 | }, { 65 | "internalID": "api_queue", 66 | "name": "ダウンロード待機リスト", 67 | "desc": "ダウンロード待機リストからアイテムの追加や削除の許可します。ダウンロード待機リスト情報とアカウントで有効化されたコンソールの数を読み込みます。" 68 | }, { 69 | "internalID": "api_pstv", 70 | "name": "PS TV", 71 | "desc": "PS TV互換タイトルを検知します。\"en-us\"ウェブストアでのみサポートされます。(PSDLEの言語設定ではありません)", 72 | "disabled": true 73 | }] 74 | } 75 | } -------------------------------------------------------------------------------- /logo/mockups/mockup_header.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "es", 3 | "es": { 4 | "author": "Positronic-Brain (#18)", 5 | "local": "Español", 6 | "startup": { 7 | "apis": "Elija APIs a utilizar. Coloque el puntero sobre el API para visualizar detalles.
Algunos APIs no pueden ser deshabilitados.", 8 | "wait": "Por favor espere...", 9 | "start": "Inicio" 10 | }, 11 | "columns": { 12 | "icon": "Ícono", 13 | "name": "Nombre", 14 | "platform": "Plataforma", 15 | "size": "Tamaño", 16 | "date": "Fecha" 17 | }, 18 | "labels": { 19 | "exportView": "Exportar vista", 20 | "page": "Página" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Juegos", 24 | "demo": "Demos", 25 | "add_on": "Complementos", 26 | "unlock": "Desbloqueables", 27 | "unlock_key": "Llaves", 28 | "avatar": "Avatares", 29 | "theme": "Temas", 30 | "other": "Otros", 31 | "other_game_related": "Otros", 32 | "game_content": "Contenidos", 33 | "tumbler_index": "tumbler_index", 34 | "home": "Home", 35 | "ungrouped_game": "No Clasificados", 36 | "promo_content": "Promociones", 37 | "beta": "Betas", 38 | "application": "Aplicaciones", 39 | "extras": "Extras", 40 | "unknown": "Desconocido" 41 | }, 42 | "strings": { 43 | "delimiter": "Ingrese delimitador:", 44 | "yes": "Sí", 45 | "no": "No", 46 | "search": "Búsqueda", 47 | "dlQueue": "Cola de Descargas", 48 | "dlList": "Lista de Descargas", 49 | "plus": "Alterna la visibilidad de los títulos de PS Plus.", 50 | "queueAll": "Todos", 51 | "queueTo": "Descargar a $SYS$" 52 | }, 53 | "apis": [{ 54 | "internalID": "api_entitle", 55 | "name": "Licencias", 56 | "desc": "No puede ser deshabilitado. Accede a la información de las compras y se utiliza para construir la lista de descargas, determinar el estado de PS Plus y otras cosas." 57 | }, { 58 | "internalID": "api_game", 59 | "name": "Catálogo", 60 | "desc": "Accede a información adicional para determinar la consola adecuada, reparar imágenes rotas, y más." 61 | }, { 62 | "internalID": "api_queue", 63 | "name": "Cola de Descargas", 64 | "desc": "Permite añadir y remover entradas a la cola de descargas. Lee la información de la cola de descargas y el número de consolas activadas en la cuenta." 65 | }, { 66 | "internalID": "api_pstv", 67 | "name": "PS TV", 68 | "desc": "Detecta títulos compatibles con PS TV. Sólo soportado en la tienda de la región \"en-us\" (región, no idioma de PSDLE).", 69 | "disabled": true 70 | }] 71 | } 72 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "br", 3 | "br": { 4 | "author": "msvalle (#33)", 5 | "local": "Português (Brasil)", 6 | "startup": { 7 | "apis": "Selecione quais APIs você gostaria de usar, passe o mouse por cima para mais detalhes.
Algumas APIs não podem ser desabilitadas.", 8 | "wait": "Por favor aguarde.", 9 | "start": "Iniciar" 10 | }, 11 | "columns": { 12 | "icon": "Ícone", 13 | "name": "Nome", 14 | "platform": "Platforma", 15 | "size": "Tamanho", 16 | "date": "Data" 17 | }, 18 | "labels": { 19 | "exportView": "Exportar Visualização", 20 | "page": "Página" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Jogos", 24 | "demo": "Demos", 25 | "add_on": "Expansões", 26 | "unlock": "Desbloqueáveis", 27 | "unlock_key": "Chaves", 28 | "avatar": "Avatarws", 29 | "theme": "Temas", 30 | "other": "Outros", 31 | "other_game_related": "Outros", 32 | "game_content": "Conteúdo", 33 | "tumbler_index": "tumbler_index", 34 | "home": "Home", 35 | "ungrouped_game": "Não classificado", 36 | "promo_content": "Promoções", 37 | "beta": "Betas", 38 | "application": "Aplicações", 39 | "extras": "Extras", 40 | "unknown": "Desconhecido" 41 | }, 42 | "strings": { 43 | "delimiter": "Entre delimitador:", 44 | "stringify_error": "Erro: Navegador não possui JSON.stringify.", 45 | "yes": "Sim", 46 | "no": "Não", 47 | "search": "Buscar por título do jogo", 48 | "dlQueue": "Fila de downlaod", 49 | "dlList": "Lista de download", 50 | "plus": "Alterna ver títulos PS+.", 51 | "queueAll": "Todos", 52 | "queueTo": "Download para $SYS$" 53 | }, 54 | "apis": [{ 55 | "internalID": "api_entitle", 56 | "name": "Licenças", 57 | "desc": "Não pode ser desabilitado. Acessa informção de compra usada para criar a lista de download, determinar o status da PS+, e outras coisas." 58 | }, { 59 | "internalID": "api_game", 60 | "name": "Catálogo", 61 | "desc": "Acessa informação adicional do jogo para determinar o console certo, corrigir imagens quebradas, e mais." 62 | }, { 63 | "internalID": "api_queue", 64 | "name": "Fila de download", 65 | "desc": "Permite adicionar e remover itens da lista de download. Lê informação da lista de download e quantidade de consoles ativos na conta." 66 | }, { 67 | "internalID": "api_pstv", 68 | "name": "PS TV", 69 | "desc": "Detecta títulos compatíveis com a PS TV. Somente suportado na web store \"en-us\" (não o idioma do PSDLE).", 70 | "disabled": true 71 | }] 72 | } 73 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/ar.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "ae", 3 | "ae": { 4 | "author": "Oakkom", 5 | "rtl": true, 6 | "local": "العربية", 7 | "startup": { 8 | "apis": "أختار الميزات التي تَود استخدامها حَرك المؤشر فوقها للمزيد من المعاومات
بعضُ الميزات لا يُمكن ابطالها", 9 | "wait": "...جَار التَحْميل", 10 | "start": "إبدأ" 11 | }, 12 | "columns": { 13 | "icon": "الأيقونة", 14 | "name": "الأسم", 15 | "platform": "نَوعُ الجهاز", 16 | "size": "الحَجم", 17 | "date": "التاريخ" 18 | }, 19 | "labels": { 20 | "exportView": "أحْفَظ الائحة", 21 | "page": "الصفْحة" 22 | }, 23 | "categories": { 24 | "downloadable_game": "الألعاب", 25 | "demo": "الإصدارات التجريبية", 26 | "add_on": "العَناصِر الأضافية", 27 | "unlock": "Unlocks", 28 | "unlock_key": "Unlock Keys", 29 | "avatar": "صٌور رمزية", 30 | "theme": "السِمات", 31 | "other": "اخرى", 32 | "other_game_related": "ألعاب مُتَعلِقة", 33 | "game_content": "مُحتويات اللعبة", 34 | "tumbler_index": "فَهرس تَمبلر (tumbler)", 35 | "home": "المَنزل", 36 | "ungrouped_game": "العاب غير مصنفة", 37 | "promo_content": "مُحتَويات تَرويجْية", 38 | "beta": "اصْدار تجريبي", 39 | "application": "التَطبيقات", 40 | "extras": "إضافات", 41 | "unknown": "مَجهول" 42 | }, 43 | "strings": { 44 | "delimiter": ":أدخل الفَواصل", 45 | "yes": "نَعم", 46 | "no": "لا", 47 | "search": "أبْحث", 48 | "dlQueue": "لائِحة التَنزيل", 49 | "dlList": "لائِحة الألعاب", 50 | "plus": "ضبط ظاهرية العاب PlayStation Plus", 51 | "queueAll": "الكُل", 52 | "queueTo": "حَمل الى $SYS$", 53 | "noTarget": "لا يوجد جِهاز للتَحميل اِليه", 54 | "exportColumnName": "أِسم العَمود", 55 | "exportProperty": "الخَصائص" 56 | }, 57 | "apis": [{ 58 | "internalID": "api_entitle", 59 | "name": "تَاريخ الشِراء", 60 | "desc": "لا يُمكن عَدم التَفعيل, أِن بَيانات شِرائك تُستَعمل لِيَتم تَشكيل الائِحة و تَحديد وَضع PlayStation Plus" 61 | }, { 62 | "internalID": "api_game", 63 | "name": "الفَهرس", 64 | "desc": ".فَعِلْ لِلحُصول على مَعلومات إضافية عَن الألعاب, مِنها الأنواع. تَزيد الوَقت اللازم لِتحميلِ اللائحة" 65 | }, { 66 | "internalID": "api_queue", 67 | "name": "لائحة التَنزيل", 68 | "desc": ".تَسمح بِزيادة أو حَذف العَناصر مِن لائحة التَنزيل, تُظهر مَعلومات لائحة التَنزيل و عَدد الأجْهِزة المُفعَلة عَلى الحِساب" 69 | }, { 70 | "internalID": "api_pstv", 71 | "name": "PS TV", 72 | "desc": "الكشف عَن الألعاب لِجهَاز PS TV مُتوافر فَقط لِمَتجر en-us", 73 | "disabled": true 74 | }] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "ru", 3 | "ru": { 4 | "author": "GenosseArroganz", 5 | "local": "Русский", 6 | "startup": { 7 | "apis": "Выберите необходимые компоненты (для описания наведите на них указатель мыши).
Некоторые компоненты обязательны и не могут быть отключены.", 8 | "wait": "Подождите…", 9 | "start": "Начать!" 10 | }, 11 | "columns": { 12 | "icon": "Иконка", 13 | "name": "Название", 14 | "platform": "Платформа", 15 | "size": "Размер", 16 | "date": "Дата" 17 | }, 18 | "labels": { 19 | "exportView": "Экспорт списка", 20 | "page": "Страница" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Игры", 24 | "demo": "Демо", 25 | "add_on": "Дополнения", 26 | "unlock": "Разблокировки", 27 | "unlock_key": "Ключи разблокировки", 28 | "avatar": "Аватары", 29 | "theme": "Темы", 30 | "other": "Другое", 31 | "other_game_related": "Другой связанный контент", 32 | "game_content": "Игровой контент", 33 | "tumbler_index": "tumbler_index", 34 | "home": "PlayStation Home", 35 | "ungrouped_game": "Без категории", 36 | "promo_content": "Промо-материалы", 37 | "beta": "Бета", 38 | "application": "Приложения", 39 | "extras": "Дополнительно", 40 | "unknown": "Неизвестно" 41 | }, 42 | "strings": { 43 | "delimiter": "Введите разделитель:", 44 | "yes": "Да", 45 | "no": "Нет", 46 | "search": "Поиск", 47 | "dlQueue": "Очередь загрузок", 48 | "dlList": "Список загрузок", 49 | "plus": "Скрыть/показать игры PS+", 50 | "queueAll": "Все", 51 | "queueTo": "Загрузить на $SYS$", 52 | "noTarget": "Не обнаружено подходящего устройства", 53 | "exportColumnName": "Название столбца", 54 | "exportProperty": "Свойство" 55 | }, 56 | "apis": [{ 57 | "internalID": "api_entitle", 58 | "name": "История покупок", 59 | "desc": "Нельзя отключить. История ваших покупок используется для создания списка загрузок и определения статуса PlayStation Plus." 60 | }, { 61 | "internalID": "api_game", 62 | "name": "Каталог", 63 | "desc": "Больше опций отображения списка, включая категории. Увеличивает время, необходимое для подготовки списка загрузок." 64 | }, { 65 | "internalID": "api_queue", 66 | "name": "Очередь загрузок", 67 | "desc": "Возможность формировать очередь загрузок. Доступ к информации об очереди загрузок и активированных консолях на аккаунте." 68 | }, { 69 | "internalID": "api_pstv", 70 | "name": "PS TV", 71 | "desc": "Определяет совместимые с PS TV игры и приложения. Только для американского магазина.", 72 | "disabled": true 73 | }] 74 | } 75 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "nl", 3 | "nl": { 4 | "author": "Tricksy", 5 | "local": "Nederlands", 6 | "startup": { 7 | "apis": "Selecteer welke APIs je wilt gebruiken, hover voor meer details.
Sommige APIs kunnen niet gedeselecteerd worden.", 8 | "wait": "Even geduld alstublieft.", 9 | "start": "Start" 10 | }, 11 | "columns": { 12 | "icon": "Icoon", 13 | "name": "Naam", 14 | "platform": "Platform", 15 | "size": "Grootte", 16 | "date": "Datum" 17 | }, 18 | "labels": { 19 | "exportView": "Exporteer View", 20 | "page": "Pagina" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Spellen", 24 | "demo": "Demos", 25 | "add_on": "Add-ons", 26 | "unlock": "Ontgrendelingen", 27 | "unlock_key": "Ontgrendelings Sleutels", 28 | "avatar": "Avatars", 29 | "theme": "Themas", 30 | "other": "anders", 31 | "other_game_related": "ander_spel_gerelateerd", 32 | "game_content": "spel_inhoud", 33 | "tumbler_index": "tumbler_index", 34 | "home": "begin", 35 | "ungrouped_game": "ongegroepeerd_spel", 36 | "promo_content": "promo_inhoud", 37 | "beta": "Betas", 38 | "application": "Applicaties", 39 | "extras": "Extras", 40 | "unknown": "Onbekend" 41 | }, 42 | "strings": { 43 | "delimiter": "Voer delimiter in:", 44 | "yes": "Ja", 45 | "no": "Nee", 46 | "search": "Zoeken", 47 | "dlQueue": "Wachtrij", 48 | "dlList": "Lijst", 49 | "plus": "Laat PS+ titels zien.", 50 | "queueAll": "Alles", 51 | "queueTo": "Download naar $SYS$", 52 | "noTarget": "Er is geen beschikbare console om naar toe te sturen", 53 | "exportColumnName": "Kolom Naam", 54 | "exportProperty": "Inhoud" 55 | }, 56 | "apis": [{ 57 | "internalID": "api_recht", 58 | "name": "Rechten", 59 | "desc": "Kan niet uitgeschakeld worden. Geeft toegang tot betalings informatie om te gebruiken voor de download lijst, bepaald PS+ status, en meer." 60 | }, { 61 | "internalID": "api_spel", 62 | "name": "Catalogus", 63 | "desc": "Geeft toegang tot extra spel informatie om de goede console te bepalen, kapotte images te fixen, en meer." 64 | }, { 65 | "internalID": "api_wachtrij", 66 | "name": "Download Wachtrij", 67 | "desc": "Geeft toegang tot het toevoegen en verwijderen van spellen op de download wachtrij. Leest de download wachtrij informatie en het aantal geactiveerde consoles op het account." 68 | }, { 69 | "internalID": "api_pstv", 70 | "name": "PS TV", 71 | "desc": "Detecteert titels die met PS TV werken. Werkt alleen op de \"en-us\" web store (niet PSDLE taal).", 72 | "disabled": true 73 | }] 74 | } 75 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "def":"de", 3 | "de":{ 4 | "local":"Deutsch", 5 | "startup":{ 6 | "apis":"Wähle aus welche PS Store Funktionen du benutzen möchtest, fahre mit der Maus über die jeweiligen Menü-Einträge für weiter Informationen.
Bestimmte Store Funktionen sind möglicherweise ausgeschaltet.", 7 | "wait":"Seite wird geladen, bitte warten.", 8 | "start":"Starten" 9 | }, 10 | "columns":{ 11 | "icon":"Symbol", 12 | "name":"Name", 13 | "platform":"Plattform", 14 | "size":"Größe", 15 | "date":"Datum" 16 | }, 17 | "labels":{ 18 | "exportView":"Ansicht exportieren", 19 | "page":"Seite" 20 | }, 21 | "categories":{ 22 | "downloadable_game":"Spiele", 23 | "demo":"Demos", 24 | "add_on":"Erweiterungen", 25 | "unlock":"Freischaltbares", 26 | "unlock_key":"Freischaltbare Schlüssel", 27 | "avatar":"Spielerbilder", 28 | "theme":"Designs", 29 | "other":"Andere", 30 | "home":"Startseite", 31 | "promo_content":"promo_inhalt", 32 | "beta":"Betas", 33 | "application":"Anwendungen", 34 | "extras":"Extras", 35 | "unknown":"Unbekannt" 36 | }, 37 | "strings":{ 38 | "delimiter":"Geben sie ein Trennzeichen ein:", 39 | "yes":"Ja", 40 | "no":"Nein", 41 | "search":"Suche", 42 | "dlQueue":"Warteschlange", 43 | "dlList":"Download-Liste", 44 | "plus":"PS+ Inhalte sichtbar/unsichtbar machen", 45 | "queueAll":"Alle", 46 | "queueTo":"Herunterladen zu $SYS$", 47 | "noTarget":"Es ist keine Ziel Konsole verfügbar.", 48 | "exportColumnName":"Spalten-Name", 49 | "exportProperty":"Eigenschaften", 50 | "exportImport":"Importieren" 51 | }, 52 | "apis":[ 53 | { 54 | "internalID":"api_entitle", 55 | "name":"Kaufverlauf", 56 | "desc":"Kann nicht deaktiviert werden. Benutze den Kaufverlauf um die Download-Liste zu erstellen und den PlayStation Plus status festzustellen." 57 | }, 58 | { 59 | "internalID":"api_game", 60 | "name":"Katalog", 61 | "desc":"Einschalten für zusätzliche Spieleinformationen, inklusive Kategorien. Verlangsamt das Erstellen der Download-Liste." 62 | }, 63 | { 64 | "internalID":"api_queue", 65 | "name":"Download-Warteschlange", 66 | "desc":"Erlaubt das Hinzufügen und Entfernen von Titeln in der Download-Warteschlange. Liest die Download-Warteschlangeninformationen und den Aktivierungszustand der Konsole." 67 | }, 68 | { 69 | "internalID":"api_pstv", 70 | "desc":"PS TV kompatible Titel wurden festgestellt. Wird nur unterstützt bei \"en-us\" Internet-Store (nicht die PSDLE Sprache).", 71 | "disabled":true 72 | } 73 | ] 74 | } 75 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "us", 3 | "us": { 4 | "local": "English", 5 | "startup": { 6 | "apis": "Select which store features you would like to use, hover for more details.
Certain store features may not be disabled.", 7 | "wait": "Please wait.", 8 | "catalog": "Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.\n\nIf you run into issues or would like to you can revoke the persmission and clear the cache below the Start button.", 9 | "start": "Start" 10 | }, 11 | "columns": { 12 | "icon": "Icon", 13 | "name": "Name", 14 | "platform": "Platform", 15 | "size": "Size", 16 | "date": "Date" 17 | }, 18 | "labels": { 19 | "exportView": "Export View", 20 | "page": "Page" 21 | }, 22 | "categories": { 23 | "downloadable_game": "Games", 24 | "demo": "Demos", 25 | "add_on": "Add-ons", 26 | "unlock": "Unlocks", 27 | "unlock_key": "Unlock Keys", 28 | "avatar": "Avatars", 29 | "theme": "Themes", 30 | "other": "other", 31 | "other_game_related": "other_game_related", 32 | "game_content": "game_content", 33 | "tumbler_index": "tumbler_index", 34 | "home": "home", 35 | "ungrouped_game": "ungrouped_game", 36 | "promo_content": "promo_content", 37 | "beta": "Betas", 38 | "application": "Applications", 39 | "extras": "Extras", 40 | "unknown": "Unknown" 41 | }, 42 | "strings": { 43 | "delimiter": "Enter separator:", 44 | "yes": "Yes", 45 | "no": "No", 46 | "search": "Search", 47 | "dlQueue": "Queue", 48 | "dlList": "List", 49 | "plus": "Toggle visibility of PS+ titles.", 50 | "queueAll": "All", 51 | "queueTo": "Download to $SYS$", 52 | "noTarget": "There is no available target console to send to.", 53 | "exportColumnName": "Column Name", 54 | "exportProperty": "Property", 55 | "exportImport": "Import" 56 | }, 57 | "apis": [{ 58 | "internalID": "api_entitle", 59 | "name": "Purchase History", 60 | "desc": "Cannot be disabled. Uses your purchase history to create the download list and determine PlayStation Plus status." 61 | }, { 62 | "internalID": "api_game", 63 | "name": "Catalog", 64 | "desc": "Enable for additional game information, including categories. Increases time needed to create the download list." 65 | }, { 66 | "internalID": "api_queue", 67 | "name": "Download Queue", 68 | "desc": "Allows adding and removing items from the download queue. Reads download queue information and console activation status." 69 | }, { 70 | "internalID": "api_pstv", 71 | "name": "PS TV", 72 | "desc": "Detect PS TV compatible titles. Only supported on \"en-us\" web store (not PSDLE language).", 73 | "disabled": true 74 | }] 75 | } 76 | } -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/all/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "def": "tw", 3 | "tw": { 4 | "author": "Alexsh", 5 | "local": "中文 (繁體)", 6 | "startup": { 7 | "apis": "請選擇要使用的PS Store功能,滑鼠游標停留以取得項目的詳細資訊
部份功能可能無法關閉", 8 | "wait": "請稍候...", 9 | "start": "開始" 10 | }, 11 | "columns": { 12 | "icon": "圖示", 13 | "name": "名稱", 14 | "platform": "平台", 15 | "size": "容量", 16 | "date": "購買日期" 17 | }, 18 | "labels": { 19 | "exportView": "匯出", 20 | "page": "Page" 21 | }, 22 | "categories": { 23 | "downloadable_game": "遊戲", 24 | "demo": "體驗版", 25 | "add_on": "追加內容", 26 | "unlock": "關卡", 27 | "unlock_key": "解鎖", 28 | "avatar": "個人造型", 29 | "theme": "主題", 30 | "other": "other", 31 | "other_game_related": "other_game_related", 32 | "game_content": "game_content", 33 | "tumbler_index": "tumbler_index", 34 | "home": "home", 35 | "ungrouped_game": "ungrouped_game", 36 | "promo_content": "promo_content", 37 | "beta": "Betas", 38 | "application": "應用程式", 39 | "extras": "Extras", 40 | "unknown": "Unknown" 41 | }, 42 | "strings": { 43 | "delimiter": "分隔字元:", 44 | "yes": "是", 45 | "no": "否", 46 | "search": "搜尋", 47 | "dlQueue": "佇列", 48 | "dlList": "清單", 49 | "plus": "選擇顯示PS+遊戲", 50 | "queueAll": "全部", 51 | "queueTo": "下載到$SYS$", 52 | "noTarget": "沒有可傳送的主機。", 53 | "exportColumnName": "欄位名稱", 54 | "exportProperty": "屬性" 55 | }, 56 | "apis": [{ 57 | "internalID": "api_entitle", 58 | "name": "購買記錄", 59 | "desc": "此項不可關閉,將使用購買記錄來建立下載清單及確認PlayStation Plus狀態。" 60 | }, { 61 | "internalID": "api_game", 62 | "name": "類別", 63 | "desc": "開啟以取得更多遊戲資訊,包括類別及購買時間來建立下載清單。" 64 | }, { 65 | "internalID": "api_queue", 66 | "name": "下載佇列", 67 | "desc": "允許從下載佇列增加/移除項目。" 68 | }, { 69 | "internalID": "api_pstv", 70 | "name": "PS TV", 71 | "desc": "偵測Playstation Vita TV相容遊戲。目前只支援en-us區域", 72 | "disabled": true 73 | }] 74 | }, 75 | "cn": { 76 | "author": "Alexsh", 77 | "local": "中文 (简体)", 78 | "startup": { 79 | "apis": "请选择要使用的PS Store功能,鼠标光标停留以取得项目的详细信息
部份功能可能无法关闭", 80 | "wait": "请稍候...", 81 | "start": "开始" 82 | }, 83 | "columns": { 84 | "icon": "图示", 85 | "name": "名称", 86 | "platform": "平台", 87 | "size": "容量", 88 | "date": "购买日期" 89 | }, 90 | "labels": { 91 | "exportView": "汇出", 92 | "page": "Page" 93 | }, 94 | "categories": { 95 | "downloadable_game": "游戏", 96 | "demo": "体验版", 97 | "add_on": "追加内容", 98 | "unlock": "关卡", 99 | "unlock_key": "解锁", 100 | "avatar": "个人造型", 101 | "theme": "主题", 102 | "other": "other", 103 | "other_game_related": "other_game_related", 104 | "game_content": "game_content", 105 | "tumbler_index": "tumbler_index", 106 | "home": "home", 107 | "ungrouped_game": "ungrouped_game", 108 | "promo_content": "promo_content", 109 | "beta": "Betas", 110 | "application": "应用程序", 111 | "extras": "Extras", 112 | "unknown": "Unknown" 113 | }, 114 | "strings": { 115 | "delimiter": "分隔字符:", 116 | "yes": "是", 117 | "no": "否", 118 | "search": "搜寻", 119 | "dlQueue": "队列", 120 | "dlList": "清单", 121 | "plus": "选择显示PS+游戏", 122 | "queueAll": "全部", 123 | "queueTo": "下载到$SYS$", 124 | "noTarget": "没有可传送的主机。", 125 | "exportColumnName": "域名", 126 | "exportProperty": "属性" 127 | }, 128 | "apis": [{ 129 | "internalID": "api_entitle", 130 | "name": "购买记录", 131 | "desc": "此项不可关闭,将使用购买记录来建立下载列表及确认PlayStation Plus状态。" 132 | }, { 133 | "internalID": "api_game", 134 | "name": "类别", 135 | "desc": "开启以取得更多游戏信息,包括类别及购买时间来建立下载清单。" 136 | }, { 137 | "internalID": "api_queue", 138 | "name": "下载队列", 139 | "desc": "允许从下载队列增加/移除项目。" 140 | }, { 141 | "internalID": "api_pstv", 142 | "name": "PS TV", 143 | "desc": "侦测Playstation Vita TV兼容游戏。目前只支持en-us区域", 144 | "disabled": true 145 | }] 146 | } 147 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | includes: { 5 | build: { 6 | files: { 7 | '_src/base/gotham/psdle.gotham.includes.js': '_src/base/gotham/psdle.base.js', 8 | '_src/base/valkyrie/psdle.valkyrie.includes.js': '_src/base/valkyrie/psdle.base.js' 9 | }, 10 | flatten: true, 11 | cwd: '.', 12 | options: { 13 | silent: true, 14 | includeRegexp: /{{{include\s+"(\S+)"}}}/, 15 | banner: '/*! <%= pkg.name %> <%= pkg.version %> <%= pkg.license %> - base - compiled <%= grunt.template.today("yyyy-mm-dd") %> */\n' 16 | } 17 | } 18 | }, 19 | uglify: { 20 | release: { 21 | options: { 22 | banner: '/*! <%= pkg.name %> <%= pkg.version %> <%= pkg.license %> - min - compiled <%= grunt.template.today("yyyy-mm-dd") %> */' 23 | }, 24 | files: { 25 | '_src/base/gotham/psdle.gotham.min.js': '_src/base/gotham/psdle.gotham.includes.js', 26 | '_src/base/valkyrie/psdle.valkyrie.min.js': '_src/base/valkyrie/psdle.valkyrie.includes.js' 27 | } 28 | } 29 | }, 30 | copy: { 31 | release: { 32 | files: [{ 33 | expand: true, 34 | flatten: true, 35 | src: '_src/base/**/psdle.*.*.js', 36 | rename: function(src, dest) { 37 | return dest.replace(".includes", "") 38 | } 39 | }] 40 | }, 41 | chrome: { 42 | files: { 43 | '_src/chrome/psdle/js/psdle.gotham.js': '_src/base/gotham/psdle.gotham.includes.js', 44 | '_src/chrome/psdle/js/psdle.valkyrie.js': '_src/base/valkyrie/psdle.valkyrie.includes.js' 45 | } 46 | } 47 | }, 48 | concat: { 49 | options: { 50 | stripBanners: true, 51 | banner: '/*! <%= pkg.name %> <%= pkg.version %> <%= pkg.license %> - user+base - compiled <%= grunt.template.today("yyyy-mm-dd") %> */\n' 52 | }, 53 | userscript: { 54 | files: { 55 | '_src/base/gotham/psdle.gotham.user.js': ['_src/psdle.user.txt', '_src/base/gotham/psdle.gotham.includes.js'], 56 | '_src/base/valkyrie/psdle.valkyrie.user.js': ['_src/psdle.user.txt', '_src/base/valkyrie/psdle.valkyrie.includes.js'] 57 | } 58 | } 59 | }, 60 | exec: { 61 | chrome_zip: 'call _src/chrome/7-Zip.bat', 62 | chrome_deploy: { 63 | command: 'call _src/chrome/deploy.bat', 64 | exitCode: [0,1] 65 | }, 66 | deploy: 'deploy-sync.bat' 67 | }, 68 | 'string-replace': { 69 | compile: { 70 | files: [{ 71 | expand: true, 72 | src: '_src/base/*/psdle.*.includes.js' 73 | }], 74 | options: { 75 | replacements: [{ 76 | pattern: /(version\s*:\s*").*?(")/ig, 77 | replacement: '$1<%= pkg.version %>$2' 78 | },{ 79 | pattern: /(versiondate\s*:\s*").*?(")/ig, 80 | replacement: '$1<%= grunt.template.today("yyyy-mm-dd") %>$2' 81 | }] 82 | } 83 | }, 84 | release: { 85 | files: { 86 | '_src/chrome/psdle/manifest.json': '_src/chrome/psdle/manifest.json', 87 | '_src/psdle.user.txt': '_src/psdle.user.txt' 88 | }, 89 | options: { 90 | replacements: [{ 91 | pattern: /("version":\s*").*?(")/ig, 92 | replacement: '$1<%= pkg.version %>$2' 93 | },{ 94 | pattern: /(\/\/ @version(\s*)).*/ig, 95 | replacement: '$1<%= pkg.version %>' 96 | }] 97 | } 98 | }, 99 | // Terrifying. 100 | userscriptGotham: { 101 | files: { '_src/base/gotham/psdle.gotham.user.js': '_src/base/gotham/psdle.gotham.user.js' }, 102 | options: { 103 | replacements: [{ 104 | pattern: /\{variant\}/ig, 105 | replacement: 'gotham' 106 | }] 107 | } 108 | }, 109 | userscriptValkyrie: { 110 | files: { '_src/base/valkyrie/psdle.valkyrie.user.js': '_src/base/valkyrie/psdle.valkyrie.user.js' }, 111 | options: { 112 | replacements: [{ 113 | pattern: /\{variant\}/ig, 114 | replacement: 'valkyrie' 115 | }] 116 | } 117 | } 118 | }, 119 | cssmin: { 120 | target: { 121 | files: [{ 122 | expand: true, 123 | //src: ['_src/base/gotham/css/*.css', '!_src/base/gotham/css/*.min.css'], 124 | src: ['_src/base/**/css/*.css', '!_src/base/**/css/*.min.css'], 125 | ext: '.min.css' 126 | }] 127 | } 128 | }, 129 | 'concat-json': { 130 | gotham: { 131 | cwd: '_src/base/gotham/lang/all', 132 | src: [ '*.json' ], 133 | dest: '_src/base/gotham/lang/lang.min.json' 134 | }, 135 | valkyrie: { 136 | cwd: '_src/base/valkyrie/lang/all', 137 | src: [ '*.json' ], 138 | dest: '_src/base/valkyrie/lang/lang.min.json' 139 | }, 140 | } 141 | }); 142 | 143 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 144 | grunt.loadNpmTasks('grunt-includes'); 145 | grunt.loadNpmTasks('grunt-contrib-uglify-es'); 146 | grunt.loadNpmTasks('grunt-contrib-copy'); 147 | grunt.loadNpmTasks('grunt-contrib-concat'); 148 | grunt.loadNpmTasks('grunt-string-replace'); 149 | grunt.loadNpmTasks('grunt-exec'); 150 | grunt.loadNpmTasks('grunt-concat-json'); 151 | 152 | grunt.registerTask('minlang', 'Concat and minify language files. Never automate, shouldn\'t need often.', ['concat-json']); 153 | grunt.registerTask('compile', 'Bake in language JSON and minified CSS, then bake in version.', function(){ 154 | grunt.task.run([ 155 | 'cssmin', 156 | 'includes:build', 157 | 'string-replace:compile' 158 | ]) 159 | }); 160 | grunt.registerTask('chrome', ['exec:chrome_deploy']); 161 | grunt.registerTask('release', 'Generate PSDLE release, compiles first.', function() { 162 | grunt.task.run([ 163 | 'compile', 164 | //'copy:release', //Base 165 | 'string-replace:release', //Set versions 166 | 'uglify:release', //Minified 167 | 'copy:chrome', 168 | 'concat:userscript', //Userscript 169 | 'string-replace:userscriptGotham', //Userscript smartness 170 | 'string-replace:userscriptValkyrie', //And again. Filenames hard. 171 | 'exec:chrome_zip' //Chrome + Firefox 172 | ]); 173 | }); 174 | grunt.registerTask('deploy', 'Run release then deploy script.', function() { 175 | grunt.task.run([ 176 | 'chrome', 177 | 'exec:deploy' 178 | ]) 179 | }); 180 | grunt.registerTask('default', 'Runs compile.', function() { 181 | grunt.task.run(['compile']); 182 | }); 183 | }; -------------------------------------------------------------------------------- /_src/base/valkyrie/css/psdle.min.css: -------------------------------------------------------------------------------- 1 | div.amopromo{margin-bottom:1em}div.amopromo a,div.amopromo a:hover{text-decoration:unset;color:unset}div.amopromo .psdle_btn{margin:unset}div.amopromo .psdle_btn:hover{margin:unset}div.amopromo>div{font-size:small}.valkyrie #export_table input,.valkyrie #export_table select,.valkyrie #lang_select{height:unset;padding:unset;margin:unset;width:unset;display:unset}.psdle_logo{display:inline-block;width:84px;height:31px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAAfCAYAAAEO89r4AAABaUlEQVRoge2XS27CQAyGPSVSUVErdqzpMqveiRvALnu67Gl6D+gFuAKIPgQrs0o1TJSJJ7aJBvnbRXE8f357XoCIGyTiEBFf33+BwgMpyg/eVRNSsENEpAQWMa27agL1e7JWcmCSVSG+tF6jp1D4o/qkqN8un+Bl7JpJUxP5vH38XT2T655CtEf6olKoaFLq3ElK2heRlgq//U/KKVj4rcrvs+Y+h7Z1ow2Vv9eg6A5p53MxhnI2an0vWSmW0HI2EhUTI5vSN4T2Xem0ycZRh4h7AJgOLaQLlf1ega2br3/IQlMW6TA2dYEPc2XToyZUGtbOdMs1lyX0lqeubEpvQqVp9GhsghxPOpvY8yPA1yo+MRtCh7iWfJ/j49rOpEE2QnM55h1U7/Wcox0nb+y9lqY6dzYtmgtmqDBmqDBmqDCDGcq5Ew5xCqViHSqMGSqMGSqMGSpMp6H3unloYR0qjBkqjBkqjBkqzAUtBKxj5lT3GAAAAABJRU5ErkJggg==)}.startup{z-index:10000;position:fixed;bottom:10px;left:10px;cursor:pointer;box-shadow:0 0 10px #fff}#muh_games_container{display:none;position:absolute;top:0;right:0;left:0;color:#000;z-index:10000;text-align:center}#sub_container{background-color:#fff}#startup_progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:400px;height:16px;border:1px solid #999;overflow:hidden;border-radius:10px;margin:10px;color:#2185f4!important}#startup_progress::-moz-progress-bar{background-color:#2185f4!important}#startup_progress::-webkit-progress-value{background-color:#2185f4!important}.psdle_btn{background-color:#2185f4}.psdle_btn{cursor:pointer;border-radius:13px;color:#fff;padding:1px 15px;display:inline-block;margin:5px auto}.psdle.tagline{font-size:small}.psdle.tagline>a,.psdle.tagline>span{line-height:0;cursor:pointer;color:#7f6d75!important}.psdle.tagline>a:hover,.psdle.tagline>span:hover{color:inherit!important;text-decoration:underline}.search.options.syntax{display:block;text-align:center;width:500px;margin:auto;margin-bottom:1em;border:none;border-bottom:1px solid #000}.search.options.filter.container{display:inline-block;background-color:#2185f4;color:#fff;margin:0 .3em;cursor:default}.search.options.filter.container .value{display:inline-block;padding:0 .2em;text-align:center}.search.options.filter.container.system .value{background-color:red}.search.options.filter.container.category .value{background-color:green}.search.options.filter.close{display:inline-block;color:#fff;padding:0 .3em;cursor:pointer}.search.options.filter.close:hover{background-color:rgba(255,51,0,.8)}.search.options.container{margin-bottom:20px}.search.main.container{position:fixed;left:0;top:0;width:100%;padding:15px 0;background-color:rgba(255,255,255,.8);z-index:10000}.search.input.plus{cursor:pointer!important}.search.export{display:inline;width:95%;max-width:600px}.psdle_table,table{text-align:left;border-collapse:initial}th[id^=sort]{cursor:pointer}th[id^=sort]:hover{background-color:#62a5f0}th{padding:5px!important;background-color:#2185f4;color:#fff;border:none;transition:background-color .3s}tr:hover{background-color:rgba(33,133,244,.7)!important}.valkyrie td{padding:0;border:none}td a.psdle_game_link{display:block;width:100%;height:100%;color:#000;padding:8px!important}.psdle_game_icon.is_plus{background-color:#ffd10d}tr[id^=psdle_index_].is_plus td:last-child{border-right:#ffd10d 3px solid}tr:nth-child(2n){background-color:#eee}td{text-align:center}td:nth-child(2),th:nth-child(2){text-align:left!important}#psdle_search_select,#psdle_search_text{font-size:large;padding:5px 10px;border:1px solid #f0f0f0;display:inline-block;width:auto}#psdle_search_select{background-color:#f0f0f0;text-align:center}#psdle_search_text{font-size:large;max-width:480px;width:100%}.negate_regex{background-color:#ff8080!important;color:#fff}.psdle_fancy_bar>span,.psdle_fancy_but,span#export_view,span[id^=dl_],span[id^=filter_],span[id^=system_]{font-weight:700;font-size:.9em;color:#fff;background-color:#2185f4;display:inline-block;margin-right:2px;margin-bottom:5px;padding:1px 10px;cursor:pointer}.psdle_fancy_but{border-radius:12px}#muh_games_container:not(.rtl) .psdle_fancy_bar>span:first-of-type{border-top-left-radius:12px;border-bottom-left-radius:12px}#muh_games_container:not(.rtl) .psdle_fancy_bar>span:last-of-type{border-top-right-radius:12px;border-bottom-right-radius:12px}.toggled_off{background-color:rgba(33,133,244,.4)!important}#muh_games_container:not(.rtl) #psdle_search_select{border-radius:90px 0 0 90px}#psdle_search_text{border-radius:0 90px 90px 0}.psdle_game_icon{max-width:100%;vertical-align:middle;padding:3px;width:42px;height:42px}.psdle_sort_asc,.psdle_sort_desc{float:right;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent}.psdle_sort_asc{border-bottom:5px solid #fff}.psdle_sort_desc{border-top:5px solid #fff}#dlQARating,#dlQAStat{color:#fff;background-color:rgba(33,133,244,.8);font-size:small}#dlQueueAsk{width:400px;height:400px}#dlQAN{background-color:rgba(33,133,244,.8);padding:7px 15px;color:#fff;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}#dlQASys{position:absolute;bottom:0;padding:7px 0;color:#fff;display:table;width:100%;table-layout:fixed}#dlQASys>div{display:table-cell}#dlQASys>div>div{cursor:pointer;background-color:rgba(33,133,244,.8);border-radius:10px;padding:2px;margin:0 10px;box-shadow:0 0 30px #000;transition:background-color .5s,box-shadow .5s}#dlQASys>div>div:hover{background-color:rgba(33,133,244,1);box-shadow:0 0 30px rgba(33,133,244,1)}#dlQAStat{border-bottom-left-radius:20px;padding:0 10px 0 15px;float:right}#dlQARating{border-bottom-right-radius:20px;padding:0 15px 0 10px;float:left}.success{background-color:#237423!important}.failure{background-color:#a43636!important}#dlQueueExt{overflow:hidden;position:absolute;left:10px;right:10px;bottom:40px;font-size:.8em;background-color:rgba(33,133,244,.8);padding:10px;border-radius:9px;top:66px;text-align:left}.cover,.cover>div>div{background-size:cover}.cover{z-index:10000;position:fixed;top:0;left:0;width:100%;height:100%;display:table;background-color:rgba(0,0,0,.25);background-position:center}.cover>div{display:table-cell;vertical-align:middle;height:inherit;text-align:center}.cover>div>div{box-shadow:0 0 30px #000;display:inline-block;background-color:#fff;border-radius:20px;overflow:hidden;position:relative}#export_select{padding:10px;background-color:#fff;color:#000}#export_select>div{border-top-left-radius:10px;border-top-right-radius:10px;overflow-y:auto;overflow-x:hidden;max-height:490px}#export_table{width:100%}#export_table th{text-align:center}#export_table .orderUp{cursor:pointer;height:1em;border-left:.4em solid transparent;border-right:.4em solid transparent;border-bottom:.9em solid rgba(0,0,0,.2);display:inline-block;padding:1px;margin:0 3px}#export_table .orderUp:hover{border-bottom-color:#000}#slider,.handle{display:inline-block}#slider{vertical-align:bottom;cursor:pointer;width:30px;height:12px;border-radius:10px;border:2px solid #f0f0f0}.handle_container{text-align:center;width:100%;height:100%}.handle{width:10px;height:10px;border-radius:100%;margin:0 2px 6px;border:1px solid #fff;background-color:#85c107}[data-tooltip]{position:relative;z-index:2;cursor:pointer}[data-tooltip]:after,[data-tooltip]:before{visibility:hidden;opacity:0;pointer-events:none}[data-tooltip]:before{--width:300px;position:absolute;top:150%;left:50%;margin-left:calc(var(--width)/2*-1);padding:7px;width:var(--width);border-radius:3px;background-color:rgba(33,133,244,.9);color:#fff;content:attr(data-tooltip);text-align:center;font-size:.9em;line-height:1.2;box-shadow:0 0 10px #fff}[data-tooltip]:after{position:absolute;top:calc(150% - 5px);left:50%;margin-left:-5px;width:0;border-bottom:5px solid rgba(33,133,244,.9);border-right:5px solid transparent;border-left:5px solid transparent;content:" ";font-size:0;line-height:0}[data-tooltip]:hover:after,[data-tooltip]:hover:before{visibility:visible;opacity:1}.ui-autocomplete{z-index:9002;max-width:590px;max-height:200px;overflow-y:auto;overflow-x:hidden}.ui-menu{position:fixed;border:2px solid #f0f0f0;border-top:none;background-color:#fff}.ui-menu>.ui-menu-item *{color:#000;text-decoration:none;white-space:nowrap;text-overflow:ellipsis;cursor:pointer}.ui-menu>.ui-menu-item:nth-child(even){background-color:#e6e6e6}.ui-menu-item .ui-state-focus{display:inline-block;width:100%;color:#000;background-color:rgba(33,133,244,.7)}.psdletv{font-style:italic;font-weight:700;font-size:.6em;vertical-align:text-top;position:absolute;top:4px}.psp3{border-left:2px solid #2185f4;border-right:2px solid #2185f4}.psp2{background-color:rgba(33,133,244,.15)!important}#muh_games_container.rtl{direction:rtl}.rtl #psdle_search_select{border-radius:0 90px 90px 0}.rtl #psdle_search_text{border-radius:90px 0 0 90px}.rtl .psdle_fancy_bar span:last-of-type{border-top-left-radius:12px;border-bottom-left-radius:12px}.rtl .psdle_fancy_bar span:first-of-type{border-top-right-radius:12px;border-bottom-right-radius:12px}.rtl .psdle_table *{text-align:right}.rtl tr.is_plus[id^=psdle_index_] td:last-child{border-right:none;border-left:#ffd10d 3px solid}.psdledark #sub_container{background-color:#222;color:#e7e7e7}.psdledark a.psdle_game_link{color:#e7e7e7}.psdledark .search.main.container{background-color:rgba(34,34,34,.7)}.psdledark tr{background-color:#222}.psdledark tr:nth-child(2n){background-color:#393939}button.psdleClose{cursor:pointer;position:absolute;top:0;right:0;z-index:10001} -------------------------------------------------------------------------------- /_src/base/valkyrie/css/psdle.css: -------------------------------------------------------------------------------- 1 | /* Prefer using " over ' and escaping otherwise due to the CSS injection in base. See base for extra details. */ 2 | 3 | /* AMO Promo CSS, remove when done. */ 4 | div.amopromo { 5 | margin-bottom: 1em; 6 | } 7 | div.amopromo a, div.amopromo a:hover { 8 | text-decoration: unset; 9 | color: unset; 10 | } 11 | div.amopromo .psdle_btn { 12 | margin: unset; 13 | } 14 | div.amopromo .psdle_btn:hover { 15 | margin: unset; 16 | } 17 | div.amopromo > div { 18 | font-size: small; 19 | } 20 | 21 | /* Undo Valkyrie overrides, in a NON-GREEDY WAY */ 22 | 23 | .valkyrie #lang_select, 24 | .valkyrie #export_table select, 25 | .valkyrie #export_table input { 26 | height: unset; 27 | padding: unset; 28 | margin: unset; 29 | width: unset; 30 | display: unset; 31 | } 32 | 33 | 34 | /*Startup */ 35 | .psdle_logo { 36 | display: inline-block; 37 | width: 84px; 38 | height: 31px; 39 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFQAAAAfCAYAAAEO89r4AAABaUlEQVRoge2XS27CQAyGPSVSUVErdqzpMqveiRvALnu67Gl6D+gFuAKIPgQrs0o1TJSJJ7aJBvnbRXE8f357XoCIGyTiEBFf33+BwgMpyg/eVRNSsENEpAQWMa27agL1e7JWcmCSVSG+tF6jp1D4o/qkqN8un+Bl7JpJUxP5vH38XT2T655CtEf6olKoaFLq3ElK2heRlgq//U/KKVj4rcrvs+Y+h7Z1ow2Vv9eg6A5p53MxhnI2an0vWSmW0HI2EhUTI5vSN4T2Xem0ycZRh4h7AJgOLaQLlf1ega2br3/IQlMW6TA2dYEPc2XToyZUGtbOdMs1lyX0lqeubEpvQqVp9GhsghxPOpvY8yPA1yo+MRtCh7iWfJ/j49rOpEE2QnM55h1U7/Wcox0nb+y9lqY6dzYtmgtmqDBmqDBmqDCDGcq5Ew5xCqViHSqMGSqMGSqMGSpMp6H3unloYR0qjBkqjBkqjBkqzAUtBKxj5lT3GAAAAABJRU5ErkJggg=="); 40 | } 41 | .startup { 42 | z-index: 10000; 43 | position: fixed; 44 | bottom: 10px; 45 | left: 10px; 46 | cursor: pointer; 47 | box-shadow: 0px 0px 10px #FFF; 48 | } 49 | /*I wanted to use z-index:calc(9999 + 1) as a joke but Chrome already has enough reflow issues*/ 50 | #muh_games_container { 51 | display: none; 52 | position: absolute; 53 | top: 0; 54 | right: 0; 55 | left: 0; 56 | color: #000; 57 | z-index: 10000; 58 | text-align: center 59 | } 60 | #sub_container { 61 | /*padding: 20px;*/ 62 | background-color: #fff 63 | } 64 | /*Progress bar */ 65 | #startup_progress { 66 | -webkit-appearance: none; 67 | -moz-appearance: none; 68 | appearance: none; 69 | width: 400px; 70 | height: 16px; 71 | border: 1px solid #999; 72 | overflow: hidden; 73 | border-radius: 10px; 74 | margin: 10px; 75 | color: #2185f4 !important; 76 | } 77 | #startup_progress::-moz-progress-bar { background-color: #2185f4 !important; } 78 | #startup_progress::-webkit-progress-value { background-color: #2185f4 !important; } 79 | 80 | .psdle_btn { 81 | background-color: #2185f4 82 | } 83 | 84 | .psdle_btn { 85 | cursor: pointer; 86 | border-radius: 13px; 87 | color: #fff; 88 | padding: 1px 15px; 89 | display: inline-block; 90 | margin: 5px auto 91 | } 92 | 93 | .psdle.tagline { 94 | font-size: small 95 | } 96 | .psdle.tagline > a, 97 | .psdle.tagline > span { 98 | line-height: 0; 99 | cursor: pointer; 100 | color: #7F6D75 !important; 101 | } 102 | .psdle.tagline > a:hover, 103 | .psdle.tagline > span:hover { 104 | color: inherit !important; 105 | text-decoration: underline; 106 | } 107 | 108 | /* New search */ 109 | .search.options.syntax { 110 | display: block; 111 | text-align: center; 112 | width: 500px; 113 | margin: auto; 114 | margin-bottom: 1em; 115 | border: none; 116 | border-bottom: 1px solid black; 117 | } 118 | .search.options.filter.container { 119 | display: inline-block; 120 | background-color: #2185f4; 121 | color: #fff; 122 | margin: 0px 0.3em; 123 | cursor: default; 124 | } 125 | .search.options.filter.container .value { 126 | display: inline-block; 127 | padding: 0 0.2em; 128 | text-align: center; 129 | } 130 | .search.options.filter.container.system .value { 131 | background-color: red; 132 | } 133 | .search.options.filter.container.category .value { 134 | background-color: green; 135 | } 136 | .search.options.filter.close { 137 | display: inline-block; 138 | color: #fff; 139 | padding: 0px 0.3em; 140 | cursor: pointer; 141 | } 142 | .search.options.filter.close:hover { 143 | background-color:rgba(255,51,0,0.8) 144 | } 145 | .search.options.container { 146 | margin-bottom:20px; 147 | } 148 | 149 | /*Search options */ 150 | .search.main.container { 151 | position: fixed; 152 | left: 0; 153 | top: 0; 154 | width: 100%; 155 | padding: 15px 0; 156 | background-color: rgba(255, 255, 255, .8); 157 | z-index: 10000 158 | } 159 | .search.input.plus { 160 | cursor: pointer !important; 161 | } 162 | .search.export { 163 | display: inline; 164 | width: 95%; 165 | max-width: 600px; 166 | } 167 | 168 | 169 | /*Table */ 170 | 171 | .psdle_table, table { 172 | text-align: left; 173 | /*display: inline-block;*/ 174 | border-collapse: initial; 175 | } 176 | th[id^=sort] { 177 | cursor: pointer 178 | } 179 | th[id^=sort]:hover { 180 | background-color: rgb(98, 165, 240); 181 | } 182 | th { 183 | padding: 5px !important; 184 | background-color: #2185F4; 185 | color: #fff; 186 | border: none; 187 | transition: background-color 0.3s 188 | } 189 | tr:hover { 190 | background-color: rgba(33, 133, 244, .7)!important 191 | } 192 | .valkyrie td { 193 | padding: 0px; 194 | border:none 195 | } 196 | td a.psdle_game_link { 197 | display: block; 198 | width: 100%; 199 | height: 100%; 200 | color: #000; 201 | padding: 8px !important 202 | } 203 | .psdle_game_icon.is_plus { 204 | background-color: #FFD10D 205 | } 206 | tr[id^=psdle_index_].is_plus td:last-child { 207 | border-right: #FFD10D 3px solid 208 | } 209 | tr:nth-child(2n) { 210 | background-color: #EEE 211 | } 212 | td { 213 | text-align: center; 214 | } 215 | th:nth-child(2), 216 | td:nth-child(2) { 217 | text-align:left !important; 218 | } 219 | 220 | /*Search buttons */ 221 | 222 | #psdle_search_select, 223 | #psdle_search_text { 224 | font-size: large; 225 | padding: 5px 10px; 226 | border: 1px solid #F0F0F0; 227 | display: inline-block; 228 | width: auto; 229 | } 230 | #psdle_search_select { 231 | background-color: #F0F0F0; 232 | text-align: center 233 | } 234 | #psdle_search_text { 235 | font-size: large; 236 | max-width: 480px; 237 | width: 100% 238 | } 239 | .negate_regex { 240 | background-color: #FF8080 !important; 241 | color: #fff 242 | } 243 | .psdle_fancy_bar>span, 244 | span#export_view, 245 | span[id^=dl_], 246 | span[id^=filter_], 247 | span[id^=system_], 248 | .psdle_fancy_but { 249 | font-weight: 700; 250 | font-size: 0.9em; 251 | color: #fff; 252 | background-color: #2185f4; 253 | display: inline-block; 254 | margin-right: 2px; 255 | margin-bottom: 5px; 256 | padding: 1px 10px; 257 | cursor: pointer 258 | } 259 | .psdle_fancy_but { 260 | border-radius: 12px 261 | } 262 | #muh_games_container:not(.rtl) .psdle_fancy_bar>span:first-of-type { 263 | border-top-left-radius: 12px; 264 | border-bottom-left-radius: 12px 265 | } 266 | #muh_games_container:not(.rtl) .psdle_fancy_bar > span:last-of-type { 267 | border-top-right-radius: 12px; 268 | border-bottom-right-radius: 12px 269 | } 270 | .toggled_off { 271 | background-color: rgba(33,133,244,0.4) !important; 272 | } 273 | /*Search borders */ 274 | 275 | #muh_games_container:not(.rtl) #psdle_search_select { 276 | border-radius: 90px 0px 0px 90px 277 | } 278 | #psdle_search_text { 279 | border-radius: 0px 90px 90px 0px 280 | } 281 | /*Content icons */ 282 | 283 | .psdle_game_icon { 284 | max-width: 100%; 285 | vertical-align: middle; 286 | padding: 3px; 287 | width: 42px; 288 | height: 42px 289 | } 290 | /*Sorting */ 291 | 292 | .psdle_sort_asc, 293 | .psdle_sort_desc { 294 | float: right; 295 | width: 0; 296 | height: 0; 297 | border-left: 5px solid transparent; 298 | border-right: 5px solid transparent 299 | } 300 | .psdle_sort_asc { 301 | border-bottom: 5px solid #fff 302 | } 303 | .psdle_sort_desc { 304 | border-top: 5px solid #fff 305 | } 306 | /*Newbox */ 307 | 308 | #dlQARating, 309 | #dlQAStat { 310 | color: #fff; 311 | background-color: rgba(33, 133, 244, .8); 312 | font-size: small 313 | } 314 | #dlQueueAsk { 315 | width: 400px; 316 | height: 400px 317 | } 318 | #dlQAN { 319 | /*cursor: move;*/ 320 | 321 | background-color: rgba(33, 133, 244, .8); 322 | padding: 7px 15px; 323 | color: #fff; 324 | overflow: hidden; 325 | white-space: nowrap; 326 | text-overflow: ellipsis 327 | } 328 | #dlQASys { 329 | position: absolute; 330 | bottom: 0; 331 | padding: 7px 0; 332 | color: #FFF; 333 | display: table; 334 | width: 100%; 335 | table-layout: fixed 336 | } 337 | #dlQASys>div { 338 | display: table-cell 339 | } 340 | #dlQASys>div>div { 341 | cursor: pointer; 342 | background-color: rgba(33, 133, 244, .8); 343 | border-radius: 10px; 344 | padding: 2px; 345 | margin: 0 10px; 346 | box-shadow: 0 0 30px #000; 347 | transition: background-color 0.5s, box-shadow 0.5s 348 | } 349 | #dlQASys>div>div:hover { 350 | background-color: rgba(33, 133, 244, 1); 351 | box-shadow: 0 0 30px rgba(33, 133, 244, 1); 352 | } 353 | #dlQAStat { 354 | border-bottom-left-radius: 20px; 355 | padding: 0 10px 0 15px; 356 | float: right 357 | } 358 | #dlQARating { 359 | border-bottom-right-radius: 20px; 360 | padding: 0 15px 0 10px; 361 | float: left 362 | } 363 | .success { 364 | background-color: #237423 !important; 365 | } 366 | .failure { 367 | background-color: #a43636 !important; 368 | } 369 | 370 | /*Newbox Extended*/ 371 | 372 | #dlQueueExt { 373 | overflow: hidden; 374 | position: absolute; 375 | left: 10px; 376 | right: 10px; 377 | bottom: 40px; 378 | font-size: .8em; 379 | background-color: rgba(33, 133, 244, .8); 380 | padding: 10px; 381 | border-radius: 9px; 382 | top: 66px; 383 | text-align: left 384 | } 385 | /*Overlays */ 386 | 387 | .cover, 388 | .cover>div>div { 389 | background-size: cover 390 | } 391 | .cover { 392 | z-index: 10000; 393 | position: fixed; 394 | top: 0; 395 | left: 0; 396 | width: 100%; 397 | height: 100%; 398 | display: table; 399 | background-color: rgba(0, 0, 0, .25); 400 | background-position: center 401 | } 402 | .cover>div { 403 | display: table-cell; 404 | vertical-align: middle; 405 | height: inherit; 406 | text-align: center 407 | } 408 | .cover>div>div { 409 | box-shadow: 0 0 30px #000; 410 | display: inline-block; 411 | background-color: #FFF; 412 | border-radius: 20px; 413 | overflow: hidden; 414 | position: relative 415 | } 416 | /*Export */ 417 | 418 | #export_select { 419 | padding: 10px; 420 | background-color: #fff; 421 | color: #000 422 | } 423 | #export_select>div { 424 | border-top-left-radius: 10px; 425 | border-top-right-radius: 10px; 426 | overflow-y: auto; 427 | overflow-x: hidden; 428 | max-height: 490px 429 | } 430 | #export_table { 431 | width: 100% 432 | } 433 | #export_table th { 434 | text-align: center 435 | } 436 | #export_table .orderUp { 437 | cursor: pointer; 438 | height:1em; 439 | border-left: 0.4em solid transparent; 440 | border-right: 0.4em solid transparent; 441 | 442 | border-bottom: 0.9em solid rgba(0,0,0,0.2); 443 | display: inline-block; 444 | padding: 1px; 445 | margin: 0px 3px; 446 | } 447 | #export_table .orderUp:hover { 448 | border-bottom-color: black; 449 | } 450 | 451 | /*PS+ switch */ 452 | 453 | #slider, 454 | .handle { 455 | display: inline-block 456 | } 457 | #slider { 458 | vertical-align: bottom; 459 | cursor: pointer; 460 | width: 30px; 461 | height: 12px; 462 | border-radius: 10px; 463 | border: 2px solid #F0F0F0 464 | } 465 | .handle_container { 466 | text-align: center; 467 | width: 100%; 468 | height: 100% 469 | } 470 | .handle { 471 | width: 10px; 472 | height: 10px; 473 | border-radius: 100%; 474 | margin: 0 2px 6px; 475 | border: 1px solid #FFF; 476 | background-color: #85C107 477 | } 478 | /*Tooltips */ 479 | [data-tooltip] { 480 | position: relative; 481 | z-index: 2; 482 | cursor: pointer; 483 | } 484 | 485 | [data-tooltip]:before, 486 | [data-tooltip]:after { 487 | visibility: hidden; 488 | opacity: 0; 489 | pointer-events: none; 490 | } 491 | 492 | [data-tooltip]:before { 493 | --width: 300px; 494 | position: absolute; 495 | top: 150%; 496 | left: 50%; 497 | margin-left: calc(var(--width)/2*-1); 498 | padding: 7px; 499 | width: var(--width); 500 | border-radius: 3px; 501 | background-color: rgba(33,133,244,0.9); 502 | color: #fff; 503 | content: attr(data-tooltip); 504 | text-align: center; 505 | font-size: 0.9em; 506 | line-height: 1.2; 507 | box-shadow: 0px 0px 10px #FFF; 508 | } 509 | 510 | [data-tooltip]:after { 511 | position: absolute; 512 | top: calc(150% - 5px); 513 | left: 50%; 514 | margin-left: -5px; 515 | width: 0; 516 | border-bottom: 5px solid rgba(33,133,244,0.9); 517 | border-right: 5px solid transparent; 518 | border-left: 5px solid transparent; 519 | content: " "; 520 | font-size: 0; 521 | line-height: 0; 522 | } 523 | 524 | [data-tooltip]:hover:before, 525 | [data-tooltip]:hover:after { 526 | visibility: visible; 527 | opacity: 1; 528 | } 529 | 530 | /*Autocomplete */ 531 | 532 | .ui-autocomplete { 533 | z-index: 9002; 534 | max-width: 590px; 535 | max-height: 200px; 536 | overflow-y: auto; 537 | overflow-x: hidden 538 | } 539 | .ui-menu { 540 | position: fixed; 541 | border: 2px solid #F0F0F0; 542 | border-top: none; 543 | background-color: #fff 544 | } 545 | .ui-menu>.ui-menu-item * { 546 | color: #000; 547 | text-decoration: none; 548 | white-space: nowrap; 549 | text-overflow: ellipsis; 550 | cursor: pointer 551 | } 552 | .ui-menu>.ui-menu-item:nth-child(even) { 553 | background-color: #e6e6e6 554 | } 555 | .ui-menu-item .ui-state-focus { 556 | display: inline-block; 557 | width: 100%; 558 | color: #000; 559 | background-color: rgba(33, 133, 244, .7) 560 | } 561 | /*PS TV */ 562 | 563 | .psdletv { 564 | font-style: italic; 565 | font-weight: 700; 566 | font-size: .6em; 567 | vertical-align: text-top; 568 | position: absolute; 569 | top: 4px 570 | } 571 | /*PSP2 */ 572 | 573 | .psp3 { 574 | border-left: 2px solid #2185F4; 575 | border-right: 2px solid #2185F4 576 | } 577 | .psp2 { 578 | background-color: rgba(33, 133, 244, .15)!important 579 | } 580 | /* RTL Languages */ 581 | /*Container */ 582 | 583 | #muh_games_container.rtl { 584 | direction: rtl 585 | } 586 | /*Search */ 587 | 588 | .rtl #psdle_search_select { 589 | border-radius: 0px 90px 90px 0px 590 | } 591 | .rtl #psdle_search_text { 592 | border-radius: 90px 0px 0px 90px 593 | } 594 | /*Fancy Bar */ 595 | 596 | .rtl .psdle_fancy_bar span:last-of-type { 597 | border-top-left-radius: 12px; 598 | border-bottom-left-radius: 12px 599 | } 600 | .rtl .psdle_fancy_bar span:first-of-type { 601 | border-top-right-radius: 12px; 602 | border-bottom-right-radius: 12px 603 | } 604 | /*Table */ 605 | 606 | .rtl .psdle_table * { 607 | text-align: right; 608 | } 609 | 610 | /*PS+ */ 611 | 612 | .rtl tr.is_plus[id^='psdle_index_'] td:last-child { 613 | border-right: none; 614 | border-left: #FFD10D 3px solid 615 | } 616 | /* Night mode */ 617 | 618 | .psdledark #sub_container { 619 | background-color: #222; 620 | color: rgb(231, 231, 231) 621 | } 622 | .psdledark a.psdle_game_link { 623 | color: rgb(231, 231, 231) 624 | } 625 | .psdledark .search.main.container { 626 | background-color: rgba(34, 34, 34, 0.7) 627 | } 628 | .psdledark tr { 629 | background-color: rgb(34, 34, 34) 630 | } 631 | .psdledark tr:nth-child(2n) { 632 | background-color: rgb(57, 57, 57) 633 | } 634 | 635 | button.psdleClose { 636 | cursor: pointer; 637 | position: absolute; 638 | top: 0px; 639 | right: 0px; 640 | z-index: 10001; 641 | } 642 | -------------------------------------------------------------------------------- /_src/base/gotham/psdle.gotham.min.js: -------------------------------------------------------------------------------- 1 | /*! psdle 4.2.2 (c) RePod, MIT https://github.com/RePod/psdle/blob/master/LICENSE - min - compiled 2024-10-14 */ 2 | var repod={};repod.psdle={config:{version:"4.2.2",versionDate:"2024-10-14"},init:function(){console.log(`PSDLE ${this.config.version} ${this.config.versionDate}`),Object.assign(this.config,{root:repod.psdle,userData:{},gameList:[],locale:__NEXT_DATA__.props.appProps.session.userData.locale,gqlHost:__NEXT_DATA__.runtimeConfig.service.gqlBrowser.host,DOMElements:{collectionFilter:"div[data-qa=collection-filter]",filterExportContainer:"psdle-filter-section-export",filterExportSelects:"psdle-filter-export-selects",PSDLEconfigurator:"psdle-configurator"},propCache:[],catalogCache:{},catalogProps:[],catalogDatabase:this.database}),this.config.lang=this.language.getCurrent(this.config),this.css(),this.userData.init(this.config),this.reactisms.init(this.config),this.reactisms.stateChange.callback(this.config)},postInit:async function(e){e.gameList=await this.api.games(e,this.reactisms.getCurrentPage()),this.caches.props(e),this.generate.filters.section(e),e.userData.catalog&&this.api.init(e)},database:{version:1,name:"PSDLECatalogCache",db:{},init:async function(e,t){await this.persist()&&(e.userData.catalog=!0,e.root.userData.save(e),this.create.db(e,t))},persist:function(){return!(!navigator.storage||!navigator.storage.persist)&&navigator.storage.persist().then(function(e){return e})},drop:function(){this.db.hasOwnProperty("close")&&this.db.close();var e=window.indexedDB.deleteDatabase(this.name);e.onsuccess=(e=>console.log(e)),e.onerror=(e=>console.error(e)),location.reload()},create:{db:function(e,t){var o=window.indexedDB.open(e.catalogDatabase.name,e.catalogDatabase.version);o.onerror=(e=>console.error(e)),o.onupgradeneeded=function(t){e.catalogDatabase.db=o.result,e.catalogDatabase.create.upgrade(e,t,o.result)},o.onsuccess=function(r){e.catalogDatabase.db=o.result,t()}},upgrade:function(e,t,o){t.oldVersion>0&&t.oldVersionconsole.log(e)),t.transaction.onerror=(e=>console.log(e))}},transact:{get:function(e,t){return e.catalogDatabase.db.transaction("cache",t)},dumpCacheToDB:function(e,t,o){var r=this.get(e,"readwrite"),n=r.objectStore("cache");for(var[a,s]of(r.oncomplete=function(r){o?e.catalogDatabase.transact.dumpDBToCache(e,t):t(r)},r.onerror=(e=>console.log(e)),Object.entries(e.catalogCache)))n.put({id:a,json:s})},dumpDBToCache:function(e,t){var o=this.get(e),r=o.objectStore("cache"),n={};o.oncomplete=(e=>t(e)),o.onerror=(e=>console.log(e)),r.openCursor().onsuccess=function(t){var o=t.target.result;o?(n[o.key]=o.value.json,o.continue()):e.catalogCache=Object.assign({},n)}},getNewIDs:function(e,t){var o=e.gameList.map(e=>e.entitlementId),r=this.get(e,"readonly"),n=r.objectStore("cache"),a=[];r.onerror=(e=>console.log(e)),n.openCursor().onsuccess=function(e){var r=e.target.result;if(r)a.push(r.key),r.continue();else{var n=o.filter(e=>a.indexOf(e)<0);console.debug("Catalog new IDs:",n),t(n)}}}}},reactisms:{init:function(e){this.stateChange.newPushState(e)},getCurrentPage:function(e){return(this.stateChange.tracked||location.pathname).replace(/^\//,"")},stateChange:{tracked:"",timer:0,orgPushState:window.history.pushState,newPushState:function(e){!function(e,t,o){window.history.pushState=function(){t.apply(this,arguments),o.bind(e.root.reactisms.stateChange)(e,arguments)}}(e,this.orgPushState,this.callback)},callback:function(e,t){this.tracked=location.pathname,this.waitForPage(e,e.root.postInit.bind(e.root))},waitForPage:function(e,t){clearInterval(e.root.reactisms.stateChange.timer),this.timer=setInterval(function(){null!=document.querySelector(e.DOMElements.collectionFilter)?(document.querySelectorAll(`${e.DOMElements.collectionFilter} .psw-radio`).forEach(t=>t.addEventListener("click",t=>e.root.reactisms.stateChange.callback(e))),clearInterval(e.root.reactisms.stateChange.timer),t(e)):console.log('PSDLE casts "Spin Wheels" on',location.href)},125)}}},caches:{regen:function(e,t){},props:function(e){let t=["__typename","webctas","conceptId"];if(Object.assign(e,{propCache:e.gameList.map(e=>Object.keys(e)).reduce((e,t)=>t).concat(["empty","sortedIndex"])}),Object.keys(e.catalogCache).length>0){let t=[],o=Object.entries(e.catalogCache).filter(e=>null!==e[1]).reduce((e,t)=>Object.keys(t[1])).concat(t);e.catalogProps=o,e.propCache=e.propCache.concat(o)}else{let t=e.userData.exports.map(e=>e.property).filter(t=>e.propCache.indexOf(t)<0);e.catalogProps=t}e.propCache=e.propCache.filter(function(t,o){var r=e.propCache.indexOf(t)==o;return r||console.warn("Dupe in propCache:",t),r}).filter(e=>t.indexOf(e)<0).sort(),Object.keys(e.catalogCache).length>0&&e.root.exportView.section.refresh(e)}},userData:{key:"PSDLEuserData",defaults:{catalog:!1,exports:[{property:"name",title:"Name"},{property:"platform",title:"Platform"},{property:"sortedIndex",title:"Date (relative)"}]},init:function(e){e.userData=Object.assign({},this.defaults),console.debug(this.load(e))},save:function(e){localStorage.setItem(this.key,JSON.stringify(e.userData)),console.debug("Saved userData:",localStorage.getItem(this.key))},load:function(e){if(!localStorage.hasOwnProperty(this.key))return`${this.key} not found.`;try{var t=JSON.parse(localStorage.getItem(this.key));return Object.assign(e.userData,t),t}catch(e){return console.debug(e),confirm(`PSDLE had an issue loading user data. Would you like to reset it?\n\n${e}`)&&(this.remove(),location.reload()),e}},remove:function(){localStorage.removeItem(this.key)}},generate:{filters:{section:function(e){var t=this.button(e,"h4",this.logo(e),["psw-cell","psw-m-b-m","psdle-filter-button"],t=>this.toggleSectionVisibility(e,e.DOMElements.PSDLEconfigurator)),o=(this.button(e,"h4","Sort",["psw-cell","psw-m-b-m","psdle-filter-button","psdle-filter-sort"],e=>console.log(e)),this.button(e,"h4",`${e.lang.labels.exportView} (${e.gameList.length})`,["psw-cell","psw-m-b-m","psdle-filter-button","psdle-filter-export"],t=>this.toggleSectionVisibility(e,e.DOMElements.filterExportContainer))),r=this.sectionGroup(e,"div",e.root.generate.config.section(e,this),["psdle","psw-cell","psw-m-b-m","psw-round-m"],e.DOMElements.PSDLEconfigurator,!0),n=this.sectionGroup(e,"div",e.root.exportView.section.generate(e),["psdle","psw-cell","psw-m-b-m","psw-round-m"],e.DOMElements.filterExportContainer,!0);document.querySelector(e.DOMElements.collectionFilter).prepend(t,r,o,n)},logo:function(e){var t=document.createElement("div");return t.classList.add("psdle","psdle-logo"),t.title=`${e.version} ${e.versionDate}`,t},toggleSectionVisibility:function(e,t){var o=document.querySelector(`#${t}`);return o.style.display="none"==o.style.display?"":"none",o},sectionGroup:function(e,t,o,r,n,a){var s=document.createElement(t);return s.classList.add(...r),s.appendChild(o),s.id=n,a&&(s.style.display="none"),s},button:function(e,t,o,r,n){var a=document.createElement(t);return a.classList.add(...r),a.onclick=n,"object"==typeof o?a.appendChild(o):a.textContent=o,a}},config:{section:function(e){var t=document.createElement("div");return t.append(this.buttons(e)),t},buttons:function(e){var t=document.createElement("div");return t.append(this.helpers.rowButton(e,e.lang.labels.website,function(e){window.open("https://repod.github.io/psdle/","_blank")}),this.helpers.rowButton(e,e.lang.labels.deleteData,function(t){e.root.userData.remove(),e.root.database.drop()})),t.append(this.helpers.version(e)),t},helpers:{rowButton:function(e,t,o){var r=document.createElement("button");return r.innerText=t,r.onclick=o||(e=>console.log(e)),r},version:function(e){let t=document.createElement("span");return t.innerText=`${e.version} ${e.versionDate}`,t}}}},exportView:{section:{generate:function(e){var t=document.createElement("div");return t.append(this.optionPairs(e)),t.append(this.buttons(e)),t},refresh:function(e){let t=document.querySelector(`#${e.DOMElements.filterExportSelects}`),o=e.root.exportView.section.optionPairs(e);t.replaceWith(o),e.root.userData.save(e)},buttons:function(e){var t=document.createElement("div");return t.append(this.button(e,"+",t=>this.addOptionPair(e)),this.button(e,"-",t=>this.removeOptionPair(e)),document.createElement("br"),this.button(e,"Import",t=>e.root.exportView.download.importExports(e)),this.button(e,"JSON",t=>e.root.exportView.download.generate(e,"JSON")),this.button(e,"CSV",t=>e.root.exportView.download.generate(e,"CSV"))),t},button:function(e,t,o){var r=document.createElement("button");return r.innerText=t,r.onclick=o||(e=>console.log(e)),r},optionPairs:function(e){var t=document.createElement("div");for(var o in t.id=e.DOMElements.filterExportSelects,e.userData.exports){var r=e.userData.exports[o];t.append(this.optionPair(e,r.title,r.property))}return t},optionPair:function(e,t,o){var r=document.createElement("span");return r.append(this.textOption(e,t),this.selectOption(e,o)),r},addOptionPair:function(e){document.querySelector(`#${e.DOMElements.filterExportSelects}`).append(this.optionPair(e)),e.root.exportView.download.saveExports(e)},removeOptionPair:function(e){var t=document.querySelectorAll(`#${e.DOMElements.filterExportSelects} span`);t.length>0&&[...t].pop().remove(),e.root.exportView.download.saveExports(e)},selectOption:function(e,t){var o=document.createElement("select");return o.onchange=(t=>this.saveOptions(e,t)),e.propCache.forEach(function(e){var r=document.createElement("option");r.value=e,r.text=e,r.selected=e===t,o.appendChild(r)}),o},textOption:function(e,t){var o=document.createElement("input");return o.onchange=(t=>this.saveOptions(e,t)),o.type="text",o.placeholder=e.lang.labels.exportEmpty,o.defaultValue=t||"",o},saveOptions:function(e){e.root.exportView.download.saveExports(e)}},download:{importExports:function(e){try{var t=JSON.parse(prompt("",JSON.stringify(e.userData.exports)));t.map(function(t){return-1==e.propCache.indexOf(t.property)&&(console.warn("Bad property?",t.property),t.property=e.propCache[0]),t}),e.userData.exports=t}catch(e){alert(e)}e.root.exportView.section.refresh(e)},saveExports:function(e){var t=[...document.querySelectorAll(`#${e.DOMElements.filterExportSelects} > span > *`)];return e.userData.exports=t.reduce((e,t,o,r)=>"SELECT"==t.nodeName?[...e,{property:t.value,title:r[o-1].value}]:e,[]),e.root.userData.save(e),e.userData.exports},generate:function(e,t){if(0==Object.keys(e.catalogCache).length){let t=e.catalogProps;if(t.length>0&&!confirm(`${e.lang.messages.exportNoProps}\n\n`+t.join(" ")))return}"CSV"==t&&this.present(e,this.format.csv.build(e,this.format.helpers),".csv"),"JSON"==t&&this.present(e,this.format.json(e,this.format.helpers),".json")},format:{helpers:{sanitize:function(e,t,o,r){switch(o){case"sortedIndex":return e.gameList.length-t.index;case"empty":return"";default:try{var n=t[o]||e.catalogCache[t.entitlementId][o];return"descriptions"==o&&(n=n.filter(e=>"LONG"==e.type).pop().value),"price"==o&&(n=e.catalogCache[t.entitlementId].price.basePrice),"image"==o&&(n=n.url),"object"==typeof n&&(n=r?n:n.map(e=>e.type?`${e.type}:${e.value}`:e.value).join(",")),"string"==typeof n&&((n=n.replace(/"/g,'""')).indexOf(",")>-1||n.indexOf('"')>-1)&&(n=`"${n}"`),n}catch(e){return console.warn(`Couldn't find ${o} in ${t.name} ${t.entitlementId}`),""}}}},json:function(e,t){var o={version:e.version,columns:e.userData.exports,items:[]};return e.gameList.forEach(function(r,n){var a={};for(var s in e.userData.exports){var i=e.userData.exports[s].property;a[i]=t.sanitize(e,{...r,index:n},i,!0)}o.items.push(a)}),JSON.stringify(o)},csv:{build:function(e,t){var o=[];return o.push(this.rowSpecial(e,"header")),e.gameList.forEach((r,n)=>o.push(this.row(e,r,n,t))),o.push(this.rowSpecial(e,"footer")),o.join("\n")},row:function(e,t,o,r){var n=[];for(var a in e.userData.exports){var s=e.userData.exports[a].property;n.push(r.sanitize(e,{...t,index:o},s))}return n.join(",")},rowSpecial:function(e,t,o){var r=[];return"header"==t&&(r=r.concat(repod.psdle.config.userData.exports.map(e=>e.title))),"footer"==t&&(r=r.concat(repod.psdle.config.userData.exports.map(e=>e.property))).push(`"${JSON.stringify(e.userData.exports).replace(/"/g,"'")}"`,e.version),r.join(",")}}},present:function(e,t,o){var r=new Blob(["\ufeff",t],{type:"octet/stream"}),n=document.createElement("a");n.download=`psdle_${(new Date).toISOString()}${o||"_generic.txt"}`,n.href=window.URL.createObjectURL(r),n.dispatchEvent(new MouseEvent("click")),window.URL.revokeObjectURL(r)}}},api:{process:{cur:0,total:0},init:async function(e){await e.catalogDatabase.persist()?e.catalogDatabase.init(e,()=>this.fetchIDs(e)):this.catalog(e)},fetchIDs:function(e){return!1},games:function(e,t){return this.fetch(e,t).then(e=>e.json()).then(e=>e.data.purchasedTitlesRetrieve.games)},catalog:async function(e,t){var o=t||e.gameList.map(e=>e.entitlementId),r=this.process;if(r.total=o.length,this.queries.catalog.hash=await this.hash(this.queries.catalog.query),0!=o.length)for(let t of o)this.call(e,"catalog",{productId:t}).then(e=>e.json()).then(function(o){r.cur+=1,e.catalogCache[t]=o.errors?null:o.data.productRetrieve;var n=Math.ceil(r.cur/r.total*100);document.querySelector(".psdle-logo").style.background=`var(--psdle-logo-clear), linear-gradient(to right, var(--blue) ${n}%, var(--darker-blue) ${n}%)`,r.cur==r.total&&e.root.api.finish(e)});else this.finish(e)},finish:async function(e){await e.catalogDatabase.persist()?e.catalogDatabase.transact.dumpCacheToDB(e,function(){e.root.caches.props(e)},!0):e.root.caches.props(e)},call:function(e,t,o){return this.fetch(e,t,o)},fetch:function(e,t,o){var r=this.buildGQLQuery(e,t,o),n=Object.entries(r).filter(e=>"query"!==e[0]).map(e=>`${e[0]}=`+("object"==typeof e[1]?encodeURIComponent(JSON.stringify(e[1])):e[1])).join("&");return fetch(`${e.gqlHost}/op?${n}`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json","x-psn-store-locale-override":e.locale},body:JSON.stringify(r)})},buildGQLQuery:function(e,t,o){var r=this.queries[t],n={variables:o&&Object.keys(o).length>0?o:r.variables,extensions:{persistedQuery:{version:1,sha256Hash:r.hash}}};if(r.query?n.query=r.query:n.operationName=r.operationName,r.variables.platform){let t=`${e.DOMElements.collectionFilter} .psw-radio.psw-is-active .psw-radio-label`,o=document.querySelector(t).parentElement.dataset.qa.split("-").pop();r.variables.platform="all"==o?["ps4","ps5"]:[o]}return n},queries:{"recently-purchased":{operationName:"getPurchasedGameList",variables:{isActive:!0,platform:["ps4","ps5"],size:9999,sortBy:"ACTIVE_DATE",sortDirection:"desc",subscriptionService:"NONE"},hash:"00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496"},"ps-plus":{operationName:"getPurchasedGameList",variables:{platform:["ps4","ps5"],size:9999,sortBy:"ACTIVE_DATE",sortDirection:"desc",subscriptionService:"PS_PLUS"},hash:"00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496"},catalog:{query:"query queryRetrieveTelemetryDataPDPProduct($productId: String!) {\n productRetrieve(productId: $productId) {\n ... productFragment\n }\n}\nfragment productFragment on Product {\n id\n name\n publisherName\n topCategory\n releaseDate\n descriptions {\n type\n value\n }\n compatibilityNotices {\n type\n value\n }\n media {\n type\n url\n role\n }\n edition {\n name\n }\n defaultSku {\n id\n name\n type\n }\n skus {\n id\n }\n contentRating {\n name\n }\n localizedStoreDisplayClassification\n localizedGenres {\n value\n }\n price {\n basePrice\n discountedPrice\n serviceBranding\n }\n}",variables:"",hash:""}},hash:async function(e){const t=(new TextEncoder).encode(e),o=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(o)).map(e=>e.toString(16).padStart(2,"0")).join("")}},css:function(){var e=document.createElement("style");e.type="text/css",e.innerHTML='.psdle{--blue:#2185f4;--darker-blue:#063f7e;--bg-hover-filters:#e8e8e8;--psdle-logo-clear:url("data:image/webp;base64,UklGRnIAAABXRUJQVlA4TGUAAAAvU4AHEC9ApG1T/27Hzm6DINum/pwjuMAFBEX/R0OQbTOk+dMM4QYP8H8MbVsBRZEkNXMOAiAACUjAv6wMr3tG9H8Ckn3bhE1lUwEFhE0Nr2LzH4TNGLN5v5VtPgibV5YaT27fVgA=");--thin-padding:8px 16px}.psdle-logo{margin:0 auto;display:block;width:84px;height:31px;background-image:var(--psdle-logo-clear);background-color:var(--blue)}.psdle-filter-button{cursor:pointer}#psdle-configurator button{text-align:left;width:100%;padding:var(--thin-padding);font-weight:400!important;font-size:1rem}#psdle-configurator button:not(:last-child){border-bottom:.0625rem solid #dedede}#psdle-configurator,#psdle-filter-section-export{text-align:center;overflow:hidden;background-color:var(--bg-1)}#psdle-filter-section-export input{cursor:text}#psdle-filter-section-export select{border-style:solid;border:none;background-color:var(--bg-1);border-bottom:.0625rem solid #dedede}#psdle-filter-section-export select option{background-color:#fff}#psdle-filter-section-export input,#psdle-filter-section-export select{width:100%;padding:var(--thin-padding)}#psdle-configurator button:hover,#psdle-filter-section-export input:hover,#psdle-filter-section-export select:hover{background-color:var(--bg-hover-filters)}#psdle-filter-section-export button{padding:.2rem .3rem;margin:.3rem}#psdle-filter-section-export button:hover{color:var(--blue);background-color:var(--bg-hover-filters)}',document.getElementsByTagName("head")[0].appendChild(e)},language:{getCurrent:function(e,t){let o=Object.assign({},this.cache.en.us),r=(t||e.locale||"en-us").split("-");if(this.cache.hasOwnProperty(r[0])){let e={};if(this.cache[r[0]].hasOwnProperty(r[1]))e=this.cache[r[0]][r[1]];else{let t=this.cache[r[0]].def;console.warn(`${r[1]} not found for ${r[0]}, falling back to ${t}`),e=this.cache[r[0]][t]}Object.assign(o,e)}return o},cache:{en:{def:"us",us:{author:"",local:"English",labels:{exportView:"Export View",exportEmpty:"..?",deleteData:"Delete user data",catalogEnable:"Enable Catalog",website:"Website"},messages:{catalogFirstRun:"Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.",exportNoProps:"The following properties may not currently exist and could export as nothing, continue?\nThese may require running Catalog first."}}}}}},repod.psdle.init(); -------------------------------------------------------------------------------- /_src/base/valkyrie/lang/lang.min.json: -------------------------------------------------------------------------------- 1 | {"ar":{"def":"ae","ae":{"author":"Oakkom","rtl":true,"local":"العربية","startup":{"apis":"أختار الميزات التي تَود استخدامها حَرك المؤشر فوقها للمزيد من المعاومات
بعضُ الميزات لا يُمكن ابطالها","wait":"...جَار التَحْميل","start":"إبدأ"},"columns":{"icon":"الأيقونة","name":"الأسم","platform":"نَوعُ الجهاز","size":"الحَجم","date":"التاريخ"},"labels":{"exportView":"أحْفَظ الائحة","page":"الصفْحة"},"categories":{"downloadable_game":"الألعاب","demo":"الإصدارات التجريبية","add_on":"العَناصِر الأضافية","unlock":"Unlocks","unlock_key":"Unlock Keys","avatar":"صٌور رمزية","theme":"السِمات","other":"اخرى","other_game_related":"ألعاب مُتَعلِقة","game_content":"مُحتويات اللعبة","tumbler_index":"فَهرس تَمبلر (tumbler)","home":"المَنزل","ungrouped_game":"العاب غير مصنفة","promo_content":"مُحتَويات تَرويجْية","beta":"اصْدار تجريبي","application":"التَطبيقات","extras":"إضافات","unknown":"مَجهول"},"strings":{"delimiter":":أدخل الفَواصل","yes":"نَعم","no":"لا","search":"أبْحث","dlQueue":"لائِحة التَنزيل","dlList":"لائِحة الألعاب","plus":"ضبط ظاهرية العاب PlayStation Plus","queueAll":"الكُل","queueTo":"حَمل الى $SYS$","noTarget":"لا يوجد جِهاز للتَحميل اِليه","exportColumnName":"أِسم العَمود","exportProperty":"الخَصائص"},"apis":[{"internalID":"api_entitle","name":"تَاريخ الشِراء","desc":"لا يُمكن عَدم التَفعيل, أِن بَيانات شِرائك تُستَعمل لِيَتم تَشكيل الائِحة و تَحديد وَضع PlayStation Plus"},{"internalID":"api_game","name":"الفَهرس","desc":".فَعِلْ لِلحُصول على مَعلومات إضافية عَن الألعاب, مِنها الأنواع. تَزيد الوَقت اللازم لِتحميلِ اللائحة"},{"internalID":"api_queue","name":"لائحة التَنزيل","desc":".تَسمح بِزيادة أو حَذف العَناصر مِن لائحة التَنزيل, تُظهر مَعلومات لائحة التَنزيل و عَدد الأجْهِزة المُفعَلة عَلى الحِساب"},{"internalID":"api_pstv","name":"PS TV","desc":"الكشف عَن الألعاب لِجهَاز PS TV مُتوافر فَقط لِمَتجر en-us","disabled":true}]}},"de":{"def":"de","de":{"local":"Deutsch","startup":{"apis":"Wähle aus welche PS Store Funktionen du benutzen möchtest, fahre mit der Maus über die jeweiligen Menü-Einträge für weiter Informationen.
Bestimmte Store Funktionen sind möglicherweise ausgeschaltet.","wait":"Seite wird geladen, bitte warten.","start":"Starten"},"columns":{"icon":"Symbol","name":"Name","platform":"Plattform","size":"Größe","date":"Datum"},"labels":{"exportView":"Ansicht exportieren","page":"Seite"},"categories":{"downloadable_game":"Spiele","demo":"Demos","add_on":"Erweiterungen","unlock":"Freischaltbares","unlock_key":"Freischaltbare Schlüssel","avatar":"Spielerbilder","theme":"Designs","other":"Andere","home":"Startseite","promo_content":"promo_inhalt","beta":"Betas","application":"Anwendungen","extras":"Extras","unknown":"Unbekannt"},"strings":{"delimiter":"Geben sie ein Trennzeichen ein:","yes":"Ja","no":"Nein","search":"Suche","dlQueue":"Warteschlange","dlList":"Download-Liste","plus":"PS+ Inhalte sichtbar/unsichtbar machen","queueAll":"Alle","queueTo":"Herunterladen zu $SYS$","noTarget":"Es ist keine Ziel Konsole verfügbar.","exportColumnName":"Spalten-Name","exportProperty":"Eigenschaften","exportImport":"Importieren"},"apis":[{"internalID":"api_entitle","name":"Kaufverlauf","desc":"Kann nicht deaktiviert werden. Benutze den Kaufverlauf um die Download-Liste zu erstellen und den PlayStation Plus status festzustellen."},{"internalID":"api_game","name":"Katalog","desc":"Einschalten für zusätzliche Spieleinformationen, inklusive Kategorien. Verlangsamt das Erstellen der Download-Liste."},{"internalID":"api_queue","name":"Download-Warteschlange","desc":"Erlaubt das Hinzufügen und Entfernen von Titeln in der Download-Warteschlange. Liest die Download-Warteschlangeninformationen und den Aktivierungszustand der Konsole."},{"internalID":"api_pstv","desc":"PS TV kompatible Titel wurden festgestellt. Wird nur unterstützt bei \"en-us\" Internet-Store (nicht die PSDLE Sprache).","disabled":true}]}},"en":{"def":"us","us":{"local":"English","startup":{"apis":"Select which store features you would like to use, hover for more details.
Certain store features may not be disabled.","wait":"Please wait.","catalog":"Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.\n\nIf you run into issues or would like to you can revoke the persmission and clear the cache below the Start button.","start":"Start"},"columns":{"icon":"Icon","name":"Name","platform":"Platform","size":"Size","date":"Date"},"labels":{"exportView":"Export View","page":"Page"},"categories":{"downloadable_game":"Games","demo":"Demos","add_on":"Add-ons","unlock":"Unlocks","unlock_key":"Unlock Keys","avatar":"Avatars","theme":"Themes","other":"other","other_game_related":"other_game_related","game_content":"game_content","tumbler_index":"tumbler_index","home":"home","ungrouped_game":"ungrouped_game","promo_content":"promo_content","beta":"Betas","application":"Applications","extras":"Extras","unknown":"Unknown"},"strings":{"delimiter":"Enter separator:","yes":"Yes","no":"No","search":"Search","dlQueue":"Queue","dlList":"List","plus":"Toggle visibility of PS+ titles.","queueAll":"All","queueTo":"Download to $SYS$","noTarget":"There is no available target console to send to.","exportColumnName":"Column Name","exportProperty":"Property","exportImport":"Import"},"apis":[{"internalID":"api_entitle","name":"Purchase History","desc":"Cannot be disabled. Uses your purchase history to create the download list and determine PlayStation Plus status."},{"internalID":"api_game","name":"Catalog","desc":"Enable for additional game information, including categories. Increases time needed to create the download list."},{"internalID":"api_queue","name":"Download Queue","desc":"Allows adding and removing items from the download queue. Reads download queue information and console activation status."},{"internalID":"api_pstv","name":"PS TV","desc":"Detect PS TV compatible titles. Only supported on \"en-us\" web store (not PSDLE language).","disabled":true}]}},"es":{"def":"es","es":{"author":"Positronic-Brain (#18)","local":"Español","startup":{"apis":"Elija APIs a utilizar. Coloque el puntero sobre el API para visualizar detalles.
Algunos APIs no pueden ser deshabilitados.","wait":"Por favor espere...","start":"Inicio"},"columns":{"icon":"Ícono","name":"Nombre","platform":"Plataforma","size":"Tamaño","date":"Fecha"},"labels":{"exportView":"Exportar vista","page":"Página"},"categories":{"downloadable_game":"Juegos","demo":"Demos","add_on":"Complementos","unlock":"Desbloqueables","unlock_key":"Llaves","avatar":"Avatares","theme":"Temas","other":"Otros","other_game_related":"Otros","game_content":"Contenidos","tumbler_index":"tumbler_index","home":"Home","ungrouped_game":"No Clasificados","promo_content":"Promociones","beta":"Betas","application":"Aplicaciones","extras":"Extras","unknown":"Desconocido"},"strings":{"delimiter":"Ingrese delimitador:","yes":"Sí","no":"No","search":"Búsqueda","dlQueue":"Cola de Descargas","dlList":"Lista de Descargas","plus":"Alterna la visibilidad de los títulos de PS Plus.","queueAll":"Todos","queueTo":"Descargar a $SYS$"},"apis":[{"internalID":"api_entitle","name":"Licencias","desc":"No puede ser deshabilitado. Accede a la información de las compras y se utiliza para construir la lista de descargas, determinar el estado de PS Plus y otras cosas."},{"internalID":"api_game","name":"Catálogo","desc":"Accede a información adicional para determinar la consola adecuada, reparar imágenes rotas, y más."},{"internalID":"api_queue","name":"Cola de Descargas","desc":"Permite añadir y remover entradas a la cola de descargas. Lee la información de la cola de descargas y el número de consolas activadas en la cuenta."},{"internalID":"api_pstv","name":"PS TV","desc":"Detecta títulos compatibles con PS TV. Sólo soportado en la tienda de la región \"en-us\" (región, no idioma de PSDLE).","disabled":true}]}},"fr":{"def":"fr","fr":{"author":"cramoisan (#9)","local":"Français","startup":{"apis":"Sélectionner l'API à utiliser; Survoler pour plus de détails.
Certaines APIs ne peuvent pas être désactivées.","wait":"Merci de patienter.","start":"Commencer"},"columns":{"icon":"Icône","name":"Nom","platform":"Plate-forme","size":"Taille","date":"Date"},"labels":{"exportView":"Exporter la vue","page":"Page"},"categories":{"downloadable_game":"Jeux","demo":"Démos","add_on":"DLCs","unlock":"Codes de déverouillage","avatar":"Avatars","theme":"Thèmes","application":"Applications","unknown":"Inconnu"},"strings":{"delimiter":"Entrer le délimiteur:","yes":"Oui","no":"Non","search":"Rechercher","dlQueue":"Queue","dlList":"Liste","plus":"Afficher/cacher les titres PS+.","queueAll":"Tous","queueTo":"Télécharger sur $SYS$"},"apis":[{"internalID":"api_entitle","name":"Droits","desc":"Ne peut pas être désactivée. Accède aux informations d'achat afin de créer la liste de téléchargement, et déterminer le statut PS+, ainsi que d'autres choses."},{"internalID":"api_game","name":"Catalogue","desc":"Accède aux informations supplémentaires des jeux pour déterminer la plate-forme, corriger les liens d'images cassés, et plus."},{"internalID":"api_queue","name":"Liste de téléchargement","desc":"Permet d'ajouter ou de retirer des articles de la liste de téléchargement. Lit les informations de la liste de téléchargement et le nombre de consoles activées sur le compte."},{"internalID":"api_pstv","name":"PS TV","desc":"Détecte les titres compatibles PS TV. Ne marche que sur le store \"en-us\" (différent de la langue choisie pour PSDLE).","disabled":true}]}},"ja":{"def":"jp","jp":{"author":"k0ta0uchi (#36)","local":"日本語","startup":{"apis":"使用したいAPIを選択してください。ホバーすることによって詳細を確認することができます。
特定のAPIは無効化することが出来ない可能性があります。","wait":"お待ちください。","start":"開始"},"columns":{"icon":"アイコン","name":"ゲーム名","platform":"プラットフォーム","size":"サイズ","date":"日付"},"labels":{"exportView":"ビューをエクスポート","page":"ページ"},"categories":{"downloadable_game":"ゲーム","demo":"デモ","add_on":"アドオン","unlock":"アンロック","unlock_key":"アンロックキー","avatar":"アバター","theme":"テーマ","other":"その他","other_game_related":"その他ゲーム関連","game_content":"ゲームコンテンツ","tumbler_index":"タンブラーインデックス","home":"ホーム","ungrouped_game":"未分類のゲーム","promo_content":"プロモコンテンツ","beta":"ベータ","application":"アプリケーション","extras":"エキストラ","unknown":"不明"},"strings":{"delimiter":"区切り文字を入力してください:","yes":"はい","no":"いいえ","search":"検索","dlQueue":"待機リスト","dlList":"リスト","plus":"PS+タイトルの表示を切り替える。","queueAll":"全て","queueTo":"$SYS$にダウンロード","noTarget":"送信可能なコンソールが存在しません。","exportColumnName":"カラム名","exportProperty":"プロパティ"},"apis":[{"internalID":"api_entitle","name":"エンタイトルメント","desc":"無効化することは出来ません。購入情報にアクセスし、ダウンロードリストを作成、PS+の状態を確認、その他を行います。"},{"internalID":"api_game","name":"カタログ","desc":"ゲームの追加情報にアクセスし、正確なコンソールの把握、壊れたイメージの修正、その他を行います。"},{"internalID":"api_queue","name":"ダウンロード待機リスト","desc":"ダウンロード待機リストからアイテムの追加や削除の許可します。ダウンロード待機リスト情報とアカウントで有効化されたコンソールの数を読み込みます。"},{"internalID":"api_pstv","name":"PS TV","desc":"PS TV互換タイトルを検知します。\"en-us\"ウェブストアでのみサポートされます。(PSDLEの言語設定ではありません)","disabled":true}]}},"nl":{"def":"nl","nl":{"author":"Tricksy","local":"Nederlands","startup":{"apis":"Selecteer welke APIs je wilt gebruiken, hover voor meer details.
Sommige APIs kunnen niet gedeselecteerd worden.","wait":"Even geduld alstublieft.","start":"Start"},"columns":{"icon":"Icoon","name":"Naam","platform":"Platform","size":"Grootte","date":"Datum"},"labels":{"exportView":"Exporteer View","page":"Pagina"},"categories":{"downloadable_game":"Spellen","demo":"Demos","add_on":"Add-ons","unlock":"Ontgrendelingen","unlock_key":"Ontgrendelings Sleutels","avatar":"Avatars","theme":"Themas","other":"anders","other_game_related":"ander_spel_gerelateerd","game_content":"spel_inhoud","tumbler_index":"tumbler_index","home":"begin","ungrouped_game":"ongegroepeerd_spel","promo_content":"promo_inhoud","beta":"Betas","application":"Applicaties","extras":"Extras","unknown":"Onbekend"},"strings":{"delimiter":"Voer delimiter in:","yes":"Ja","no":"Nee","search":"Zoeken","dlQueue":"Wachtrij","dlList":"Lijst","plus":"Laat PS+ titels zien.","queueAll":"Alles","queueTo":"Download naar $SYS$","noTarget":"Er is geen beschikbare console om naar toe te sturen","exportColumnName":"Kolom Naam","exportProperty":"Inhoud"},"apis":[{"internalID":"api_recht","name":"Rechten","desc":"Kan niet uitgeschakeld worden. Geeft toegang tot betalings informatie om te gebruiken voor de download lijst, bepaald PS+ status, en meer."},{"internalID":"api_spel","name":"Catalogus","desc":"Geeft toegang tot extra spel informatie om de goede console te bepalen, kapotte images te fixen, en meer."},{"internalID":"api_wachtrij","name":"Download Wachtrij","desc":"Geeft toegang tot het toevoegen en verwijderen van spellen op de download wachtrij. Leest de download wachtrij informatie en het aantal geactiveerde consoles op het account."},{"internalID":"api_pstv","name":"PS TV","desc":"Detecteert titels die met PS TV werken. Werkt alleen op de \"en-us\" web store (niet PSDLE taal).","disabled":true}]}},"pl":{"def":"pl","pl":{"local":"Polski","startup":{"apis":"Zaznacz wybrane opcje; najedź kursorem, aby uzyskać więcej szczegółów.
Niektóre opcje nie mogą być odznaczone.","wait":"Proszę czekać.","start":"Start"},"columns":{"icon":"Ikona","name":"Nazwa","platform":"Platforma","size":"Rozmiar","date":"Data"},"labels":{"exportView":"Eksportuj","page":"Strona"},"categories":{"downloadable_game":"Gry","demo":"Dema","add_on":"Dodatki","unlock":"Odblokowane","unlock_key":"Kody","avatar":"Awatary","theme":"Motywy","other":"inne","other_game_related":"inne_związane_z_grami","game_content":"zawartość_gry","tumbler_index":"znacznik","home":"dom","ungrouped_game":"nieprzypisana_gra","promo_content":"zawartość_promocyjna","beta":"Bety","application":"Aplikacje","extras":"Zawartość dodatkowa","unknown":"Nieznane"},"strings":{"delimiter":"Wstaw separator:","yes":"Tak","no":"Nie","search":"Szukaj","dlQueue":"Kolejka","dlList":"Lista","plus":"Zmień widoczność tytułów PS+.","queueAll":"Wszystko","queueTo":"Pobierz do $SYS$","noTarget":"Nie ma dostępnej konsoli, do której można by wysłać zawartość.","exportColumnName":"Nazwa Kolumny","exportProperty":"Własność","exportImport":"Importuj"},"apis":[{"internalID":"api_entitle","name":"Historia Zakupów","desc":"Nie może być odznaczona. Historia zakupów zostanie użyta do stworzenia listy objektów do pobrania i określenia, czy jest to zawartość z PS+."},{"internalID":"api_game","name":"Katalog","desc":"Zaznaczenie umożliwi podanie dodatkowych informacji, w tym kategorii. Zwiększa czas potrzebny na utworzenie listy."},{"internalID":"api_queue","name":"Kolejka pobierania","desc":"Umożliwia dodawanie i usuwanie objektów z kolejki pobierania. Odczytuje informacje o stanie kolejki i sprawdza, czy kosola jest aktywna."},{"internalID":"api_pstv","name":"PS TV","desc":"Wykrywanie tytułów kompatybilnych z PS TV. Dostępne wyłącznie na stronie w języku \"en - us\" (nie w języku PSDLE).","disabled":true}]}},"pt":{"def":"br","br":{"author":"msvalle (#33)","local":"Português (Brasil)","startup":{"apis":"Selecione quais APIs você gostaria de usar, passe o mouse por cima para mais detalhes.
Algumas APIs não podem ser desabilitadas.","wait":"Por favor aguarde.","start":"Iniciar"},"columns":{"icon":"Ícone","name":"Nome","platform":"Platforma","size":"Tamanho","date":"Data"},"labels":{"exportView":"Exportar Visualização","page":"Página"},"categories":{"downloadable_game":"Jogos","demo":"Demos","add_on":"Expansões","unlock":"Desbloqueáveis","unlock_key":"Chaves","avatar":"Avatarws","theme":"Temas","other":"Outros","other_game_related":"Outros","game_content":"Conteúdo","tumbler_index":"tumbler_index","home":"Home","ungrouped_game":"Não classificado","promo_content":"Promoções","beta":"Betas","application":"Aplicações","extras":"Extras","unknown":"Desconhecido"},"strings":{"delimiter":"Entre delimitador:","stringify_error":"Erro: Navegador não possui JSON.stringify.","yes":"Sim","no":"Não","search":"Buscar por título do jogo","dlQueue":"Fila de downlaod","dlList":"Lista de download","plus":"Alterna ver títulos PS+.","queueAll":"Todos","queueTo":"Download para $SYS$"},"apis":[{"internalID":"api_entitle","name":"Licenças","desc":"Não pode ser desabilitado. Acessa informção de compra usada para criar a lista de download, determinar o status da PS+, e outras coisas."},{"internalID":"api_game","name":"Catálogo","desc":"Acessa informação adicional do jogo para determinar o console certo, corrigir imagens quebradas, e mais."},{"internalID":"api_queue","name":"Fila de download","desc":"Permite adicionar e remover itens da lista de download. Lê informação da lista de download e quantidade de consoles ativos na conta."},{"internalID":"api_pstv","name":"PS TV","desc":"Detecta títulos compatíveis com a PS TV. Somente suportado na web store \"en-us\" (não o idioma do PSDLE).","disabled":true}]}},"ru":{"def":"ru","ru":{"author":"GenosseArroganz","local":"Русский","startup":{"apis":"Выберите необходимые компоненты (для описания наведите на них указатель мыши).
Некоторые компоненты обязательны и не могут быть отключены.","wait":"Подождите…","start":"Начать!"},"columns":{"icon":"Иконка","name":"Название","platform":"Платформа","size":"Размер","date":"Дата"},"labels":{"exportView":"Экспорт списка","page":"Страница"},"categories":{"downloadable_game":"Игры","demo":"Демо","add_on":"Дополнения","unlock":"Разблокировки","unlock_key":"Ключи разблокировки","avatar":"Аватары","theme":"Темы","other":"Другое","other_game_related":"Другой связанный контент","game_content":"Игровой контент","tumbler_index":"tumbler_index","home":"PlayStation Home","ungrouped_game":"Без категории","promo_content":"Промо-материалы","beta":"Бета","application":"Приложения","extras":"Дополнительно","unknown":"Неизвестно"},"strings":{"delimiter":"Введите разделитель:","yes":"Да","no":"Нет","search":"Поиск","dlQueue":"Очередь загрузок","dlList":"Список загрузок","plus":"Скрыть/показать игры PS+","queueAll":"Все","queueTo":"Загрузить на $SYS$","noTarget":"Не обнаружено подходящего устройства","exportColumnName":"Название столбца","exportProperty":"Свойство"},"apis":[{"internalID":"api_entitle","name":"История покупок","desc":"Нельзя отключить. История ваших покупок используется для создания списка загрузок и определения статуса PlayStation Plus."},{"internalID":"api_game","name":"Каталог","desc":"Больше опций отображения списка, включая категории. Увеличивает время, необходимое для подготовки списка загрузок."},{"internalID":"api_queue","name":"Очередь загрузок","desc":"Возможность формировать очередь загрузок. Доступ к информации об очереди загрузок и активированных консолях на аккаунте."},{"internalID":"api_pstv","name":"PS TV","desc":"Определяет совместимые с PS TV игры и приложения. Только для американского магазина.","disabled":true}]}},"zh":{"def":"tw","tw":{"author":"Alexsh","local":"中文 (繁體)","startup":{"apis":"請選擇要使用的PS Store功能,滑鼠游標停留以取得項目的詳細資訊
部份功能可能無法關閉","wait":"請稍候...","start":"開始"},"columns":{"icon":"圖示","name":"名稱","platform":"平台","size":"容量","date":"購買日期"},"labels":{"exportView":"匯出","page":"Page"},"categories":{"downloadable_game":"遊戲","demo":"體驗版","add_on":"追加內容","unlock":"關卡","unlock_key":"解鎖","avatar":"個人造型","theme":"主題","other":"other","other_game_related":"other_game_related","game_content":"game_content","tumbler_index":"tumbler_index","home":"home","ungrouped_game":"ungrouped_game","promo_content":"promo_content","beta":"Betas","application":"應用程式","extras":"Extras","unknown":"Unknown"},"strings":{"delimiter":"分隔字元:","yes":"是","no":"否","search":"搜尋","dlQueue":"佇列","dlList":"清單","plus":"選擇顯示PS+遊戲","queueAll":"全部","queueTo":"下載到$SYS$","noTarget":"沒有可傳送的主機。","exportColumnName":"欄位名稱","exportProperty":"屬性"},"apis":[{"internalID":"api_entitle","name":"購買記錄","desc":"此項不可關閉,將使用購買記錄來建立下載清單及確認PlayStation Plus狀態。"},{"internalID":"api_game","name":"類別","desc":"開啟以取得更多遊戲資訊,包括類別及購買時間來建立下載清單。"},{"internalID":"api_queue","name":"下載佇列","desc":"允許從下載佇列增加/移除項目。"},{"internalID":"api_pstv","name":"PS TV","desc":"偵測Playstation Vita TV相容遊戲。目前只支援en-us區域","disabled":true}]},"cn":{"author":"Alexsh","local":"中文 (简体)","startup":{"apis":"请选择要使用的PS Store功能,鼠标光标停留以取得项目的详细信息
部份功能可能无法关闭","wait":"请稍候...","start":"开始"},"columns":{"icon":"图示","name":"名称","platform":"平台","size":"容量","date":"购买日期"},"labels":{"exportView":"汇出","page":"Page"},"categories":{"downloadable_game":"游戏","demo":"体验版","add_on":"追加内容","unlock":"关卡","unlock_key":"解锁","avatar":"个人造型","theme":"主题","other":"other","other_game_related":"other_game_related","game_content":"game_content","tumbler_index":"tumbler_index","home":"home","ungrouped_game":"ungrouped_game","promo_content":"promo_content","beta":"Betas","application":"应用程序","extras":"Extras","unknown":"Unknown"},"strings":{"delimiter":"分隔字符:","yes":"是","no":"否","search":"搜寻","dlQueue":"队列","dlList":"清单","plus":"选择显示PS+游戏","queueAll":"全部","queueTo":"下载到$SYS$","noTarget":"没有可传送的主机。","exportColumnName":"域名","exportProperty":"属性"},"apis":[{"internalID":"api_entitle","name":"购买记录","desc":"此项不可关闭,将使用购买记录来建立下载列表及确认PlayStation Plus状态。"},{"internalID":"api_game","name":"类别","desc":"开启以取得更多游戏信息,包括类别及购买时间来建立下载清单。"},{"internalID":"api_queue","name":"下载队列","desc":"允许从下载队列增加/移除项目。"},{"internalID":"api_pstv","name":"PS TV","desc":"侦测Playstation Vita TV兼容游戏。目前只支持en-us区域","disabled":true}]}}} -------------------------------------------------------------------------------- /_src/base/gotham/psdle.base.js: -------------------------------------------------------------------------------- 1 | var repod = {} 2 | repod.psdle = { 3 | config: { 4 | version: "4.869", 5 | versionDate: "202X-🔥-39" 6 | }, 7 | init: function() { 8 | console.log(`PSDLE ${this.config.version} ${this.config.versionDate}`) 9 | 10 | Object.assign(this.config, { 11 | root: repod.psdle, 12 | userData: {}, 13 | gameList: [], 14 | locale: __NEXT_DATA__.props.appProps.session.userData.locale, 15 | gqlHost: __NEXT_DATA__.runtimeConfig.service.gqlBrowser.host, 16 | DOMElements: { 17 | collectionFilter: "div[data-qa=collection-filter]", 18 | filterExportContainer: "psdle-filter-section-export", 19 | filterExportSelects: "psdle-filter-export-selects", 20 | PSDLEconfigurator: "psdle-configurator" 21 | }, 22 | propCache: [], 23 | catalogCache: {}, 24 | catalogProps: [], 25 | catalogDatabase: this.database 26 | }) 27 | 28 | //Not currently hooked up to anything like a select box. 29 | this.config.lang = this.language.getCurrent(this.config) 30 | 31 | this.css() 32 | this.userData.init(this.config) 33 | this.reactisms.init(this.config) 34 | this.reactisms.stateChange.callback(this.config) 35 | }, 36 | postInit: async function(config) { 37 | //But also on page changes. 38 | 39 | //Fetch games. 40 | config.gameList = await this.api.games(config, this.reactisms.getCurrentPage()) 41 | 42 | this.caches.props(config) 43 | this.generate.filters.section(config) 44 | 45 | if (config.userData.catalog) { 46 | this.api.init(config) 47 | } 48 | }, 49 | database: { 50 | version: 1, 51 | name: "PSDLECatalogCache", 52 | db: {}, 53 | init: async function(config, callback) { 54 | var persist = await this.persist() 55 | 56 | if (persist) { 57 | config.userData.catalog = true 58 | config.root.userData.save(config) 59 | 60 | this.create.db(config, callback) 61 | } else { 62 | //Temporary would defeat the purpose. 63 | } 64 | }, 65 | persist: function() { 66 | if (navigator.storage && navigator.storage.persist) { 67 | return navigator.storage.persist().then(function(persistent) { 68 | return persistent 69 | }) 70 | } 71 | 72 | return false 73 | }, 74 | drop: function() { 75 | if (this.db.hasOwnProperty("close")) this.db.close() 76 | 77 | var dropDatabase = window.indexedDB.deleteDatabase(this.name) 78 | dropDatabase.onsuccess = (e => console.log(e)) 79 | dropDatabase.onerror = (e => console.error(e)) 80 | location.reload() 81 | }, 82 | create: { 83 | db: function(config, callback) { 84 | var db = window.indexedDB.open( 85 | config.catalogDatabase.name, 86 | config.catalogDatabase.version 87 | ) 88 | 89 | db.onerror = (e => console.error(e)) 90 | db.onupgradeneeded = function(e) { 91 | config.catalogDatabase.db = db.result 92 | config.catalogDatabase.create.upgrade(config, e, db.result) 93 | } 94 | db.onsuccess = function(e) { 95 | config.catalogDatabase.db = db.result 96 | callback() 97 | } 98 | }, 99 | upgrade: function(config, e, db) { 100 | if (e.oldVersion > 0 && e.oldVersion < config.catalogDatabase.version) { 101 | db.deleteObjectStore("cache") 102 | } 103 | 104 | this.objectStore(config, db) 105 | }, 106 | objectStore: function(config, db) { 107 | var db = db.createObjectStore("cache", { keyPath: "id" }) 108 | db.createIndex("ID", "id", {unique: true}) 109 | 110 | db.transaction.oncomplete = (e => console.log(e)) 111 | db.transaction.onerror = (e => console.log(e)) 112 | } 113 | }, 114 | transact: { 115 | get: function(config, type) { 116 | return config.catalogDatabase.db.transaction("cache", type) 117 | }, 118 | dumpCacheToDB: function(config, callback, backToCache) { 119 | var db = this.get(config, "readwrite") 120 | var store = db.objectStore("cache") 121 | 122 | db.oncomplete = function(e) { 123 | if (backToCache) { 124 | config.catalogDatabase.transact.dumpDBToCache(config, callback) 125 | } else { 126 | callback(e) 127 | } 128 | } 129 | db.onerror = (e => console.log(e)) 130 | 131 | for (var [id, json] of Object.entries(config.catalogCache)) { 132 | store.put({"id": id, "json": json}) 133 | } 134 | }, 135 | dumpDBToCache: function(config, callback) { 136 | var db = this.get(config) 137 | var store = db.objectStore("cache") 138 | var readOut = {} 139 | 140 | db.oncomplete = (e => callback(e)) 141 | db.onerror = (e => console.log(e)) 142 | 143 | store.openCursor().onsuccess = function(event) { 144 | var cursor = event.target.result 145 | if (cursor) { 146 | readOut[cursor.key] = cursor.value.json 147 | cursor.continue() 148 | } 149 | else { 150 | config.catalogCache = Object.assign({}, readOut) 151 | } 152 | } 153 | }, 154 | getNewIDs: function(config, callback) { 155 | var games = config.gameList.map(e=>e.entitlementId) 156 | var db = this.get(config, "readonly") 157 | var store = db.objectStore("cache") 158 | var readOut = [] 159 | 160 | //db.oncomplete = (e => console.log(e)) 161 | db.onerror = (e => console.log(e)) 162 | 163 | store.openCursor().onsuccess = function(event) { 164 | var cursor = event.target.result 165 | if (cursor) { 166 | readOut.push(cursor.key) 167 | cursor.continue() 168 | } 169 | else { 170 | var newIDs = games.filter(e => readOut.indexOf(e) < 0) 171 | console.debug("Catalog new IDs:", newIDs) 172 | 173 | callback(newIDs) 174 | } 175 | } 176 | }, 177 | } 178 | }, 179 | reactisms: { 180 | init: function(config) { 181 | this.stateChange.newPushState(config) 182 | }, 183 | getCurrentPage: function(config) { 184 | //Remove leading slash. Overkill regex just in case. 185 | return (this.stateChange.tracked || location.pathname).replace(/^\//, "") 186 | }, 187 | stateChange: { 188 | tracked: "", 189 | timer: 0, 190 | orgPushState: window.history.pushState, 191 | newPushState: function(config) { 192 | (function(config, orgPushState, callback) { 193 | window.history.pushState = function() { 194 | orgPushState.apply(this, arguments) 195 | callback.bind(config.root.reactisms.stateChange)(config, arguments) 196 | } 197 | })(config, this.orgPushState, this.callback) 198 | }, 199 | callback: function(config, args) { 200 | this.tracked = location.pathname 201 | this.waitForPage(config, config.root.postInit.bind(config.root)) 202 | }, 203 | waitForPage: function(config, callback) { 204 | clearInterval(config.root.reactisms.stateChange.timer) 205 | 206 | this.timer = setInterval(function() { 207 | if (document.querySelector(config.DOMElements.collectionFilter) == null) { 208 | console.log('PSDLE casts "Spin Wheels" on', location.href) 209 | return 210 | } 211 | 212 | // Regret part 1. 213 | document.querySelectorAll(`${config.DOMElements.collectionFilter} .psw-radio`) 214 | .forEach( systemFilter => 215 | systemFilter.addEventListener("click", e => config.root.reactisms.stateChange.callback(config)) 216 | ) 217 | 218 | clearInterval(config.root.reactisms.stateChange.timer) 219 | 220 | callback(config) 221 | }, 125) 222 | } 223 | } 224 | }, 225 | caches: { 226 | regen: function(config, target) { 227 | 228 | }, 229 | props: function(config) { 230 | //Populate property cache. 231 | let customProps = ['empty','sortedIndex'] 232 | let excludeProps = ['__typename','webctas','conceptId'] 233 | 234 | Object.assign(config, { 235 | propCache: config.gameList.map(i => Object.keys(i)).reduce((i, itemKeys) => itemKeys).concat(customProps) 236 | }) 237 | 238 | if (Object.keys(config.catalogCache).length > 0) { 239 | let customProps = [] //['price'] 240 | let catalogProps = Object.entries(config.catalogCache) 241 | .filter(data => data[1] !== null) 242 | .reduce((a,b) => Object.keys(b[1])) 243 | .concat(customProps) 244 | 245 | config.catalogProps = catalogProps 246 | config.propCache = config.propCache.concat(catalogProps) 247 | } else { 248 | //Preserve props that don't exist yet. Does not include imports. 249 | let importedProps = config.userData.exports 250 | .map(e => e.property) 251 | .filter(e => config.propCache.indexOf(e) < 0) 252 | 253 | config.catalogProps = importedProps 254 | } 255 | 256 | //Dedupe and remove excluded props. 257 | config.propCache = config.propCache 258 | .filter(function(item,i) { 259 | var dupeCheck = config.propCache.indexOf(item) == i 260 | 261 | if (!dupeCheck) { 262 | console.warn("Dupe in propCache:", item) 263 | } 264 | 265 | return dupeCheck 266 | }) 267 | .filter(item => excludeProps.indexOf(item) < 0) 268 | .sort() 269 | 270 | if (Object.keys(config.catalogCache).length > 0) { 271 | config.root.exportView.section.refresh(config) 272 | } 273 | } 274 | }, 275 | userData: { 276 | key: "PSDLEuserData", 277 | defaults: { 278 | catalog: false, 279 | exports: [ 280 | {"property": "name", "title": "Name"}, 281 | {"property": "platform", "title": "Platform"}, 282 | {"property": "sortedIndex", "title": "Date (relative)"}, 283 | ] 284 | }, 285 | init: function(config) { 286 | config.userData = Object.assign({}, this.defaults) 287 | 288 | console.debug(this.load(config)) 289 | }, 290 | save: function(config) { 291 | localStorage.setItem(this.key, JSON.stringify(config.userData)) 292 | 293 | console.debug("Saved userData:", localStorage.getItem(this.key)) 294 | }, 295 | load: function(config) { 296 | if (!localStorage.hasOwnProperty(this.key)) { 297 | return `${this.key} not found.` 298 | } 299 | 300 | try { 301 | var userData = JSON.parse(localStorage.getItem(this.key)) 302 | Object.assign(config.userData, userData) 303 | 304 | return userData 305 | } catch (e) { 306 | console.debug(e) 307 | var oldReliable = confirm(`PSDLE had an issue loading user data. Would you like to reset it?\n\n${e}`) 308 | 309 | if (oldReliable) { 310 | this.remove() 311 | location.reload() 312 | } 313 | 314 | return e 315 | } 316 | }, 317 | remove: function() { 318 | localStorage.removeItem(this.key) 319 | } 320 | }, 321 | generate: { 322 | filters: { 323 | section: function(config) { 324 | //This is going to get out of hand isn't it. 325 | var psdleLogo = this.button(config, 326 | "h4", 327 | this.logo(config), 328 | ["psw-cell", "psw-m-b-m", "psdle-filter-button"], 329 | (e => this.toggleSectionVisibility(config, config.DOMElements.PSDLEconfigurator)) 330 | ) 331 | var sortCollection = this.button(config, 332 | "h4", 333 | "Sort", 334 | ["psw-cell", "psw-m-b-m", "psdle-filter-button", "psdle-filter-sort"], 335 | (e => console.log(e)) 336 | ) 337 | var exportButton = this.button(config, 338 | "h4", 339 | `${config.lang.labels.exportView} (${config.gameList.length})`, 340 | ["psw-cell", "psw-m-b-m", "psdle-filter-button", "psdle-filter-export"], 341 | (e => this.toggleSectionVisibility(config, config.DOMElements.filterExportContainer)) 342 | ) 343 | 344 | var configSection = this.sectionGroup(config, 345 | "div", 346 | config.root.generate.config.section(config, this), 347 | ["psdle", "psw-cell", "psw-m-b-m", "psw-round-m"], 348 | config.DOMElements.PSDLEconfigurator, 349 | true 350 | ) 351 | 352 | var exportSection = this.sectionGroup(config, 353 | "div", 354 | config.root.exportView.section.generate(config), 355 | ["psdle", "psw-cell", "psw-m-b-m", "psw-round-m"], 356 | config.DOMElements.filterExportContainer, 357 | true 358 | ) 359 | 360 | document.querySelector(config.DOMElements.collectionFilter).prepend( 361 | psdleLogo, 362 | configSection, 363 | exportButton, 364 | exportSection 365 | ) 366 | //sortCollection 367 | }, 368 | logo: function(config) { 369 | var logo = document.createElement("div") 370 | logo.classList.add("psdle", "psdle-logo") 371 | logo.title = `${config.version} ${config.versionDate}` 372 | //logo.onclick = (() => config.root.api.init(config)) 373 | 374 | return logo 375 | }, 376 | toggleSectionVisibility: function(config, id) { 377 | var el = document.querySelector(`#${id}`) 378 | el.style.display = (el.style.display == "none") ? "" : "none" 379 | 380 | return el 381 | }, 382 | sectionGroup: function(config, elem, content, classes, id, hidden) { 383 | var el = document.createElement(elem) 384 | el.classList.add(...classes) 385 | el.appendChild(content) 386 | el.id = id 387 | 388 | if (hidden) { 389 | el.style.display = "none" 390 | } 391 | 392 | return el 393 | }, 394 | button: function(config, elem, content, classes, onclick) { 395 | var el = document.createElement(elem) 396 | el.classList.add(...classes) 397 | el.onclick = onclick 398 | 399 | if (typeof content === "object") { 400 | el.appendChild(content) 401 | } else { 402 | el.textContent = content 403 | } 404 | 405 | return el 406 | } 407 | }, 408 | config: { 409 | section: function(config) { 410 | var el = document.createElement("div") 411 | el.append(this.buttons(config)) 412 | 413 | return el 414 | }, 415 | buttons: function(config) { 416 | var el = document.createElement("div") 417 | 418 | /* 419 | if (config.userData.catalog !== true) { 420 | el.append( 421 | this.helpers.rowButton(config, config.lang.labels.catalogEnable, function(e) { 422 | e.target.remove() 423 | alert(config.lang.messages.catalogFirstRun) 424 | config.root.api.init(config) 425 | }) 426 | ) 427 | } 428 | */ 429 | 430 | el.append( 431 | this.helpers.rowButton(config, config.lang.labels.website, function(e) { 432 | window.open("https://repod.github.io/psdle/", "_blank"); 433 | }), 434 | this.helpers.rowButton(config, config.lang.labels.deleteData, function(e) { 435 | config.root.userData.remove() 436 | config.root.database.drop() 437 | }) 438 | ) 439 | 440 | el.append(this.helpers.version(config)) 441 | 442 | return el 443 | }, 444 | helpers: { 445 | rowButton: function(config, text, onclick) { 446 | var el = document.createElement("button") 447 | el.innerText = text 448 | el.onclick = (onclick || (e => console.log(e))) 449 | 450 | return el 451 | }, 452 | version: function(config) { 453 | let versionLabel = document.createElement("span") 454 | versionLabel.innerText = `${config.version} ${config.versionDate}` 455 | 456 | return versionLabel 457 | } 458 | } 459 | } 460 | }, 461 | exportView: { 462 | section: { 463 | generate: function(config) { 464 | //Contain the optionPairs, row manipulation buttons, and CSV/JSON buttons 465 | var el = document.createElement("div") 466 | el.append(this.optionPairs(config)) 467 | el.append(this.buttons(config)) 468 | 469 | return el 470 | }, 471 | refresh: function(config) { 472 | let oldSelects = document.querySelector(`#${config.DOMElements.filterExportSelects}`) 473 | let newSelects = config.root.exportView.section.optionPairs(config) 474 | 475 | oldSelects.replaceWith(newSelects) 476 | config.root.userData.save(config) 477 | }, 478 | buttons: function(config) { 479 | var el = document.createElement("div") 480 | 481 | el.append( 482 | this.button(config, "+", (e => this.addOptionPair(config))), 483 | this.button(config, "-", (e => this.removeOptionPair(config))), 484 | document.createElement("br"), 485 | this.button(config, "Import", (e => config.root.exportView.download.importExports(config))), 486 | this.button(config, "JSON", (e => config.root.exportView.download.generate(config, "JSON"))), 487 | this.button(config, "CSV", (e => config.root.exportView.download.generate(config, "CSV"))) 488 | ) 489 | 490 | return el 491 | }, 492 | button: function(config, text, onclick) { 493 | var el = document.createElement("button") 494 | el.innerText = text 495 | el.onclick = (onclick || (e => console.log(e))) 496 | 497 | return el 498 | }, 499 | optionPairs: function(config) { 500 | //Select box and text input 501 | var el = document.createElement("div") 502 | el.id = config.DOMElements.filterExportSelects 503 | 504 | for (var i in config.userData.exports) { 505 | var userProps = config.userData.exports[i] 506 | el.append( 507 | this.optionPair(config, userProps.title, userProps.property) 508 | ) 509 | } 510 | 511 | return el 512 | }, 513 | optionPair: function(config, textOption, selectOption) { 514 | var elSpan = document.createElement("span") 515 | 516 | elSpan.append( 517 | this.textOption(config, textOption), 518 | this.selectOption(config, selectOption) 519 | ) 520 | 521 | return elSpan 522 | }, 523 | addOptionPair: function(config) { 524 | var el = document.querySelector(`#${config.DOMElements.filterExportSelects}`) 525 | 526 | el.append(this.optionPair(config)) 527 | config.root.exportView.download.saveExports(config) 528 | }, 529 | removeOptionPair: function(config) { 530 | var removeTarget = document.querySelectorAll(`#${config.DOMElements.filterExportSelects} span`) 531 | 532 | if (removeTarget.length > 0) { 533 | [...removeTarget].pop().remove() 534 | } 535 | 536 | config.root.exportView.download.saveExports(config) 537 | }, 538 | selectOption: function(config, defaultOption) { 539 | var el = document.createElement("select") 540 | el.onchange = (e => this.saveOptions(config, e)) 541 | 542 | config.propCache.forEach(function(prop) { 543 | var elOption = document.createElement("option") 544 | elOption.value = prop 545 | elOption.text = prop 546 | elOption.selected = (prop === defaultOption) 547 | 548 | el.appendChild(elOption) 549 | }) 550 | 551 | return el 552 | }, 553 | textOption: function(config, defaultText) { 554 | var el = document.createElement("input") 555 | el.onchange = (e => this.saveOptions(config, e)) 556 | el.type = "text" 557 | el.placeholder = config.lang.labels.exportEmpty 558 | el.defaultValue = (defaultText || "") 559 | 560 | return el 561 | }, 562 | saveOptions: function(config) { 563 | config.root.exportView.download.saveExports(config) 564 | } 565 | }, 566 | download: { 567 | importExports: function(config) { 568 | try { 569 | //Do we really want to error handle this. 570 | var imports = JSON.parse(prompt("", JSON.stringify(config.userData.exports))) 571 | 572 | imports.map(function(pair) { 573 | if (config.propCache.indexOf(pair.property) == -1) { 574 | console.warn("Bad property?", pair.property) 575 | pair.property = config.propCache[0] 576 | } 577 | 578 | return pair 579 | }) 580 | 581 | config.userData.exports = imports 582 | } catch (e) { 583 | alert(e) 584 | } 585 | 586 | config.root.exportView.section.refresh(config) 587 | }, 588 | saveExports: function(config) { 589 | var els = [...document.querySelectorAll(`#${config.DOMElements.filterExportSelects} > span > *`)] 590 | 591 | //Ask me how I know this is a bad idea. 592 | config.userData.exports = els.reduce((t, elem, i, src) => 593 | (elem.nodeName == "SELECT") ? [...t, {"property": elem.value, "title": src[i-1].value}] : t 594 | , []) 595 | 596 | config.root.userData.save(config) 597 | return config.userData.exports 598 | }, 599 | generate: function(config, download) { 600 | //Verify if attempting to export keys that don't exist (typically Catalog) 601 | if (Object.keys(config.catalogCache).length == 0) { 602 | let catalogProps = config.catalogProps 603 | 604 | if (catalogProps.length > 0) { 605 | if (!confirm(`${config.lang.messages.exportNoProps}\n\n` + catalogProps.join(" "))) 606 | return 607 | } 608 | } 609 | 610 | if (download == "CSV") { 611 | this.present(config, this.format.csv.build(config, this.format.helpers), ".csv") 612 | } 613 | if (download == "JSON") { 614 | this.present(config, this.format.json(config, this.format.helpers), ".json") 615 | } 616 | }, 617 | format: { 618 | helpers: { 619 | sanitize: function(config, itemKeys, userProp, toJSON) { 620 | //Handle exceptions and custom properties 621 | //Surprise, everything is an exception. Definitely can be done better. 622 | switch (userProp) { 623 | case "sortedIndex": 624 | return config.gameList.length - itemKeys.index 625 | case "empty": 626 | return "" 627 | default: 628 | try { 629 | var prop = (itemKeys[userProp] || config.catalogCache[itemKeys.entitlementId][userProp]) 630 | 631 | if (userProp == "descriptions") { 632 | prop = prop.filter(e => e.type=="LONG").pop().value 633 | } 634 | if (userProp == "price") { 635 | prop = config.catalogCache[itemKeys.entitlementId].price.basePrice 636 | } 637 | if (userProp == "image") { 638 | prop = prop.url 639 | } 640 | 641 | if (typeof prop == "object") { 642 | prop = toJSON ? prop : prop.map(e => (e.type) ? `${e.type}:${e.value}`: e.value).join(",") 643 | } 644 | if (typeof prop == "string") { 645 | prop = prop.replace(/"/g,'""') //Escape dquotes 646 | 647 | if (prop.indexOf(",") > -1 || prop.indexOf('"') > -1) { 648 | prop = `"${prop}"` 649 | } 650 | } 651 | 652 | return prop 653 | } catch (e) { 654 | console.warn(`Couldn't find ${userProp} in ${itemKeys.name} ${itemKeys.entitlementId}`) 655 | return "" 656 | } 657 | } 658 | } 659 | }, 660 | json: function(config, helpers) { 661 | var temp = { 662 | "version": config.version, 663 | "columns": config.userData.exports, 664 | "items": [] 665 | } 666 | 667 | config.gameList.forEach(function (itemKeys, index) { 668 | var tempItem = {} 669 | 670 | for (var i in config.userData.exports) { 671 | var userProp = config.userData.exports[i].property 672 | 673 | tempItem[userProp] = helpers.sanitize(config, {...itemKeys, "index": index}, userProp, true) 674 | } 675 | 676 | temp.items.push(tempItem) 677 | }) 678 | 679 | return JSON.stringify(temp) 680 | }, 681 | csv: { 682 | build: function(config, helpers) { 683 | var temp = [] 684 | 685 | temp.push(this.rowSpecial(config, "header")) 686 | 687 | config.gameList.forEach((itemKeys, index) => 688 | temp.push(this.row(config, itemKeys, index, helpers)) 689 | ) 690 | 691 | temp.push(this.rowSpecial(config, "footer")) 692 | 693 | return temp.join("\n") 694 | }, 695 | row: function(config, itemKeys, index, helpers) { 696 | var temp = [] 697 | 698 | for (var i in config.userData.exports) { 699 | var userProp = config.userData.exports[i].property 700 | 701 | temp.push(helpers.sanitize(config, {...itemKeys, "index": index}, userProp)) 702 | } 703 | 704 | return temp.join(",") 705 | }, 706 | rowSpecial: function(config, type, index) { 707 | var temp = [] 708 | 709 | if (type == "header") { 710 | temp = temp.concat(repod.psdle.config.userData.exports.map(prop => prop.title)) 711 | } 712 | 713 | if (type == "footer") { 714 | temp = temp.concat(repod.psdle.config.userData.exports.map(prop => prop.property)) 715 | temp.push( 716 | `"${JSON.stringify(config.userData.exports).replace(/"/g,"'")}"`, 717 | config.version 718 | ) 719 | } 720 | 721 | return temp.join(",") 722 | } 723 | } 724 | }, 725 | present: function(config, content, download) { 726 | var blob = new Blob(["\ufeff", content], {type: "octet/stream"}) 727 | var elExport = document.createElement("a") 728 | elExport.download = `psdle_${new Date().toISOString()}${(download || "_generic.txt")}` 729 | elExport.href = window.URL.createObjectURL(blob) 730 | 731 | elExport.dispatchEvent(new MouseEvent("click")) 732 | 733 | window.URL.revokeObjectURL(blob) 734 | } 735 | } 736 | }, 737 | api: { 738 | process: { 739 | cur: 0, 740 | total: 0 741 | }, 742 | init: async function(config) { 743 | var persistDB = await config.catalogDatabase.persist() 744 | 745 | if (persistDB) { 746 | config.catalogDatabase.init(config, (() => this.fetchIDs(config))) 747 | } else { 748 | this.catalog(config) 749 | } 750 | }, 751 | fetchIDs: function(config) { 752 | //Temporarily broken. 753 | return false; 754 | 755 | config.catalogDatabase.transact.getNewIDs( 756 | config, ((e) => this.catalog(config, e)) 757 | ) 758 | }, 759 | games: function(config, currentPage) { 760 | //Currently only recents and plus, so hard assume purchasedTitlesRetrieve. Regret later. 761 | return this.fetch(config, currentPage) 762 | .then(r => r.json()) 763 | .then(data => data.data.purchasedTitlesRetrieve.games) 764 | }, 765 | catalog: async function(config, newIDs) { 766 | var target = (newIDs || config.gameList.map(e => e.entitlementId)) 767 | var process = this.process 768 | process.total = target.length 769 | this.queries.catalog.hash = await this.hash(this.queries.catalog.query) 770 | 771 | if (target.length == 0) { 772 | this.finish(config) 773 | return 774 | } 775 | 776 | for (let id of target) { 777 | this.call(config, "catalog", {"productId": id}) 778 | .then(r => r.json()) 779 | .then(function(data) { 780 | process.cur += 1 781 | config.catalogCache[id] = (data.errors) ? null : data.data.productRetrieve 782 | 783 | var p = Math.ceil(process.cur/process.total*100) 784 | document.querySelector(".psdle-logo").style.background = `var(--psdle-logo-clear), linear-gradient(to right, var(--blue) ${p}%, var(--darker-blue) ${p}%)` 785 | 786 | if (process.cur == process.total) { 787 | config.root.api.finish(config) 788 | } 789 | }) 790 | } 791 | }, 792 | finish: async function(config) { 793 | var persistDB = await config.catalogDatabase.persist() 794 | if (persistDB) { 795 | config.catalogDatabase.transact.dumpCacheToDB( 796 | config, 797 | (function() { config.root.caches.props(config) }), 798 | true 799 | ) 800 | } else { 801 | config.root.caches.props(config) 802 | } 803 | }, 804 | call: function(config, fetchName, variables) { 805 | return this.fetch( 806 | config, 807 | fetchName, 808 | variables 809 | ) 810 | }, 811 | fetch: function(config, fetchName, variables) { 812 | //query = (query || this.queries[operationName]) 813 | 814 | var gqlRequest = this.buildGQLQuery(config, fetchName, variables) 815 | 816 | //Might as well since we're already here. 817 | var queryParams = Object.entries(gqlRequest).filter(e => e[0] !== 'query').map(e => 818 | `${e[0]}=` + ((typeof e[1] === 'object') ? encodeURIComponent(JSON.stringify(e[1])) : e[1]) 819 | ).join("&") 820 | 821 | return fetch(`${config.gqlHost}/op?${queryParams}`, { 822 | method: 'POST', 823 | credentials: 'include', 824 | headers: { 825 | 'Content-Type': 'application/json', 826 | 'Accept': 'application/json', 827 | 'x-psn-store-locale-override': config.locale 828 | }, 829 | body: JSON.stringify( 830 | gqlRequest 831 | ) 832 | }) 833 | }, 834 | buildGQLQuery: function(config, fetchName, variables) { 835 | var gqlQuery = this.queries[fetchName] 836 | var gqlRequest = { 837 | variables: (variables && Object.keys(variables).length > 0 ? variables : gqlQuery.variables), 838 | extensions: { 839 | persistedQuery: { 840 | version: 1, 841 | sha256Hash: gqlQuery.hash 842 | } 843 | } 844 | } 845 | 846 | if (gqlQuery.query) { 847 | gqlRequest.query = gqlQuery.query 848 | } else { 849 | gqlRequest.operationName = gqlQuery.operationName 850 | } 851 | 852 | //Regret part 2. This will never break. 853 | if (gqlQuery.variables.platform) { 854 | let selector = `${config.DOMElements.collectionFilter} .psw-radio.psw-is-active .psw-radio-label` 855 | let platform = document.querySelector(selector).parentElement.dataset.qa.split("-").pop() 856 | gqlQuery.variables.platform = platform == "all" ? ["ps4","ps5"] : [platform] 857 | } 858 | 859 | return gqlRequest 860 | }, 861 | queries: { 862 | "recently-purchased": { 863 | operationName: "getPurchasedGameList", 864 | variables: {"isActive":true,"platform":["ps4","ps5"],"size":9999,"sortBy":"ACTIVE_DATE","sortDirection":"desc","subscriptionService":"NONE"}, 865 | hash: "00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496" 866 | }, 867 | "ps-plus": { 868 | operationName: "getPurchasedGameList", 869 | variables: {"platform":["ps4","ps5"],"size":9999,"sortBy":"ACTIVE_DATE","sortDirection":"desc","subscriptionService":"PS_PLUS"}, 870 | hash: "00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496" 871 | }, 872 | catalog: { 873 | query: `query queryRetrieveTelemetryDataPDPProduct($productId: String!) {\n productRetrieve(productId: $productId) {\n ... productFragment\n }\n}\nfragment productFragment on Product {\n id\n name\n publisherName\n topCategory\n releaseDate\n descriptions {\n type\n value\n }\n compatibilityNotices {\n type\n value\n }\n media {\n type\n url\n role\n }\n edition {\n name\n }\n defaultSku {\n id\n name\n type\n }\n skus {\n id\n }\n contentRating {\n name\n }\n localizedStoreDisplayClassification\n localizedGenres {\n value\n }\n price {\n basePrice\n discountedPrice\n serviceBranding\n }\n}`, 874 | variables: "", 875 | hash: "" //COULD precalculate this, but effort. 876 | } 877 | }, 878 | hash: async function(string) { 879 | const msgUint8 = new TextEncoder().encode(string) 880 | const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) 881 | const hashArray = Array.from(new Uint8Array(hashBuffer)) 882 | const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') 883 | return hashHex 884 | } 885 | }, 886 | css: function() { 887 | var style = document.createElement('style') 888 | style.type = 'text/css' 889 | style.innerHTML = `{{{include "css/psdle.min.css"}}}` 890 | document.getElementsByTagName('head')[0].appendChild(style) 891 | }, 892 | language: { 893 | getCurrent: function(config, override) { 894 | let outputLang = Object.assign({}, this.cache.en.us) 895 | let locale = (override || config.locale || "en-us").split("-") 896 | 897 | if (this.cache.hasOwnProperty(locale[0])) { 898 | let target = {} 899 | 900 | if (this.cache[locale[0]].hasOwnProperty(locale[1])) { 901 | target = this.cache[locale[0]][locale[1]] 902 | } else { 903 | let def = this.cache[locale[0]].def 904 | console.warn(`${locale[1]} not found for ${locale[0]}, falling back to ${def}`) 905 | target = this.cache[locale[0]][def] 906 | } 907 | 908 | Object.assign(outputLang, target) 909 | } 910 | 911 | return outputLang 912 | }, 913 | cache: {{{include "lang/lang.min.json"}}} 914 | } 915 | } 916 | 917 | repod.psdle.init() 918 | -------------------------------------------------------------------------------- /_src/base/gotham/psdle.gotham.includes.js: -------------------------------------------------------------------------------- 1 | /*! psdle 4.2.2 (c) RePod, MIT https://github.com/RePod/psdle/blob/master/LICENSE - base - compiled 2024-10-14 */ 2 | var repod = {} 3 | repod.psdle = { 4 | config: { 5 | version: "4.2.2", 6 | versionDate: "2024-10-14" 7 | }, 8 | init: function() { 9 | console.log(`PSDLE ${this.config.version} ${this.config.versionDate}`) 10 | 11 | Object.assign(this.config, { 12 | root: repod.psdle, 13 | userData: {}, 14 | gameList: [], 15 | locale: __NEXT_DATA__.props.appProps.session.userData.locale, 16 | gqlHost: __NEXT_DATA__.runtimeConfig.service.gqlBrowser.host, 17 | DOMElements: { 18 | collectionFilter: "div[data-qa=collection-filter]", 19 | filterExportContainer: "psdle-filter-section-export", 20 | filterExportSelects: "psdle-filter-export-selects", 21 | PSDLEconfigurator: "psdle-configurator" 22 | }, 23 | propCache: [], 24 | catalogCache: {}, 25 | catalogProps: [], 26 | catalogDatabase: this.database 27 | }) 28 | 29 | //Not currently hooked up to anything like a select box. 30 | this.config.lang = this.language.getCurrent(this.config) 31 | 32 | this.css() 33 | this.userData.init(this.config) 34 | this.reactisms.init(this.config) 35 | this.reactisms.stateChange.callback(this.config) 36 | }, 37 | postInit: async function(config) { 38 | //But also on page changes. 39 | 40 | //Fetch games. 41 | config.gameList = await this.api.games(config, this.reactisms.getCurrentPage()) 42 | 43 | this.caches.props(config) 44 | this.generate.filters.section(config) 45 | 46 | if (config.userData.catalog) { 47 | this.api.init(config) 48 | } 49 | }, 50 | database: { 51 | version: 1, 52 | name: "PSDLECatalogCache", 53 | db: {}, 54 | init: async function(config, callback) { 55 | var persist = await this.persist() 56 | 57 | if (persist) { 58 | config.userData.catalog = true 59 | config.root.userData.save(config) 60 | 61 | this.create.db(config, callback) 62 | } else { 63 | //Temporary would defeat the purpose. 64 | } 65 | }, 66 | persist: function() { 67 | if (navigator.storage && navigator.storage.persist) { 68 | return navigator.storage.persist().then(function(persistent) { 69 | return persistent 70 | }) 71 | } 72 | 73 | return false 74 | }, 75 | drop: function() { 76 | if (this.db.hasOwnProperty("close")) this.db.close() 77 | 78 | var dropDatabase = window.indexedDB.deleteDatabase(this.name) 79 | dropDatabase.onsuccess = (e => console.log(e)) 80 | dropDatabase.onerror = (e => console.error(e)) 81 | location.reload() 82 | }, 83 | create: { 84 | db: function(config, callback) { 85 | var db = window.indexedDB.open( 86 | config.catalogDatabase.name, 87 | config.catalogDatabase.version 88 | ) 89 | 90 | db.onerror = (e => console.error(e)) 91 | db.onupgradeneeded = function(e) { 92 | config.catalogDatabase.db = db.result 93 | config.catalogDatabase.create.upgrade(config, e, db.result) 94 | } 95 | db.onsuccess = function(e) { 96 | config.catalogDatabase.db = db.result 97 | callback() 98 | } 99 | }, 100 | upgrade: function(config, e, db) { 101 | if (e.oldVersion > 0 && e.oldVersion < config.catalogDatabase.version) { 102 | db.deleteObjectStore("cache") 103 | } 104 | 105 | this.objectStore(config, db) 106 | }, 107 | objectStore: function(config, db) { 108 | var db = db.createObjectStore("cache", { keyPath: "id" }) 109 | db.createIndex("ID", "id", {unique: true}) 110 | 111 | db.transaction.oncomplete = (e => console.log(e)) 112 | db.transaction.onerror = (e => console.log(e)) 113 | } 114 | }, 115 | transact: { 116 | get: function(config, type) { 117 | return config.catalogDatabase.db.transaction("cache", type) 118 | }, 119 | dumpCacheToDB: function(config, callback, backToCache) { 120 | var db = this.get(config, "readwrite") 121 | var store = db.objectStore("cache") 122 | 123 | db.oncomplete = function(e) { 124 | if (backToCache) { 125 | config.catalogDatabase.transact.dumpDBToCache(config, callback) 126 | } else { 127 | callback(e) 128 | } 129 | } 130 | db.onerror = (e => console.log(e)) 131 | 132 | for (var [id, json] of Object.entries(config.catalogCache)) { 133 | store.put({"id": id, "json": json}) 134 | } 135 | }, 136 | dumpDBToCache: function(config, callback) { 137 | var db = this.get(config) 138 | var store = db.objectStore("cache") 139 | var readOut = {} 140 | 141 | db.oncomplete = (e => callback(e)) 142 | db.onerror = (e => console.log(e)) 143 | 144 | store.openCursor().onsuccess = function(event) { 145 | var cursor = event.target.result 146 | if (cursor) { 147 | readOut[cursor.key] = cursor.value.json 148 | cursor.continue() 149 | } 150 | else { 151 | config.catalogCache = Object.assign({}, readOut) 152 | } 153 | } 154 | }, 155 | getNewIDs: function(config, callback) { 156 | var games = config.gameList.map(e=>e.entitlementId) 157 | var db = this.get(config, "readonly") 158 | var store = db.objectStore("cache") 159 | var readOut = [] 160 | 161 | //db.oncomplete = (e => console.log(e)) 162 | db.onerror = (e => console.log(e)) 163 | 164 | store.openCursor().onsuccess = function(event) { 165 | var cursor = event.target.result 166 | if (cursor) { 167 | readOut.push(cursor.key) 168 | cursor.continue() 169 | } 170 | else { 171 | var newIDs = games.filter(e => readOut.indexOf(e) < 0) 172 | console.debug("Catalog new IDs:", newIDs) 173 | 174 | callback(newIDs) 175 | } 176 | } 177 | }, 178 | } 179 | }, 180 | reactisms: { 181 | init: function(config) { 182 | this.stateChange.newPushState(config) 183 | }, 184 | getCurrentPage: function(config) { 185 | //Remove leading slash. Overkill regex just in case. 186 | return (this.stateChange.tracked || location.pathname).replace(/^\//, "") 187 | }, 188 | stateChange: { 189 | tracked: "", 190 | timer: 0, 191 | orgPushState: window.history.pushState, 192 | newPushState: function(config) { 193 | (function(config, orgPushState, callback) { 194 | window.history.pushState = function() { 195 | orgPushState.apply(this, arguments) 196 | callback.bind(config.root.reactisms.stateChange)(config, arguments) 197 | } 198 | })(config, this.orgPushState, this.callback) 199 | }, 200 | callback: function(config, args) { 201 | this.tracked = location.pathname 202 | this.waitForPage(config, config.root.postInit.bind(config.root)) 203 | }, 204 | waitForPage: function(config, callback) { 205 | clearInterval(config.root.reactisms.stateChange.timer) 206 | 207 | this.timer = setInterval(function() { 208 | if (document.querySelector(config.DOMElements.collectionFilter) == null) { 209 | console.log('PSDLE casts "Spin Wheels" on', location.href) 210 | return 211 | } 212 | 213 | // Regret part 1. 214 | document.querySelectorAll(`${config.DOMElements.collectionFilter} .psw-radio`) 215 | .forEach( systemFilter => 216 | systemFilter.addEventListener("click", e => config.root.reactisms.stateChange.callback(config)) 217 | ) 218 | 219 | clearInterval(config.root.reactisms.stateChange.timer) 220 | 221 | callback(config) 222 | }, 125) 223 | } 224 | } 225 | }, 226 | caches: { 227 | regen: function(config, target) { 228 | 229 | }, 230 | props: function(config) { 231 | //Populate property cache. 232 | let customProps = ['empty','sortedIndex'] 233 | let excludeProps = ['__typename','webctas','conceptId'] 234 | 235 | Object.assign(config, { 236 | propCache: config.gameList.map(i => Object.keys(i)).reduce((i, itemKeys) => itemKeys).concat(customProps) 237 | }) 238 | 239 | if (Object.keys(config.catalogCache).length > 0) { 240 | let customProps = [] //['price'] 241 | let catalogProps = Object.entries(config.catalogCache) 242 | .filter(data => data[1] !== null) 243 | .reduce((a,b) => Object.keys(b[1])) 244 | .concat(customProps) 245 | 246 | config.catalogProps = catalogProps 247 | config.propCache = config.propCache.concat(catalogProps) 248 | } else { 249 | //Preserve props that don't exist yet. Does not include imports. 250 | let importedProps = config.userData.exports 251 | .map(e => e.property) 252 | .filter(e => config.propCache.indexOf(e) < 0) 253 | 254 | config.catalogProps = importedProps 255 | } 256 | 257 | //Dedupe and remove excluded props. 258 | config.propCache = config.propCache 259 | .filter(function(item,i) { 260 | var dupeCheck = config.propCache.indexOf(item) == i 261 | 262 | if (!dupeCheck) { 263 | console.warn("Dupe in propCache:", item) 264 | } 265 | 266 | return dupeCheck 267 | }) 268 | .filter(item => excludeProps.indexOf(item) < 0) 269 | .sort() 270 | 271 | if (Object.keys(config.catalogCache).length > 0) { 272 | config.root.exportView.section.refresh(config) 273 | } 274 | } 275 | }, 276 | userData: { 277 | key: "PSDLEuserData", 278 | defaults: { 279 | catalog: false, 280 | exports: [ 281 | {"property": "name", "title": "Name"}, 282 | {"property": "platform", "title": "Platform"}, 283 | {"property": "sortedIndex", "title": "Date (relative)"}, 284 | ] 285 | }, 286 | init: function(config) { 287 | config.userData = Object.assign({}, this.defaults) 288 | 289 | console.debug(this.load(config)) 290 | }, 291 | save: function(config) { 292 | localStorage.setItem(this.key, JSON.stringify(config.userData)) 293 | 294 | console.debug("Saved userData:", localStorage.getItem(this.key)) 295 | }, 296 | load: function(config) { 297 | if (!localStorage.hasOwnProperty(this.key)) { 298 | return `${this.key} not found.` 299 | } 300 | 301 | try { 302 | var userData = JSON.parse(localStorage.getItem(this.key)) 303 | Object.assign(config.userData, userData) 304 | 305 | return userData 306 | } catch (e) { 307 | console.debug(e) 308 | var oldReliable = confirm(`PSDLE had an issue loading user data. Would you like to reset it?\n\n${e}`) 309 | 310 | if (oldReliable) { 311 | this.remove() 312 | location.reload() 313 | } 314 | 315 | return e 316 | } 317 | }, 318 | remove: function() { 319 | localStorage.removeItem(this.key) 320 | } 321 | }, 322 | generate: { 323 | filters: { 324 | section: function(config) { 325 | //This is going to get out of hand isn't it. 326 | var psdleLogo = this.button(config, 327 | "h4", 328 | this.logo(config), 329 | ["psw-cell", "psw-m-b-m", "psdle-filter-button"], 330 | (e => this.toggleSectionVisibility(config, config.DOMElements.PSDLEconfigurator)) 331 | ) 332 | var sortCollection = this.button(config, 333 | "h4", 334 | "Sort", 335 | ["psw-cell", "psw-m-b-m", "psdle-filter-button", "psdle-filter-sort"], 336 | (e => console.log(e)) 337 | ) 338 | var exportButton = this.button(config, 339 | "h4", 340 | `${config.lang.labels.exportView} (${config.gameList.length})`, 341 | ["psw-cell", "psw-m-b-m", "psdle-filter-button", "psdle-filter-export"], 342 | (e => this.toggleSectionVisibility(config, config.DOMElements.filterExportContainer)) 343 | ) 344 | 345 | var configSection = this.sectionGroup(config, 346 | "div", 347 | config.root.generate.config.section(config, this), 348 | ["psdle", "psw-cell", "psw-m-b-m", "psw-round-m"], 349 | config.DOMElements.PSDLEconfigurator, 350 | true 351 | ) 352 | 353 | var exportSection = this.sectionGroup(config, 354 | "div", 355 | config.root.exportView.section.generate(config), 356 | ["psdle", "psw-cell", "psw-m-b-m", "psw-round-m"], 357 | config.DOMElements.filterExportContainer, 358 | true 359 | ) 360 | 361 | document.querySelector(config.DOMElements.collectionFilter).prepend( 362 | psdleLogo, 363 | configSection, 364 | exportButton, 365 | exportSection 366 | ) 367 | //sortCollection 368 | }, 369 | logo: function(config) { 370 | var logo = document.createElement("div") 371 | logo.classList.add("psdle", "psdle-logo") 372 | logo.title = `${config.version} ${config.versionDate}` 373 | //logo.onclick = (() => config.root.api.init(config)) 374 | 375 | return logo 376 | }, 377 | toggleSectionVisibility: function(config, id) { 378 | var el = document.querySelector(`#${id}`) 379 | el.style.display = (el.style.display == "none") ? "" : "none" 380 | 381 | return el 382 | }, 383 | sectionGroup: function(config, elem, content, classes, id, hidden) { 384 | var el = document.createElement(elem) 385 | el.classList.add(...classes) 386 | el.appendChild(content) 387 | el.id = id 388 | 389 | if (hidden) { 390 | el.style.display = "none" 391 | } 392 | 393 | return el 394 | }, 395 | button: function(config, elem, content, classes, onclick) { 396 | var el = document.createElement(elem) 397 | el.classList.add(...classes) 398 | el.onclick = onclick 399 | 400 | if (typeof content === "object") { 401 | el.appendChild(content) 402 | } else { 403 | el.textContent = content 404 | } 405 | 406 | return el 407 | } 408 | }, 409 | config: { 410 | section: function(config) { 411 | var el = document.createElement("div") 412 | el.append(this.buttons(config)) 413 | 414 | return el 415 | }, 416 | buttons: function(config) { 417 | var el = document.createElement("div") 418 | 419 | /* 420 | if (config.userData.catalog !== true) { 421 | el.append( 422 | this.helpers.rowButton(config, config.lang.labels.catalogEnable, function(e) { 423 | e.target.remove() 424 | alert(config.lang.messages.catalogFirstRun) 425 | config.root.api.init(config) 426 | }) 427 | ) 428 | } 429 | */ 430 | 431 | el.append( 432 | this.helpers.rowButton(config, config.lang.labels.website, function(e) { 433 | window.open("https://repod.github.io/psdle/", "_blank"); 434 | }), 435 | this.helpers.rowButton(config, config.lang.labels.deleteData, function(e) { 436 | config.root.userData.remove() 437 | config.root.database.drop() 438 | }) 439 | ) 440 | 441 | el.append(this.helpers.version(config)) 442 | 443 | return el 444 | }, 445 | helpers: { 446 | rowButton: function(config, text, onclick) { 447 | var el = document.createElement("button") 448 | el.innerText = text 449 | el.onclick = (onclick || (e => console.log(e))) 450 | 451 | return el 452 | }, 453 | version: function(config) { 454 | let versionLabel = document.createElement("span") 455 | versionLabel.innerText = `${config.version} ${config.versionDate}` 456 | 457 | return versionLabel 458 | } 459 | } 460 | } 461 | }, 462 | exportView: { 463 | section: { 464 | generate: function(config) { 465 | //Contain the optionPairs, row manipulation buttons, and CSV/JSON buttons 466 | var el = document.createElement("div") 467 | el.append(this.optionPairs(config)) 468 | el.append(this.buttons(config)) 469 | 470 | return el 471 | }, 472 | refresh: function(config) { 473 | let oldSelects = document.querySelector(`#${config.DOMElements.filterExportSelects}`) 474 | let newSelects = config.root.exportView.section.optionPairs(config) 475 | 476 | oldSelects.replaceWith(newSelects) 477 | config.root.userData.save(config) 478 | }, 479 | buttons: function(config) { 480 | var el = document.createElement("div") 481 | 482 | el.append( 483 | this.button(config, "+", (e => this.addOptionPair(config))), 484 | this.button(config, "-", (e => this.removeOptionPair(config))), 485 | document.createElement("br"), 486 | this.button(config, "Import", (e => config.root.exportView.download.importExports(config))), 487 | this.button(config, "JSON", (e => config.root.exportView.download.generate(config, "JSON"))), 488 | this.button(config, "CSV", (e => config.root.exportView.download.generate(config, "CSV"))) 489 | ) 490 | 491 | return el 492 | }, 493 | button: function(config, text, onclick) { 494 | var el = document.createElement("button") 495 | el.innerText = text 496 | el.onclick = (onclick || (e => console.log(e))) 497 | 498 | return el 499 | }, 500 | optionPairs: function(config) { 501 | //Select box and text input 502 | var el = document.createElement("div") 503 | el.id = config.DOMElements.filterExportSelects 504 | 505 | for (var i in config.userData.exports) { 506 | var userProps = config.userData.exports[i] 507 | el.append( 508 | this.optionPair(config, userProps.title, userProps.property) 509 | ) 510 | } 511 | 512 | return el 513 | }, 514 | optionPair: function(config, textOption, selectOption) { 515 | var elSpan = document.createElement("span") 516 | 517 | elSpan.append( 518 | this.textOption(config, textOption), 519 | this.selectOption(config, selectOption) 520 | ) 521 | 522 | return elSpan 523 | }, 524 | addOptionPair: function(config) { 525 | var el = document.querySelector(`#${config.DOMElements.filterExportSelects}`) 526 | 527 | el.append(this.optionPair(config)) 528 | config.root.exportView.download.saveExports(config) 529 | }, 530 | removeOptionPair: function(config) { 531 | var removeTarget = document.querySelectorAll(`#${config.DOMElements.filterExportSelects} span`) 532 | 533 | if (removeTarget.length > 0) { 534 | [...removeTarget].pop().remove() 535 | } 536 | 537 | config.root.exportView.download.saveExports(config) 538 | }, 539 | selectOption: function(config, defaultOption) { 540 | var el = document.createElement("select") 541 | el.onchange = (e => this.saveOptions(config, e)) 542 | 543 | config.propCache.forEach(function(prop) { 544 | var elOption = document.createElement("option") 545 | elOption.value = prop 546 | elOption.text = prop 547 | elOption.selected = (prop === defaultOption) 548 | 549 | el.appendChild(elOption) 550 | }) 551 | 552 | return el 553 | }, 554 | textOption: function(config, defaultText) { 555 | var el = document.createElement("input") 556 | el.onchange = (e => this.saveOptions(config, e)) 557 | el.type = "text" 558 | el.placeholder = config.lang.labels.exportEmpty 559 | el.defaultValue = (defaultText || "") 560 | 561 | return el 562 | }, 563 | saveOptions: function(config) { 564 | config.root.exportView.download.saveExports(config) 565 | } 566 | }, 567 | download: { 568 | importExports: function(config) { 569 | try { 570 | //Do we really want to error handle this. 571 | var imports = JSON.parse(prompt("", JSON.stringify(config.userData.exports))) 572 | 573 | imports.map(function(pair) { 574 | if (config.propCache.indexOf(pair.property) == -1) { 575 | console.warn("Bad property?", pair.property) 576 | pair.property = config.propCache[0] 577 | } 578 | 579 | return pair 580 | }) 581 | 582 | config.userData.exports = imports 583 | } catch (e) { 584 | alert(e) 585 | } 586 | 587 | config.root.exportView.section.refresh(config) 588 | }, 589 | saveExports: function(config) { 590 | var els = [...document.querySelectorAll(`#${config.DOMElements.filterExportSelects} > span > *`)] 591 | 592 | //Ask me how I know this is a bad idea. 593 | config.userData.exports = els.reduce((t, elem, i, src) => 594 | (elem.nodeName == "SELECT") ? [...t, {"property": elem.value, "title": src[i-1].value}] : t 595 | , []) 596 | 597 | config.root.userData.save(config) 598 | return config.userData.exports 599 | }, 600 | generate: function(config, download) { 601 | //Verify if attempting to export keys that don't exist (typically Catalog) 602 | if (Object.keys(config.catalogCache).length == 0) { 603 | let catalogProps = config.catalogProps 604 | 605 | if (catalogProps.length > 0) { 606 | if (!confirm(`${config.lang.messages.exportNoProps}\n\n` + catalogProps.join(" "))) 607 | return 608 | } 609 | } 610 | 611 | if (download == "CSV") { 612 | this.present(config, this.format.csv.build(config, this.format.helpers), ".csv") 613 | } 614 | if (download == "JSON") { 615 | this.present(config, this.format.json(config, this.format.helpers), ".json") 616 | } 617 | }, 618 | format: { 619 | helpers: { 620 | sanitize: function(config, itemKeys, userProp, toJSON) { 621 | //Handle exceptions and custom properties 622 | //Surprise, everything is an exception. Definitely can be done better. 623 | switch (userProp) { 624 | case "sortedIndex": 625 | return config.gameList.length - itemKeys.index 626 | case "empty": 627 | return "" 628 | default: 629 | try { 630 | var prop = (itemKeys[userProp] || config.catalogCache[itemKeys.entitlementId][userProp]) 631 | 632 | if (userProp == "descriptions") { 633 | prop = prop.filter(e => e.type=="LONG").pop().value 634 | } 635 | if (userProp == "price") { 636 | prop = config.catalogCache[itemKeys.entitlementId].price.basePrice 637 | } 638 | if (userProp == "image") { 639 | prop = prop.url 640 | } 641 | 642 | if (typeof prop == "object") { 643 | prop = toJSON ? prop : prop.map(e => (e.type) ? `${e.type}:${e.value}`: e.value).join(",") 644 | } 645 | if (typeof prop == "string") { 646 | prop = prop.replace(/"/g,'""') //Escape dquotes 647 | 648 | if (prop.indexOf(",") > -1 || prop.indexOf('"') > -1) { 649 | prop = `"${prop}"` 650 | } 651 | } 652 | 653 | return prop 654 | } catch (e) { 655 | console.warn(`Couldn't find ${userProp} in ${itemKeys.name} ${itemKeys.entitlementId}`) 656 | return "" 657 | } 658 | } 659 | } 660 | }, 661 | json: function(config, helpers) { 662 | var temp = { 663 | "version": config.version, 664 | "columns": config.userData.exports, 665 | "items": [] 666 | } 667 | 668 | config.gameList.forEach(function (itemKeys, index) { 669 | var tempItem = {} 670 | 671 | for (var i in config.userData.exports) { 672 | var userProp = config.userData.exports[i].property 673 | 674 | tempItem[userProp] = helpers.sanitize(config, {...itemKeys, "index": index}, userProp, true) 675 | } 676 | 677 | temp.items.push(tempItem) 678 | }) 679 | 680 | return JSON.stringify(temp) 681 | }, 682 | csv: { 683 | build: function(config, helpers) { 684 | var temp = [] 685 | 686 | temp.push(this.rowSpecial(config, "header")) 687 | 688 | config.gameList.forEach((itemKeys, index) => 689 | temp.push(this.row(config, itemKeys, index, helpers)) 690 | ) 691 | 692 | temp.push(this.rowSpecial(config, "footer")) 693 | 694 | return temp.join("\n") 695 | }, 696 | row: function(config, itemKeys, index, helpers) { 697 | var temp = [] 698 | 699 | for (var i in config.userData.exports) { 700 | var userProp = config.userData.exports[i].property 701 | 702 | temp.push(helpers.sanitize(config, {...itemKeys, "index": index}, userProp)) 703 | } 704 | 705 | return temp.join(",") 706 | }, 707 | rowSpecial: function(config, type, index) { 708 | var temp = [] 709 | 710 | if (type == "header") { 711 | temp = temp.concat(repod.psdle.config.userData.exports.map(prop => prop.title)) 712 | } 713 | 714 | if (type == "footer") { 715 | temp = temp.concat(repod.psdle.config.userData.exports.map(prop => prop.property)) 716 | temp.push( 717 | `"${JSON.stringify(config.userData.exports).replace(/"/g,"'")}"`, 718 | config.version 719 | ) 720 | } 721 | 722 | return temp.join(",") 723 | } 724 | } 725 | }, 726 | present: function(config, content, download) { 727 | var blob = new Blob(["\ufeff", content], {type: "octet/stream"}) 728 | var elExport = document.createElement("a") 729 | elExport.download = `psdle_${new Date().toISOString()}${(download || "_generic.txt")}` 730 | elExport.href = window.URL.createObjectURL(blob) 731 | 732 | elExport.dispatchEvent(new MouseEvent("click")) 733 | 734 | window.URL.revokeObjectURL(blob) 735 | } 736 | } 737 | }, 738 | api: { 739 | process: { 740 | cur: 0, 741 | total: 0 742 | }, 743 | init: async function(config) { 744 | var persistDB = await config.catalogDatabase.persist() 745 | 746 | if (persistDB) { 747 | config.catalogDatabase.init(config, (() => this.fetchIDs(config))) 748 | } else { 749 | this.catalog(config) 750 | } 751 | }, 752 | fetchIDs: function(config) { 753 | //Temporarily broken. 754 | return false; 755 | 756 | config.catalogDatabase.transact.getNewIDs( 757 | config, ((e) => this.catalog(config, e)) 758 | ) 759 | }, 760 | games: function(config, currentPage) { 761 | //Currently only recents and plus, so hard assume purchasedTitlesRetrieve. Regret later. 762 | return this.fetch(config, currentPage) 763 | .then(r => r.json()) 764 | .then(data => data.data.purchasedTitlesRetrieve.games) 765 | }, 766 | catalog: async function(config, newIDs) { 767 | var target = (newIDs || config.gameList.map(e => e.entitlementId)) 768 | var process = this.process 769 | process.total = target.length 770 | this.queries.catalog.hash = await this.hash(this.queries.catalog.query) 771 | 772 | if (target.length == 0) { 773 | this.finish(config) 774 | return 775 | } 776 | 777 | for (let id of target) { 778 | this.call(config, "catalog", {"productId": id}) 779 | .then(r => r.json()) 780 | .then(function(data) { 781 | process.cur += 1 782 | config.catalogCache[id] = (data.errors) ? null : data.data.productRetrieve 783 | 784 | var p = Math.ceil(process.cur/process.total*100) 785 | document.querySelector(".psdle-logo").style.background = `var(--psdle-logo-clear), linear-gradient(to right, var(--blue) ${p}%, var(--darker-blue) ${p}%)` 786 | 787 | if (process.cur == process.total) { 788 | config.root.api.finish(config) 789 | } 790 | }) 791 | } 792 | }, 793 | finish: async function(config) { 794 | var persistDB = await config.catalogDatabase.persist() 795 | if (persistDB) { 796 | config.catalogDatabase.transact.dumpCacheToDB( 797 | config, 798 | (function() { config.root.caches.props(config) }), 799 | true 800 | ) 801 | } else { 802 | config.root.caches.props(config) 803 | } 804 | }, 805 | call: function(config, fetchName, variables) { 806 | return this.fetch( 807 | config, 808 | fetchName, 809 | variables 810 | ) 811 | }, 812 | fetch: function(config, fetchName, variables) { 813 | //query = (query || this.queries[operationName]) 814 | 815 | var gqlRequest = this.buildGQLQuery(config, fetchName, variables) 816 | 817 | //Might as well since we're already here. 818 | var queryParams = Object.entries(gqlRequest).filter(e => e[0] !== 'query').map(e => 819 | `${e[0]}=` + ((typeof e[1] === 'object') ? encodeURIComponent(JSON.stringify(e[1])) : e[1]) 820 | ).join("&") 821 | 822 | return fetch(`${config.gqlHost}/op?${queryParams}`, { 823 | method: 'POST', 824 | credentials: 'include', 825 | headers: { 826 | 'Content-Type': 'application/json', 827 | 'Accept': 'application/json', 828 | 'x-psn-store-locale-override': config.locale 829 | }, 830 | body: JSON.stringify( 831 | gqlRequest 832 | ) 833 | }) 834 | }, 835 | buildGQLQuery: function(config, fetchName, variables) { 836 | var gqlQuery = this.queries[fetchName] 837 | var gqlRequest = { 838 | variables: (variables && Object.keys(variables).length > 0 ? variables : gqlQuery.variables), 839 | extensions: { 840 | persistedQuery: { 841 | version: 1, 842 | sha256Hash: gqlQuery.hash 843 | } 844 | } 845 | } 846 | 847 | if (gqlQuery.query) { 848 | gqlRequest.query = gqlQuery.query 849 | } else { 850 | gqlRequest.operationName = gqlQuery.operationName 851 | } 852 | 853 | //Regret part 2. This will never break. 854 | if (gqlQuery.variables.platform) { 855 | let selector = `${config.DOMElements.collectionFilter} .psw-radio.psw-is-active .psw-radio-label` 856 | let platform = document.querySelector(selector).parentElement.dataset.qa.split("-").pop() 857 | gqlQuery.variables.platform = platform == "all" ? ["ps4","ps5"] : [platform] 858 | } 859 | 860 | return gqlRequest 861 | }, 862 | queries: { 863 | "recently-purchased": { 864 | operationName: "getPurchasedGameList", 865 | variables: {"isActive":true,"platform":["ps4","ps5"],"size":9999,"sortBy":"ACTIVE_DATE","sortDirection":"desc","subscriptionService":"NONE"}, 866 | hash: "00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496" 867 | }, 868 | "ps-plus": { 869 | operationName: "getPurchasedGameList", 870 | variables: {"platform":["ps4","ps5"],"size":9999,"sortBy":"ACTIVE_DATE","sortDirection":"desc","subscriptionService":"PS_PLUS"}, 871 | hash: "00694ada3d374422aa34564e91a0589f23c5f52e0e9a703b19d065dedceb3496" 872 | }, 873 | catalog: { 874 | query: `query queryRetrieveTelemetryDataPDPProduct($productId: String!) {\n productRetrieve(productId: $productId) {\n ... productFragment\n }\n}\nfragment productFragment on Product {\n id\n name\n publisherName\n topCategory\n releaseDate\n descriptions {\n type\n value\n }\n compatibilityNotices {\n type\n value\n }\n media {\n type\n url\n role\n }\n edition {\n name\n }\n defaultSku {\n id\n name\n type\n }\n skus {\n id\n }\n contentRating {\n name\n }\n localizedStoreDisplayClassification\n localizedGenres {\n value\n }\n price {\n basePrice\n discountedPrice\n serviceBranding\n }\n}`, 875 | variables: "", 876 | hash: "" //COULD precalculate this, but effort. 877 | } 878 | }, 879 | hash: async function(string) { 880 | const msgUint8 = new TextEncoder().encode(string) 881 | const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) 882 | const hashArray = Array.from(new Uint8Array(hashBuffer)) 883 | const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') 884 | return hashHex 885 | } 886 | }, 887 | css: function() { 888 | var style = document.createElement('style') 889 | style.type = 'text/css' 890 | style.innerHTML = `.psdle{--blue:#2185f4;--darker-blue:#063f7e;--bg-hover-filters:#e8e8e8;--psdle-logo-clear:url("data:image/webp;base64,UklGRnIAAABXRUJQVlA4TGUAAAAvU4AHEC9ApG1T/27Hzm6DINum/pwjuMAFBEX/R0OQbTOk+dMM4QYP8H8MbVsBRZEkNXMOAiAACUjAv6wMr3tG9H8Ckn3bhE1lUwEFhE0Nr2LzH4TNGLN5v5VtPgibV5YaT27fVgA=");--thin-padding:8px 16px}.psdle-logo{margin:0 auto;display:block;width:84px;height:31px;background-image:var(--psdle-logo-clear);background-color:var(--blue)}.psdle-filter-button{cursor:pointer}#psdle-configurator button{text-align:left;width:100%;padding:var(--thin-padding);font-weight:400!important;font-size:1rem}#psdle-configurator button:not(:last-child){border-bottom:.0625rem solid #dedede}#psdle-configurator,#psdle-filter-section-export{text-align:center;overflow:hidden;background-color:var(--bg-1)}#psdle-filter-section-export input{cursor:text}#psdle-filter-section-export select{border-style:solid;border:none;background-color:var(--bg-1);border-bottom:.0625rem solid #dedede}#psdle-filter-section-export select option{background-color:#fff}#psdle-filter-section-export input,#psdle-filter-section-export select{width:100%;padding:var(--thin-padding)}#psdle-configurator button:hover,#psdle-filter-section-export input:hover,#psdle-filter-section-export select:hover{background-color:var(--bg-hover-filters)}#psdle-filter-section-export button{padding:.2rem .3rem;margin:.3rem}#psdle-filter-section-export button:hover{color:var(--blue);background-color:var(--bg-hover-filters)}` 891 | document.getElementsByTagName('head')[0].appendChild(style) 892 | }, 893 | language: { 894 | getCurrent: function(config, override) { 895 | let outputLang = Object.assign({}, this.cache.en.us) 896 | let locale = (override || config.locale || "en-us").split("-") 897 | 898 | if (this.cache.hasOwnProperty(locale[0])) { 899 | let target = {} 900 | 901 | if (this.cache[locale[0]].hasOwnProperty(locale[1])) { 902 | target = this.cache[locale[0]][locale[1]] 903 | } else { 904 | let def = this.cache[locale[0]].def 905 | console.warn(`${locale[1]} not found for ${locale[0]}, falling back to ${def}`) 906 | target = this.cache[locale[0]][def] 907 | } 908 | 909 | Object.assign(outputLang, target) 910 | } 911 | 912 | return outputLang 913 | }, 914 | cache: {"en":{"def":"us","us":{"author":"","local":"English","labels":{"exportView":"Export View","exportEmpty":"..?","deleteData":"Delete user data","catalogEnable":"Enable Catalog","website":"Website"},"messages":{"catalogFirstRun":"Your browser may prompt you for storage permissions. PSDLE will use this to store Catalog responses for quicker and automatic startup.\n\nGranting permission is only required for these benefits.","exportNoProps":"The following properties may not currently exist and could export as nothing, continue?\nThese may require running Catalog first."}}}} 915 | } 916 | } 917 | 918 | repod.psdle.init() 919 | --------------------------------------------------------------------------------