Merge pull request #1424 from Materialious/update/1.15.3

Update/1.15.3
This commit is contained in:
Ward
2026-02-15 21:53:31 +13:00
committed by GitHub
18 changed files with 728 additions and 452 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "us.materialio.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 210
versionName "1.15.1"
versionCode 211
versionName "1.15.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -75,7 +75,11 @@
<release version="1.15.1" date="2026-2-15">
<release version="1.15.3" date="2026-2-15">
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.3</url>
</release>
<release version="1.15.1" date="2026-2-15">
<url>https://github.com/Materialious/Materialious/releases/tag/1.15.1</url>
</release>
<release version="1.15.0" date="2026-2-13">
+109 -162
View File
@@ -1,12 +1,12 @@
{
"name": "Materialious",
"version": "1.15.1",
"version": "1.15.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Materialious",
"version": "1.15.1",
"version": "1.15.3",
"license": "MIT",
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
@@ -18,7 +18,7 @@
"electron-unhandled": "^4.0.1",
"electron-updater": "^6.3.4",
"electron-window-state": "^5.0.3",
"jsdom": "^27.4.0",
"jsdom": "^28.0.0",
"jsonfile": "^6.2.0",
"youtubei.js": "^16.0.1"
},
@@ -30,28 +30,28 @@
}
},
"node_modules/@acemir/cssom": {
"version": "0.9.28",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.28.tgz",
"integrity": "sha512-LuS6IVEivI75vKN8S04qRD+YySP0RmU/cV8UNukhQZvprxF+76Z43TNo/a08eCodaGhT1Us8etqS1ZRY9/Or0A==",
"version": "0.9.31",
"resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz",
"integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==",
"license": "MIT"
},
"node_modules/@asamuzakjp/css-color": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz",
"integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz",
"integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==",
"license": "MIT",
"dependencies": {
"@csstools/css-calc": "^2.1.4",
"@csstools/css-color-parser": "^3.1.0",
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4",
"lru-cache": "^11.2.2"
"@csstools/css-calc": "^3.0.0",
"@csstools/css-color-parser": "^4.0.1",
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0",
"lru-cache": "^11.2.5"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
"integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
@@ -221,9 +221,9 @@
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz",
"integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==",
"funding": [
{
"type": "github",
@@ -236,13 +236,13 @@
],
"license": "MIT-0",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
"integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
"funding": [
{
"type": "github",
@@ -255,17 +255,17 @@
],
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz",
"integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==",
"funding": [
{
"type": "github",
@@ -278,21 +278,21 @@
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
"@csstools/color-helpers": "^6.0.1",
"@csstools/css-calc": "^3.0.0"
},
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-parser-algorithms": "^4.0.0",
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"funding": [
{
"type": "github",
@@ -306,16 +306,16 @@
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
"node": ">=20.19.0"
},
"peerDependencies": {
"@csstools/css-tokenizer": "^3.0.4"
"@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz",
"integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==",
"version": "1.0.27",
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz",
"integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==",
"funding": [
{
"type": "github",
@@ -326,18 +326,12 @@
"url": "https://opencollective.com/csstools"
}
],
"license": "MIT-0",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.4"
}
"license": "MIT-0"
},
"node_modules/@csstools/css-tokenizer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"funding": [
{
"type": "github",
@@ -351,7 +345,7 @@
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
"node": ">=20.19.0"
}
},
"node_modules/@develar/schema-utils": {
@@ -1049,18 +1043,18 @@
}
},
"node_modules/@exodus/bytes": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.7.0.tgz",
"integrity": "sha512-5i+BtvujK/vM07YCGDyz4C4AyDzLmhxHMtM5HpUyPRtJPBdFPsj290ffXW+UXY21/G7GtXeHD2nRmq0T1ShyQQ==",
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz",
"integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==",
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@exodus/crypto": "^1.0.0-rc.4"
"@noble/hashes": "^1.8.0 || ^2.0.0"
},
"peerDependenciesMeta": {
"@exodus/crypto": {
"@noble/hashes": {
"optional": true
}
}
@@ -2794,30 +2788,40 @@
}
},
"node_modules/cssstyle": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.4.tgz",
"integrity": "sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw==",
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz",
"integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^4.1.0",
"@csstools/css-syntax-patches-for-csstree": "1.0.14",
"css-tree": "^3.1.0"
"@asamuzakjp/css-color": "^4.1.1",
"@csstools/css-syntax-patches-for-csstree": "^1.0.21",
"css-tree": "^3.1.0",
"lru-cache": "^11.2.4"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cssstyle/node_modules/lru-cache": {
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/data-urls": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz",
"integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"license": "MIT",
"dependencies": {
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^15.0.0"
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.0"
},
"engines": {
"node": ">=20"
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/debug": {
@@ -4487,16 +4491,16 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "27.4.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz",
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"version": "28.0.0",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz",
"integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==",
"license": "MIT",
"dependencies": {
"@acemir/cssom": "^0.9.28",
"@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.7.6",
"@exodus/bytes": "^1.6.0",
"cssstyle": "^5.3.4",
"data-urls": "^6.0.0",
"@exodus/bytes": "^1.11.0",
"cssstyle": "^5.3.7",
"data-urls": "^7.0.0",
"decimal.js": "^10.6.0",
"html-encoding-sniffer": "^6.0.0",
"http-proxy-agent": "^7.0.2",
@@ -4506,11 +4510,11 @@
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
"tough-cookie": "^6.0.0",
"undici": "^7.20.0",
"w3c-xmlserializer": "^5.0.0",
"webidl-conversions": "^8.0.0",
"whatwg-mimetype": "^4.0.0",
"whatwg-url": "^15.1.0",
"ws": "^8.18.3",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
"whatwg-url": "^16.0.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
@@ -4997,24 +5001,6 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/native-run": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.3.tgz",
@@ -5396,6 +5382,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
@@ -5426,35 +5413,6 @@
"node": ">=10.4.0"
}
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postject": {
"version": "1.0.0-alpha.6",
"resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
@@ -6458,6 +6416,15 @@
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
@@ -6569,34 +6536,35 @@
}
},
"node_modules/webidl-conversions": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz",
"integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=20"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"license": "MIT",
"engines": {
"node": ">=18"
"node": ">=20"
}
},
"node_modules/whatwg-url": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz",
"integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz",
"integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==",
"license": "MIT",
"dependencies": {
"@exodus/bytes": "^1.11.0",
"tr46": "^6.0.0",
"webidl-conversions": "^8.0.0"
"webidl-conversions": "^8.0.1"
},
"engines": {
"node": ">=20"
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -6666,27 +6634,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "Materialious",
"version": "1.15.1",
"version": "1.15.3",
"description": "Modern material design for YouTube and Invidious.",
"author": {
"name": "Ward Pearce",
@@ -31,7 +31,7 @@
"electron-unhandled": "^4.0.1",
"electron-updater": "^6.3.4",
"electron-window-state": "^5.0.3",
"jsdom": "^27.4.0",
"jsdom": "^28.0.0",
"jsonfile": "^6.2.0",
"youtubei.js": "^16.0.1"
},
+68 -68
View File
@@ -1,12 +1,12 @@
{
"name": "materialious",
"version": "1.15.1",
"version": "1.15.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "materialious",
"version": "1.15.1",
"version": "1.15.3",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/electron": "^5.0.1",
@@ -31,7 +31,7 @@
"comlink": "^4.4.2",
"cookie-signature": "^1.2.2",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"dexie": "^4.3.0",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
@@ -85,7 +85,7 @@
"svelte-check": "^4.3.3",
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.53.1",
"typescript-eslint": "^8.55.0",
"vite": "^7.3.1"
}
},
@@ -4772,17 +4772,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz",
"integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz",
"integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/type-utils": "8.53.1",
"@typescript-eslint/utils": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/type-utils": "8.55.0",
"@typescript-eslint/utils": "8.55.0",
"@typescript-eslint/visitor-keys": "8.55.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -4795,7 +4795,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.53.1",
"@typescript-eslint/parser": "^8.55.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -4811,17 +4811,17 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz",
"integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz",
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
"@typescript-eslint/typescript-estree": "8.55.0",
"@typescript-eslint/visitor-keys": "8.55.0",
"debug": "^4.4.3"
},
"engines": {
@@ -4862,14 +4862,14 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz",
"integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz",
"integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.53.1",
"@typescript-eslint/types": "^8.53.1",
"@typescript-eslint/tsconfig-utils": "^8.55.0",
"@typescript-eslint/types": "^8.55.0",
"debug": "^4.4.3"
},
"engines": {
@@ -4909,14 +4909,14 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz",
"integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1"
"@typescript-eslint/types": "8.55.0",
"@typescript-eslint/visitor-keys": "8.55.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4927,9 +4927,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz",
"integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz",
"integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4944,15 +4944,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz",
"integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz",
"integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1",
"@typescript-eslint/utils": "8.53.1",
"@typescript-eslint/types": "8.55.0",
"@typescript-eslint/typescript-estree": "8.55.0",
"@typescript-eslint/utils": "8.55.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -4994,9 +4994,9 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/types": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz",
"integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5008,16 +5008,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz",
"integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.53.1",
"@typescript-eslint/tsconfig-utils": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/visitor-keys": "8.53.1",
"@typescript-eslint/project-service": "8.55.0",
"@typescript-eslint/tsconfig-utils": "8.55.0",
"@typescript-eslint/types": "8.55.0",
"@typescript-eslint/visitor-keys": "8.55.0",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
@@ -5087,16 +5087,16 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/utils": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz",
"integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.53.1",
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1"
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
"@typescript-eslint/typescript-estree": "8.55.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5111,13 +5111,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz",
"integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.53.1",
"@typescript-eslint/types": "8.55.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -7264,9 +7264,9 @@
"license": "MIT"
},
"node_modules/dexie": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.2.1.tgz",
"integrity": "sha512-Ckej0NS6jxQ4Po3OrSQBFddayRhTCic2DoCAG5zacOfOVB9P2Q5Xc5uL/nVa7ZVs+HdMnvUPzLFCB/JwpB6Csg==",
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-4.3.0.tgz",
"integrity": "sha512-5EeoQpJvMKHe6zWt/FSIIuRa3CWlZeIl6zKXt+Lz7BU6RoRRLgX9dZEynRfXrkLcldKYCBiz7xekTEylnie1Ug==",
"license": "Apache-2.0"
},
"node_modules/dezalgo": {
@@ -15207,16 +15207,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.53.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz",
"integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==",
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz",
"integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.53.1",
"@typescript-eslint/parser": "8.53.1",
"@typescript-eslint/typescript-estree": "8.53.1",
"@typescript-eslint/utils": "8.53.1"
"@typescript-eslint/eslint-plugin": "8.55.0",
"@typescript-eslint/parser": "8.55.0",
"@typescript-eslint/typescript-estree": "8.55.0",
"@typescript-eslint/utils": "8.55.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "materialious",
"version": "1.15.1",
"version": "1.15.3",
"private": true,
"scripts": {
"dev": "npm run patch:github && vite dev",
@@ -40,7 +40,7 @@
"svelte-check": "^4.3.3",
"tslib": "^2.7.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.53.1",
"typescript-eslint": "^8.55.0",
"vite": "^7.3.1"
},
"type": "module",
@@ -67,7 +67,7 @@
"comlink": "^4.4.2",
"cookie-signature": "^1.2.2",
"dayjs": "^1.11.19",
"dexie": "^4.2.1",
"dexie": "^4.3.0",
"fuse.js": "^7.0.0",
"googlevideo": "^4.0.4",
"he": "^1.2.0",
+34
View File
@@ -220,6 +220,40 @@ export async function loginUserBackend(
return true;
}
export async function addOrUpdateKeyValue(key: string, value: string) {
const valueEncrypted = await encryptWithMasterKey(value);
await fetch(`/api/user/keyValue/${key}`, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
valueCipher: valueEncrypted?.cipher,
valueNonce: valueEncrypted?.nonce
})
});
}
export async function deleteKeyValue(key: string) {
await fetch(`/api/user/keyValue/${key}`, {
method: 'DELETE',
credentials: 'same-origin'
});
}
export type KeyValue = boolean | number | string[] | string | object | undefined;
export async function getKeyValue(key: string): Promise<KeyValue | null> {
const resp = await fetch(`/api/user/keyValue/${key}`, {
method: 'GET',
credentials: 'same-origin'
});
if (!resp.ok) return null;
const respJson = await resp.json();
return (await decryptWithMasterKey(respJson.valueNonce, respJson.valueCipher)) ?? null;
}
async function encryptWithMasterKey(
text: string
): Promise<{ nonce: string; cipher: string } | undefined> {
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { bookmarkletSaveToUrl } from '$lib/externalSettings';
import { bookmarkletSaveToUrl } from '$lib/externalSettings/index';
import { letterCase, titleCases } from '$lib/letterCasing';
import { setAmoledTheme } from '$lib/theme';
import { Clipboard } from '@capacitor/clipboard';
@@ -13,7 +13,6 @@
import { get } from 'svelte/store';
import {
isMobile,
clearCaches,
isUnrestrictedPlatform,
setInvidiousInstance,
goToInvidiousLogin
@@ -82,23 +81,15 @@
}
}
function reloadState() {
clearCaches();
ui('#dialog-settings');
goto(resolve('/', {}), { replaceState: true });
location.reload();
}
async function setInstance(event: Event) {
event.preventDefault();
invalidInstance = !(await setInvidiousInstance(invidiousInstance));
reloadState();
goto(resolve('/', {}), { replaceState: true });
}
async function setBackend(event: Event) {
const select = event.target as HTMLSelectElement;
backendInUseStore.set(select.value as 'ivg' | 'yt');
reloadState();
}
function allowInsecureRequests() {
@@ -165,7 +156,7 @@
</button>
</nav>
</form>
{#if isOwnBackend()?.internalAuth}
{#if isOwnBackend()?.internalAuth && $invidiousInstanceStore}
{#if !$invidiousAuthStore}
<button onclick={goToInvidiousLogin}>
<i>link</i>
@@ -0,0 +1,128 @@
import { page } from '$app/state';
import { get } from 'svelte/store';
import { z } from 'zod';
import { env } from '$env/dynamic/public';
import { persistedStores } from './settings';
import { isOwnBackend } from '$lib/shared';
import { addOrUpdateKeyValue, getKeyValue } from '$lib/api/backend';
import { rawMasterKeyStore } from '$lib/store';
export async function syncSettingsToBackend() {
if (!isOwnBackend() || !get(rawMasterKeyStore)) return;
await Promise.all(
persistedStores.map(async (store) => {
getKeyValue(store.name).then((currentKeyValue) => {
if (currentKeyValue !== null) {
const currentKeyValueParsed = parseWithSchema(store.schema, currentKeyValue);
if (currentKeyValueParsed !== null && currentKeyValueParsed !== undefined) {
store.store.set(currentKeyValueParsed);
}
}
});
let initalLoad = true;
store.store.subscribe((value) => {
if (initalLoad) {
initalLoad = false;
return;
}
if (value === undefined) return;
return addOrUpdateKeyValue(
store.name,
store.serialize ? store.serialize(value) : value?.toString()
);
});
})
);
}
export function parseWithSchema<T>(schema: z.ZodType<T>, raw: unknown): T | undefined {
try {
if (typeof raw === 'string') {
// Try JSON first (records / objects)
try {
return schema.parse(JSON.parse(raw));
} catch {
return schema.parse(raw);
}
}
return schema.parse(raw);
} catch {
return undefined;
}
}
function setStores(toSet: Record<string, unknown>, overwriteExisting = false) {
if (!overwriteExisting) return;
for (const { name, store, schema } of persistedStores) {
const raw = toSet[name];
if (raw === undefined) continue;
const parsed = parseWithSchema(schema, raw);
if (parsed !== undefined) {
store.set(parsed);
}
}
}
export function loadSettingsFromEnv() {
if (
typeof import.meta.env.VITE_DEFAULT_SETTINGS !== 'string' &&
typeof env.PUBLIC_DEFAULT_SETTINGS !== 'string'
)
return;
let raw = import.meta.env.VITE_DEFAULT_SETTINGS || env.PUBLIC_DEFAULT_SETTINGS;
// Docker wraps env vars in quotes
if (raw.startsWith('"')) raw = raw.slice(1);
if (raw.endsWith('"')) raw = raw.slice(0, -1);
let isInitialLoad = false;
try {
if (localStorage.getItem('initialLoadState') === null) {
isInitialLoad = true;
localStorage.setItem('initialLoadState', '0');
}
} catch {
isInitialLoad = true;
}
try {
const parsed = JSON.parse(raw);
setStores(parsed, isInitialLoad);
} catch (err) {
console.error(err);
}
}
export function bookmarkletSaveToUrl(): string {
const url = new URL(location.origin);
for (const { name, store, serialize } of persistedStores) {
const value = get(store);
const encoded = serialize ? serialize(value) : value?.toString();
if (encoded !== undefined) {
url.searchParams.set(name, encoded);
}
}
return url.toString();
}
export function bookmarkletLoadFromUrl() {
const toSet: Record<string, string> = {};
page.url.searchParams.forEach((value, key) => {
toSet[key] = value;
});
setStores(toSet, true);
}
@@ -1,8 +1,5 @@
import { page } from '$app/state';
import { get, type Writable } from 'svelte/store';
import { z } from 'zod';
import { env } from '$env/dynamic/public';
import { type Writable } from 'svelte/store';
import {
darkModeStore,
@@ -40,8 +37,24 @@ import {
playerDefaultPlaybackSpeed,
playerCCByDefault,
playerMiniplayerEnabled,
sponsorBlockCategoriesStore
sponsorBlockCategoriesStore,
invidiousAuthStore,
backendInUseStore,
invidiousInstanceStore,
engineFallbacksStore,
engineMaxConcurrentChannelsStore,
engineCooldownYTStore,
engineCullYTStore,
interfaceAllowInsecureRequests,
interfaceDisableAutoUpdate,
interfaceAndroidUseNativeShare,
playerAndroidPauseOnNetworkChange,
playerAndroidLockOrientation,
playerYouTubeJsFallback,
playerYouTubeJsAlways,
interfaceSearchHistoryEnabled
} from '$lib/store';
import { isOwnBackend } from '$lib/shared';
type PersistedStore<T> = {
name: string;
@@ -50,29 +63,18 @@ type PersistedStore<T> = {
serialize?: (value: T) => string;
};
function parseWithSchema<T>(schema: z.ZodType<T>, raw: unknown): T | undefined {
try {
if (typeof raw === 'string') {
// Try JSON first (records / objects)
try {
return schema.parse(JSON.parse(raw));
} catch {
return schema.parse(raw);
}
}
return schema.parse(raw);
} catch {
return undefined;
}
}
const zBoolean = z.coerce.boolean();
const zNumber = z.coerce.number();
const zString = z.string();
const zArray = z.array(z.string());
const zChapterMode = z.enum(['automatic', 'manual', 'timeline']);
const zChapterModeRecord = z.record(z.string(), zChapterMode.optional());
const zAuth = z.object({
username: z.string(),
token: z.string()
});
const persistedStores: PersistedStore<any>[] = [
export const persistedStores: PersistedStore<any>[] = [
{
name: 'returnYTDislikesInstance',
store: returnYTDislikesInstanceStore,
@@ -128,6 +130,11 @@ const persistedStores: PersistedStore<any>[] = [
store: interfaceSearchSuggestionsStore,
schema: zBoolean
},
{
name: 'searchHistoryEnabled',
store: interfaceSearchHistoryEnabled,
schema: zBoolean
},
{
name: 'sponsorBlock',
store: sponsorBlockStore,
@@ -256,72 +263,79 @@ const persistedStores: PersistedStore<any>[] = [
}
];
function setStores(toSet: Record<string, unknown>, overwriteExisting = false) {
if (!overwriteExisting) return;
for (const { name, store, schema } of persistedStores) {
const raw = toSet[name];
if (raw === undefined) continue;
const parsed = parseWithSchema(schema, raw);
if (parsed !== undefined) {
store.set(parsed);
}
}
}
export function loadSettingsFromEnv() {
if (
typeof import.meta.env.VITE_DEFAULT_SETTINGS !== 'string' &&
typeof env.PUBLIC_DEFAULT_SETTINGS !== 'string'
)
return;
let raw = import.meta.env.VITE_DEFAULT_SETTINGS || env.PUBLIC_DEFAULT_SETTINGS;
// Docker wraps env vars in quotes
if (raw.startsWith('"')) raw = raw.slice(1);
if (raw.endsWith('"')) raw = raw.slice(0, -1);
let isInitialLoad = false;
try {
if (localStorage.getItem('initialLoadState') === null) {
isInitialLoad = true;
localStorage.setItem('initialLoadState', '0');
}
} catch {
isInitialLoad = true;
}
try {
const parsed = JSON.parse(raw);
setStores(parsed, isInitialLoad);
} catch (err) {
console.error(err);
}
}
export function bookmarkletSaveToUrl(): string {
const url = new URL(location.origin);
for (const { name, store, serialize } of persistedStores) {
const value = get(store);
const encoded = serialize ? serialize(value) : value?.toString();
if (encoded !== undefined) {
url.searchParams.set(name, encoded);
}
}
return url.toString();
}
export function bookmarkletLoadFromUrl() {
const toSet: Record<string, string> = {};
page.url.searchParams.forEach((value, key) => {
toSet[key] = value;
// If using own backend with can support more externalSettings.
if (isOwnBackend()) {
persistedStores.push({
name: 'authToken',
store: invidiousAuthStore,
schema: zAuth,
serialize: JSON.stringify
});
persistedStores.push({
name: 'backendInUse',
store: backendInUseStore,
schema: zString
});
persistedStores.push({
name: 'invidiousInstance',
store: invidiousInstanceStore,
schema: zString
});
persistedStores.push({
name: 'engineFallbacks',
store: engineFallbacksStore,
schema: zArray
});
persistedStores.push({
name: 'engineMaxConcurrentChannels',
store: engineMaxConcurrentChannelsStore,
schema: zNumber
});
persistedStores.push({
name: 'engineCooldownYT',
store: engineCooldownYTStore,
schema: zNumber
});
persistedStores.push({
name: 'engineCullYT',
store: engineCullYTStore,
schema: zNumber
});
persistedStores.push({
name: 'allowInsecureRequests',
store: interfaceAllowInsecureRequests,
schema: zString
});
persistedStores.push({
name: 'disableAutoUpdate',
store: interfaceDisableAutoUpdate,
schema: zString
});
persistedStores.push({
name: 'androidUseNativeShare',
store: interfaceAndroidUseNativeShare,
schema: zString
});
persistedStores.push({
name: 'pauseOnNetworkChange',
store: playerAndroidPauseOnNetworkChange,
schema: zBoolean
});
persistedStores.push({
name: 'androidLockOrientation',
store: playerAndroidLockOrientation,
schema: zBoolean
});
persistedStores.push({
name: 'youTubeJsFallback',
store: playerYouTubeJsFallback,
schema: zBoolean
});
persistedStores.push({
name: 'youTubeJsAlways',
store: playerYouTubeJsAlways,
schema: zBoolean
});
setStores(toSet, true);
}
export const persistedStoreKeys = persistedStores.map((store) => store.name);
+39 -2
View File
@@ -4,6 +4,7 @@ import { env } from '$env/dynamic/private';
let UserTable: ModelCtor<Model<any, any>>;
let ChannelSubscriptionTable: ModelCtor<Model<any, any>>;
let CaptchaTable: ModelCtor<Model<any, any>>;
let UserKeyValueTable: ModelCtor<Model<any, any>>;
let sequelizeInstance: Sequelize | null = null;
@@ -13,13 +14,15 @@ export function getSequelize(): {
UserTable: ModelCtor<Model<any, any>>;
ChannelSubscriptionTable: ModelCtor<Model<any, any>>;
CaptchaTable: ModelCtor<Model<any, any>>;
UserKeyValueTable: ModelCtor<Model<any, any>>;
} {
if (sequelizeInstance) {
return {
sequelize: sequelizeInstance,
UserTable,
ChannelSubscriptionTable,
CaptchaTable
CaptchaTable,
UserKeyValueTable
};
}
@@ -78,6 +81,32 @@ export function getSequelize(): {
}
);
UserKeyValueTable = sequelizeInstance.define(
'UserKeyValue',
{
key: {
type: DataTypes.STRING,
allowNull: false
},
valueCipher: {
type: DataTypes.STRING,
allowNull: false
},
valueNonce: {
type: DataTypes.STRING,
allowNull: false
}
},
{
indexes: [
{
unique: true,
fields: ['UserId', 'key']
}
]
}
);
ChannelSubscriptionTable = sequelizeInstance.define('Subscriptions', {
id: {
type: DataTypes.STRING,
@@ -119,12 +148,14 @@ export function getSequelize(): {
});
UserTable.hasMany(ChannelSubscriptionTable);
UserTable.hasMany(UserKeyValueTable);
return {
sequelize: sequelizeInstance,
UserTable,
ChannelSubscriptionTable,
CaptchaTable
CaptchaTable,
UserKeyValueTable
};
}
@@ -148,3 +179,9 @@ export interface UserTableModel extends Model {
masterKeyCipher: string;
masterKeyNonce: string;
}
export interface UserKeyStoreModel {
key: string;
valueCipher: string;
valueNonce: string;
}
+59 -1
View File
@@ -1,4 +1,9 @@
import { getSequelize, type ChannelSubscriptionModel, type UserTableModel } from './database';
import {
getSequelize,
type ChannelSubscriptionModel,
type UserKeyStoreModel,
type UserTableModel
} from './database';
import { Op } from 'sequelize';
import crypto from 'crypto';
import { error } from '@sveltejs/kit';
@@ -81,6 +86,59 @@ export class User {
return subscriptions as unknown as ChannelSubscriptionModel[];
}
async getKeyValue(key: string): Promise<UserKeyStoreModel> {
const keyStore = await getSequelize().UserKeyValueTable.findOne({
where: {
UserId: this.id,
key
}
});
if (!keyStore) {
throw error(404);
}
const keyStoreModel = keyStore as unknown as UserKeyStoreModel;
return {
key: keyStoreModel.key,
valueCipher: keyStoreModel.valueCipher,
valueNonce: keyStoreModel.valueNonce
};
}
async addOrUpdateKeyValue(key: string, valueCipher: string, valueNonce: string) {
const keyStore = await getSequelize().UserKeyValueTable.findOne({
where: {
UserId: this.id,
key
}
});
if (keyStore) {
await keyStore.update({
valueCipher,
valueNonce
});
} else {
await getSequelize().UserKeyValueTable.create({
key,
valueCipher,
valueNonce,
UserId: this.id
});
}
}
async deleteKeyValue(key: string) {
await getSequelize().UserKeyValueTable.destroy({
where: {
UserId: this.id,
key
}
});
}
}
export type CreateUser = {
@@ -3,7 +3,6 @@
import { goto } from '$app/navigation';
import { navigating, page } from '$app/stores';
import colorTheme, { convertToHexColorCode } from '$lib/android/plugins/colorTheme';
import { getFeed, notificationsMarkAsRead } from '$lib/api/index';
import type { Notification } from '$lib/api/model';
import Logo from '$lib/components/Logo.svelte';
@@ -14,13 +13,10 @@
import Thumbnail from '$lib/components/Thumbnail.svelte';
import Player from '$lib/components/Player.svelte';
import '$lib/css/global.css';
import { bookmarkletLoadFromUrl, loadSettingsFromEnv } from '$lib/externalSettings';
import { getPages } from '$lib/navPages';
import {
invidiousAuthStore,
darkModeStore,
invidiousInstanceStore,
interfaceAmoledTheme,
interfaceDefaultPage,
isAndroidTvStore,
playerState,
@@ -29,17 +25,12 @@
syncPartyPeerStore,
themeColorStore
} from '$lib/store';
import { setAmoledTheme, setStatusBarColor, setTheme } from '$lib/theme';
import { App } from '@capacitor/app';
import { Browser } from '@capacitor/browser';
import { Capacitor } from '@capacitor/core';
import 'beercss';
import ui from 'beercss';
import 'material-dynamic-colors';
import { onMount } from 'svelte';
import { _ } from '$lib/i18n';
import { get } from 'svelte/store';
import { pwaInfo } from 'virtual:pwa-info';
import { goToInvidiousLogin, isYTBackend, logout, truncate } from '$lib/misc';
import Author from '$lib/components/Author.svelte';
import Toast from '$lib/components/Toast.svelte';
@@ -50,17 +41,13 @@
const showLogin = !isYTBackend() || isOwnBackend()?.internalAuth;
let mobileSearchShow = $state(false);
let notifications: Notification[] = $state([]);
let playerIsPip: boolean = $state(false);
let pages = $state(getPages());
invidiousAuthStore.subscribe(() => {
pages = getPages();
});
rawMasterKeyStore.subscribe(() => {
pages = getPages();
});
@@ -74,52 +61,6 @@
requestAnimationFrame(() => resetScroll());
});
interfaceAmoledTheme.subscribe(async () => {
setAmoledTheme();
await setStatusBarColor();
});
darkModeStore.subscribe(async () => {
setTheme();
setAmoledTheme();
await setStatusBarColor();
});
App.addListener('appUrlOpen', (data) => {
const url = new URL(data.url);
// Handle youtube deeplinks
if (url.protocol !== 'materialious-auth:') {
let videoId = url.searchParams.get('v');
if (!videoId) {
videoId = url.pathname.split('/')[1];
}
if (videoId === 'shorts') {
videoId = url.pathname.split('/')[2];
}
if (!videoId) {
return;
}
goto(resolve(`/watch/[videoId]`, { videoId: videoId }));
} else {
// Auth response handling for Mobile
const username = url.searchParams.get('username');
const token = url.searchParams.get('token');
if (username && token) {
invidiousAuthStore.set({
username: username,
token: token
});
}
}
});
async function login() {
if (isOwnBackend()?.internalAuth) {
goto(resolve('/internal/login', {}));
@@ -166,7 +107,6 @@
);
if (sid) {
console.log(sid);
invidiousAuthStore.set({ username: rawUsername, token: sid });
await ui('#tv-login');
goto(resolve('/', {}), { replaceState: true });
@@ -196,41 +136,6 @@
}
onMount(async () => {
ui();
let themeHex = get(themeColorStore);
if (Capacitor.getPlatform() === 'android' && !themeHex) {
try {
const colorPalette = await colorTheme.getColorPalette();
themeHex = convertToHexColorCode(colorPalette.primary);
await ui('theme', themeHex);
} catch {
// Continue regardless of error
}
}
if (Capacitor.getPlatform() === 'android') {
document.addEventListener('click', async (event: MouseEvent) => {
// Handles opening links in browser for android.
const link = (event.target as HTMLElement).closest('a');
if (link && link.href && link.href.startsWith('http') && link.target === '_blank') {
event.preventDefault();
await Browser.open({ url: link.href });
}
});
}
loadSettingsFromEnv();
// Should always be loaded after env settings
// So user preferences overwrite instance preferences.
bookmarkletLoadFromUrl();
await setStatusBarColor();
setTheme();
setAmoledTheme();
if ($invidiousAuthStore && !isYTBackend()) {
loadNotifications().catch(() => logout());
}
@@ -245,15 +150,8 @@
resetScroll();
});
let webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
</script>
<svelte:head>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html webManifestLink}
</svelte:head>
<div>
<nav
class="left m l surface-container"
@@ -15,8 +15,13 @@
let invidiousInstanceValid: boolean = $state(true);
let invidiousInstance: string = $state(defaultInstance ?? '');
async function setupCompleted(event?: Event) {
event?.preventDefault();
async function setupCompleted() {
clearCaches();
goto(resolve('/', {}), { replaceState: true });
}
async function setInvidious(event: Event) {
event.preventDefault();
invidiousInstanceValid = await setInvidiousInstance(invidiousInstance);
@@ -24,8 +29,7 @@
return;
}
clearCaches();
goto(resolve('/', {}), { replaceState: true });
setupCompleted();
}
function setYTBackend() {
@@ -107,7 +111,7 @@
<div class="space"></div>
<h3>{$_('initalSetup.configureInstance')}</h3>
<form onsubmit={setupCompleted}>
<form onsubmit={setInvidious}>
<nav class="center-align">
<div
class="field label prefix surface-container-highest"
+117 -1
View File
@@ -1,10 +1,45 @@
<script lang="ts">
import { isAndroidTvStore } from '$lib/store';
import {
darkModeStore,
interfaceAmoledTheme,
invidiousAuthStore,
isAndroidTvStore,
rawMasterKeyStore,
themeColorStore
} from '$lib/store';
import ui from 'beercss';
import { App } from '@capacitor/app';
import '$lib/fetchProxy';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { setAmoledTheme, setStatusBarColor, setTheme } from '$lib/theme';
import { pwaInfo } from 'virtual:pwa-info';
import { onMount } from 'svelte';
import { Capacitor } from '@capacitor/core';
import colorTheme, { convertToHexColorCode } from '$lib/android/plugins/colorTheme';
import { Browser } from '@capacitor/browser';
import {
bookmarkletLoadFromUrl,
loadSettingsFromEnv,
syncSettingsToBackend
} from '$lib/externalSettings';
let { children } = $props();
interfaceAmoledTheme.subscribe(async () => {
setAmoledTheme();
await setStatusBarColor();
});
darkModeStore.subscribe(async () => {
setTheme();
setAmoledTheme();
await setStatusBarColor();
});
App.addListener('backButton', async (data) => {
if (data.canGoBack) {
window.history.back();
@@ -12,9 +47,90 @@
await App.exitApp();
}
});
App.addListener('appUrlOpen', (data) => {
const url = new URL(data.url);
// Handle youtube deeplinks
if (url.protocol !== 'materialious-auth:') {
let videoId = url.searchParams.get('v');
if (!videoId) {
videoId = url.pathname.split('/')[1];
}
if (videoId === 'shorts') {
videoId = url.pathname.split('/')[2];
}
if (!videoId) {
return;
}
goto(resolve(`/watch/[videoId]`, { videoId: videoId }));
} else {
// Auth response handling for Mobile
const username = url.searchParams.get('username');
const token = url.searchParams.get('token');
if (username && token) {
invidiousAuthStore.set({
username: username,
token: token
});
}
}
});
onMount(async () => {
ui();
if (Capacitor.getPlatform() === 'android' && $themeColorStore) {
try {
const colorPalette = await colorTheme.getColorPalette();
themeColorStore.set(convertToHexColorCode(colorPalette.primary));
await ui('theme', $themeColorStore);
} catch {
// Continue regardless of error
}
}
if (Capacitor.getPlatform() === 'android') {
document.addEventListener('click', async (event: MouseEvent) => {
// Handles opening links in browser for android.
const link = (event.target as HTMLElement).closest('a');
if (link && link.href && link.href.startsWith('http') && link.target === '_blank') {
event.preventDefault();
await Browser.open({ url: link.href });
}
});
}
loadSettingsFromEnv();
// Should always be loaded after env settings
// So user preferences overwrite instance preferences.
bookmarkletLoadFromUrl();
await setStatusBarColor();
setTheme();
setAmoledTheme();
});
let syncToSettingsInitialized = false;
rawMasterKeyStore.subscribe((value) => {
if (value === undefined || syncToSettingsInitialized) return;
syncToSettingsInitialized = true;
syncSettingsToBackend();
});
let webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
</script>
<svelte:head>
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html webManifestLink}
{#if $isAndroidTvStore}
<style>
:focus {
@@ -0,0 +1,45 @@
import { persistedStoreKeys } from '$lib/externalSettings/settings.js';
import { getUser } from '$lib/server/user.js';
import { error, json } from '@sveltejs/kit';
import z from 'zod';
async function keyAllowed(key: string) {
if (!persistedStoreKeys.includes(key)) throw error(400);
}
export async function GET({ locals, params }) {
keyAllowed(params.key);
const user = await getUser(locals.userId);
return json(await user.getKeyValue(params.key));
}
export async function DELETE({ locals, params }) {
keyAllowed(params.key);
const user = await getUser(locals.userId);
await user.deleteKeyValue(params.key);
return new Response();
}
const zUpdateKeyStore = z.object({
valueCipher: z.string().max(1000),
valueNonce: z.string().max(255)
});
export async function POST({ locals, params, request }) {
keyAllowed(params.key);
const user = await getUser(locals.userId);
const keyValue = zUpdateKeyStore.safeParse(await request.json());
if (!keyValue.success) throw error(400);
await user.addOrUpdateKeyValue(params.key, keyValue.data.valueCipher, keyValue.data.valueNonce);
return new Response();
}
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime
LATEST_VERSION = "1.15.2"
LATEST_VERSION = "1.15.3"
RELEASE_DATE = datetime.now().strftime("%Y-%-m-%d") # Format: YYYY-M-D
WORKING_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "materialious")