├── _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 |
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 |
--------------------------------------------------------------------------------
/logo/README.md:
--------------------------------------------------------------------------------
1 | [ 22x8](1_psdle_do_not_even.png)
2 |
3 | [ 42x16](2_psdle_tiny.png)
4 |
5 | [ 84x61](3_psdle_mini.png) (common)
6 |
7 | [ 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 | [](//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 |
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 |
--------------------------------------------------------------------------------