mirror of
https://github.com/trafficlunar/jellyfin-spicetify.git
synced 2026-06-13 19:07:06 +00:00
feat: settings modal + player hijack concept
This commit is contained in:
parent
8c98c2624b
commit
138c7e815c
6 changed files with 556 additions and 35 deletions
|
|
@ -12,5 +12,8 @@
|
|||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"spicetify-creator": "^1.0.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jellyfin/sdk": "^0.13.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
187
pnpm-lock.yaml
generated
187
pnpm-lock.yaml
generated
|
|
@ -7,6 +7,10 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@jellyfin/sdk':
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.0(axios@1.13.6)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
|
|
@ -33,6 +37,11 @@ packages:
|
|||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@jellyfin/sdk@0.13.0':
|
||||
resolution: {integrity: sha512-oiBAOXH6s+dKdReSsYgNktBDzbxtg4JVWhEzIxZSxKcWMdSKmBtK41MhXRO7IWAC40DguKUm3nU/Z493qPAlWA==}
|
||||
peerDependencies:
|
||||
axios: ^1.12.0
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
|
@ -149,6 +158,9 @@ packages:
|
|||
resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
at-least-node@1.0.0:
|
||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
|
@ -160,6 +172,9 @@ packages:
|
|||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.13.6:
|
||||
resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
|
||||
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
|
|
@ -179,6 +194,10 @@ packages:
|
|||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
caniuse-lite@1.0.30001776:
|
||||
resolution: {integrity: sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==}
|
||||
|
||||
|
|
@ -201,6 +220,10 @@ packages:
|
|||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
|
|
@ -232,10 +255,18 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
|
|
@ -252,6 +283,22 @@ packages:
|
|||
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
||||
hasBin: true
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
esbuild-android-64@0.14.54:
|
||||
resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -412,10 +459,23 @@ packages:
|
|||
resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
follow-redirects@1.15.11:
|
||||
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreground-child@3.3.1:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.5:
|
||||
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@5.3.4:
|
||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||
|
||||
|
|
@ -436,6 +496,14 @@ packages:
|
|||
generic-names@4.0.0:
|
||||
resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
|
@ -453,6 +521,10 @@ packages:
|
|||
resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
|
|
@ -460,6 +532,14 @@ packages:
|
|||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -565,6 +645,18 @@ packages:
|
|||
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime@1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -687,6 +779,9 @@ packages:
|
|||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
prr@1.0.1:
|
||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||
|
||||
|
|
@ -856,6 +951,10 @@ snapshots:
|
|||
wrap-ansi: 8.1.0
|
||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||
|
||||
'@jellyfin/sdk@0.13.0(axios@1.13.6)':
|
||||
dependencies:
|
||||
axios: 1.13.6
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.6':
|
||||
optional: true
|
||||
|
||||
|
|
@ -938,6 +1037,8 @@ snapshots:
|
|||
|
||||
ansi-styles@6.2.3: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
at-least-node@1.0.0: {}
|
||||
|
||||
autoprefixer@10.4.27(postcss@8.5.8):
|
||||
|
|
@ -949,6 +1050,14 @@ snapshots:
|
|||
postcss: 8.5.8
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
axios@1.13.6:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.5
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
baseline-browser-mapping@2.10.0: {}
|
||||
|
|
@ -970,6 +1079,11 @@ snapshots:
|
|||
node-releases: 2.0.27
|
||||
update-browserslist-db: 1.2.3(browserslist@4.28.1)
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
caniuse-lite@1.0.30001776: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
|
|
@ -991,6 +1105,10 @@ snapshots:
|
|||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
copy-anything@2.0.6:
|
||||
|
|
@ -1016,9 +1134,17 @@ snapshots:
|
|||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
electron-to-chromium@1.5.307: {}
|
||||
|
|
@ -1032,6 +1158,21 @@ snapshots:
|
|||
prr: 1.0.1
|
||||
optional: true
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
esbuild-android-64@0.14.54:
|
||||
optional: true
|
||||
|
||||
|
|
@ -1153,11 +1294,21 @@ snapshots:
|
|||
dependencies:
|
||||
find-file-up: 0.1.3
|
||||
|
||||
follow-redirects@1.15.11: {}
|
||||
|
||||
foreground-child@3.3.1:
|
||||
dependencies:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.5:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
hasown: 2.0.2
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
|
||||
fs-exists-sync@0.1.0: {}
|
||||
|
|
@ -1177,6 +1328,24 @@ snapshots:
|
|||
dependencies:
|
||||
loader-utils: 3.3.1
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
glob@10.5.0:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
|
|
@ -1207,10 +1376,18 @@ snapshots:
|
|||
is-windows: 0.2.0
|
||||
which: 1.3.1
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
|
@ -1314,6 +1491,14 @@ snapshots:
|
|||
semver: 5.7.2
|
||||
optional: true
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mime@1.6.0:
|
||||
optional: true
|
||||
|
||||
|
|
@ -1421,6 +1606,8 @@ snapshots:
|
|||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
prr@1.0.1:
|
||||
optional: true
|
||||
|
||||
|
|
|
|||
76
src/app.tsx
76
src/app.tsx
|
|
@ -1,8 +1,82 @@
|
|||
// TODO: hijack search result, use that as song URI
|
||||
|
||||
import React from "react";
|
||||
import { Api, Jellyfin } from "@jellyfin/sdk";
|
||||
import SettingsModal from "./settings";
|
||||
|
||||
const audio = new Audio("https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3");
|
||||
let hijackActive = false;
|
||||
|
||||
export const jellyfin = new Jellyfin({
|
||||
clientInfo: {
|
||||
name: "Spicetify",
|
||||
version: "1.0.0",
|
||||
},
|
||||
deviceInfo: {
|
||||
name: "Spotify",
|
||||
id: "spotify", // TODO: should be unique?
|
||||
},
|
||||
});
|
||||
|
||||
export let jellyfinApi: Api | undefined;
|
||||
export const setJellyfinApi = (api: Api) => {
|
||||
jellyfinApi = api;
|
||||
};
|
||||
|
||||
async function main() {
|
||||
while (!Spicetify?.showNotification) {
|
||||
while (!Spicetify.showNotification || !Spicetify.Platform.History) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
new Spicetify.Topbar.Button("Jellyfin", "podcasts", () => {
|
||||
Spicetify.PopupModal.display({
|
||||
title: "Jellyfin",
|
||||
content: React.createElement(SettingsModal) as unknown as Element,
|
||||
isLarge: false,
|
||||
});
|
||||
});
|
||||
|
||||
Spicetify.Platform.History.listen((location) => {
|
||||
if (location.pathname.startsWith("/search/")) {
|
||||
const segments = location.pathname.split("/");
|
||||
const query = segments[2];
|
||||
}
|
||||
});
|
||||
|
||||
Spicetify.Player.addEventListener("songchange", async (event) => {
|
||||
// if (event?.data.item.uri === "spotify:track:72wehM3q2RVZb4XLmAkyTr") {
|
||||
const oldVolume = Spicetify.Player.getVolume();
|
||||
await audio.play();
|
||||
|
||||
Spicetify.Player.setVolume(0);
|
||||
hijackActive = true;
|
||||
Spicetify.Player.setVolume(oldVolume);
|
||||
});
|
||||
|
||||
Spicetify.Player.addEventListener("onplaypause", async (event) => {
|
||||
if (!hijackActive) return;
|
||||
|
||||
if (event?.data.isPaused) {
|
||||
audio.pause();
|
||||
} else {
|
||||
await audio.play();
|
||||
}
|
||||
});
|
||||
|
||||
const playback = Spicetify.Platform.PlaybackAPI;
|
||||
|
||||
// Change volume of Jellyfin audio instead of Spotify audio
|
||||
playback.setVolume = new Proxy(playback.setVolume, {
|
||||
apply(target, thisArg, args) {
|
||||
if (hijackActive) {
|
||||
audio.volume = args[0];
|
||||
return;
|
||||
} else {
|
||||
return Reflect.apply(target, thisArg, args);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Show message on start.
|
||||
Spicetify.showNotification("Hello!");
|
||||
}
|
||||
|
|
|
|||
173
src/settings.tsx
Normal file
173
src/settings.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import React, { useState } from "react";
|
||||
import { getUserApi } from "@jellyfin/sdk/lib/utils/api/user-api";
|
||||
|
||||
import styles from "./styles.module.css";
|
||||
import { jellyfin, setJellyfinApi } from "./app";
|
||||
|
||||
export default function SettingsModal() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [url, setUrl] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [isUsingQuickConnect, setIsUsingQuickConnect] = useState(false);
|
||||
const [quickConnectCode, setQuickConnectCode] = useState("");
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const login = async () => {
|
||||
const servers = await jellyfin.discovery.getRecommendedServerCandidates(url);
|
||||
const best = jellyfin.discovery.findBestServer(servers);
|
||||
if (!best) {
|
||||
Spicetify.showNotification("Failed to connect to server!", true);
|
||||
return;
|
||||
}
|
||||
const api = jellyfin.createApi(best.address);
|
||||
const userApi = getUserApi(api);
|
||||
|
||||
const auth =
|
||||
isUsingQuickConnect && quickConnectCode.toString().length === 6
|
||||
? await userApi.authenticateWithQuickConnect({
|
||||
quickConnectDto: { Secret: "111000" },
|
||||
})
|
||||
: await userApi.authenticateUserByName({
|
||||
authenticateUserByName: { Username: username, Pw: password },
|
||||
});
|
||||
|
||||
if (!auth.data.AccessToken) {
|
||||
Spicetify.showNotification("Failed to login!", true);
|
||||
return;
|
||||
}
|
||||
|
||||
setJellyfinApi(api);
|
||||
setIsLoggedIn(true);
|
||||
};
|
||||
|
||||
if (isLoggedIn)
|
||||
return (
|
||||
<div className={styles.modal}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 512 512">
|
||||
<path fill="#ffb636" d="M378.553 355.648L45.117 500.733c-21.735 8.65-43.335-12.764-34.874-34.572l145.709-338.684" />
|
||||
<path
|
||||
fill="#ffd469"
|
||||
d="m10.243 466.161l11.58-26.916l2.977-4.543c57.597-87.744 116.038-174.952 176.475-260.768l67.765 69.46C217.91 278.496 51.89 450.063 17.115 495.571c-7.57-6.963-11.249-18.128-6.872-29.41"
|
||||
/>
|
||||
<path
|
||||
fill="#a06c33"
|
||||
d="M304.382 204.434c61.854 61.854 95.685 128.308 75.564 148.43c-20.121 20.121-86.575-13.71-148.43-75.564s-95.685-128.308-75.564-148.43s86.575 13.709 148.43 75.564"
|
||||
/>
|
||||
<path
|
||||
fill="#f7f9aa"
|
||||
d="M155.601 327.572c0 6.012-4.874 10.885-10.885 10.885s-10.885-4.873-10.885-10.885s4.873-10.885 10.885-10.885s10.885 4.873 10.885 10.885"
|
||||
/>
|
||||
<path
|
||||
fill="#ffb636"
|
||||
d="M501.986 213.16c0 8.628-6.994 15.622-15.622 15.622s-15.622-6.994-15.622-15.622s6.994-15.622 15.622-15.622s15.622 6.994 15.622 15.622M397.663 421.182c-8.628 0-15.622 6.994-15.622 15.622s6.994 15.622 15.622 15.622s15.622-6.994 15.622-15.622s-6.995-15.622-15.622-15.622"
|
||||
/>
|
||||
<path
|
||||
fill="#bea4ff"
|
||||
d="M355.949 79.523c-1.34 9.065-7.197 17.072-16.07 21.968c-6.126 3.38-13.33 5.137-20.807 5.137a49 49 0 0 1-7.117-.526c-5.288-.782-10.581.016-14.52 2.189c-1.766.974-4.8 3.105-5.293 6.438c-.492 3.333 1.796 6.251 3.203 7.694c3.058 3.135 7.725 5.381 12.849 6.22c.141.015.281.02.422.041c21.619 3.196 37.061 20.32 34.421 38.173c-1.34 9.066-7.197 17.073-16.071 21.969c-6.126 3.38-13.329 5.137-20.806 5.137a49 49 0 0 1-7.117-.526c-5.287-.783-10.582.015-14.521 2.189c-1.766.974-4.8 3.105-5.293 6.438c-.79 5.349 5.778 12.411 16.47 13.991c5.817.86 9.836 6.273 8.976 12.091c-.782 5.29-5.328 9.092-10.52 9.092q-.779 0-1.571-.116c-21.619-3.196-37.06-20.321-34.421-38.173c1.34-9.066 7.197-17.073 16.071-21.969c8.055-4.444 17.972-6.082 27.924-4.611c5.288.781 10.58-.016 14.52-2.189c1.766-.974 4.8-3.105 5.293-6.438c.777-5.262-5.577-12.171-15.963-13.898c-.17-.017-.341-.031-.512-.056c-9.951-1.472-18.971-5.908-25.395-12.493c-7.077-7.254-10.367-16.614-9.026-25.681c1.34-9.065 7.197-17.072 16.07-21.968c8.055-4.444 17.972-6.082 27.924-4.611c5.286.78 10.581-.016 14.52-2.189c1.766-.974 4.8-3.105 5.293-6.438c.492-3.333-1.796-6.251-3.203-7.694c-3.142-3.22-7.977-5.516-13.267-6.297c-5.817-.86-9.836-6.273-8.976-12.091s6.274-9.832 12.091-8.977c9.951 1.472 18.971 5.908 25.395 12.493c7.078 7.255 10.368 16.615 9.027 25.681"
|
||||
/>
|
||||
<path
|
||||
fill="#ff6e83"
|
||||
d="M81.731 159.689c0 9.777-7.926 17.703-17.703 17.703s-17.703-7.926-17.703-17.703s7.926-17.703 17.703-17.703s17.703 7.925 17.703 17.703m316.445-20.453c-11.296 0-20.452 9.157-20.452 20.452s9.157 20.452 20.452 20.452s20.452-9.157 20.452-20.452s-9.156-20.452-20.452-20.452M215.529 395.899c-11.296 0-20.452 9.157-20.452 20.452s9.157 20.452 20.452 20.452s20.452-9.157 20.452-20.452s-9.156-20.452-20.452-20.452m271.303-93.646c3.093-5.989.745-13.352-5.244-16.445c-2.388-1.232-5.238-2.868-8.538-4.761c-28.993-16.633-89.319-51.242-160.352 6.109c-5.245 4.234-6.063 11.919-1.829 17.163c4.233 5.245 11.917 6.065 17.163 1.829c58.035-46.856 104.882-19.985 132.871-3.928c3.403 1.952 6.617 3.796 9.483 5.276a12.205 12.205 0 0 0 16.446-5.243"
|
||||
/>
|
||||
<path
|
||||
fill="#59cafc"
|
||||
d="M434.834 62.776c0 6.012-4.874 10.885-10.885 10.885s-10.885-4.873-10.885-10.885s4.873-10.885 10.885-10.885c6.012-.001 10.885 4.873 10.885 10.885M46.324 11.894c-6.012 0-10.885 4.873-10.885 10.885s4.873 10.885 10.885 10.885S57.21 28.791 57.21 22.779s-4.874-10.885-10.886-10.885m170.681 142.057c1.231-2.414 2.749-5.163 4.356-8.073c8.154-14.771 19.32-34.999 19.992-58.559c.807-28.304-13.934-54.002-43.812-76.38c-5.187-3.885-12.539-2.828-16.421 2.357c-3.884 5.186-2.829 12.538 2.357 16.421c23.75 17.788 35.01 36.411 34.425 56.933c-.51 17.872-9.697 34.516-17.08 47.889c-1.701 3.083-3.309 5.994-4.713 8.747c-2.945 5.771-.654 12.836 5.116 15.781a11.7 11.7 0 0 0 5.323 1.285a11.73 11.73 0 0 0 10.457-6.401"
|
||||
/>
|
||||
</svg>
|
||||
<p className={styles.logged_in}>You're logged in!</p>
|
||||
|
||||
<select name="" id="">
|
||||
<option value="">Source</option>
|
||||
</select>
|
||||
|
||||
<hr style={{ width: "100%", margin: "1rem 0" }} className={styles.hr} />
|
||||
<button onClick={() => setIsLoggedIn(false)} className={styles.submit}>
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.modal}>
|
||||
{isUsingQuickConnect ? (
|
||||
<div className={styles.input_container}>
|
||||
<label htmlFor="code">Code</label>
|
||||
|
||||
<div className={styles.quick_connect_wrapper}>
|
||||
<input
|
||||
id="quick-connect"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
maxLength={6}
|
||||
value={quickConnectCode!}
|
||||
onChange={(e) => setQuickConnectCode(e.target.value.replace(/\D/g, ""))}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
// Force caret to always be at the end
|
||||
onKeyDown={(e) => {
|
||||
if (["ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
// Same here
|
||||
onSelect={(e) => {
|
||||
const element = e.target as HTMLInputElement;
|
||||
element.setSelectionRange(element.value.length, element.value.length);
|
||||
}}
|
||||
className={styles.quick_connect_input}
|
||||
/>
|
||||
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className={`${styles.quick_connect_box} ${isFocused && quickConnectCode.length === i ? styles.quick_connect_box_active : ""}`}>
|
||||
{quickConnectCode[i]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.input_container}>
|
||||
<label htmlFor="url">URL</label>
|
||||
<input id="url" type="text" placeholder="Enter Jellyfin URL..." value={url} onChange={(e) => setUrl(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className={styles.input_container}>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input id="username" type="text" placeholder="Enter username..." value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className={styles.input_container}>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input id="password" type="password" placeholder="Enter password..." value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className={styles.separator}>
|
||||
<hr className={styles.hr} />
|
||||
<span>or</span>
|
||||
<hr className={styles.hr} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsUsingQuickConnect((prev) => {
|
||||
if (!prev) {
|
||||
document.getElementById("quick-connect")?.focus();
|
||||
}
|
||||
|
||||
return !prev;
|
||||
});
|
||||
}}
|
||||
className={`${styles.quick_connect} ${styles.button}`}
|
||||
>
|
||||
{isUsingQuickConnect ? "Username/Password" : "Quick Connect"}
|
||||
</button>
|
||||
<button onClick={login} className={styles.button}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
98
src/styles.module.css
Normal file
98
src/styles.module.css
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
.button {
|
||||
background-color: var(--spice-button);
|
||||
color: var(--spice-text);
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.hr {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
border-top: 1px solid var(--spice-button-disabled);
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.logged_in {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input_container label {
|
||||
color: var(--spice-subtext);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.input_container input,
|
||||
.quick_connect_box {
|
||||
background-color: var(--spice-main-elevated);
|
||||
color: var(--spice-text);
|
||||
border: 1px solid var(--spice-card);
|
||||
padding: 0.5rem 0.6rem;
|
||||
font-size: 0.95rem;
|
||||
transition: 200ms border-color;
|
||||
}
|
||||
|
||||
.input_container input:focus,
|
||||
.quick_connect_box_active {
|
||||
border-color: var(--spice-button);
|
||||
}
|
||||
|
||||
.input_container input::placeholder {
|
||||
color: var(--spice-subtext);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--spice-subtext);
|
||||
}
|
||||
|
||||
.quick_connect {
|
||||
background-color: var(--spice-main-elevated);
|
||||
}
|
||||
|
||||
.quick_connect_wrapper {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 0.25rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.quick_connect_input {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
color: transparent !important;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.quick_connect_box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
54
src/types/spicetify.d.ts
vendored
54
src/types/spicetify.d.ts
vendored
|
|
@ -325,8 +325,8 @@ declare namespace Spicetify {
|
|||
*/
|
||||
container: HTMLElement;
|
||||
};
|
||||
}
|
||||
) => void
|
||||
},
|
||||
) => void,
|
||||
): void;
|
||||
/**
|
||||
* Skip to previous track.
|
||||
|
|
@ -519,19 +519,8 @@ declare namespace Spicetify {
|
|||
function put(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||
function del(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||
function patch(url: string, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||
function sub(
|
||||
url: string,
|
||||
callback: (b: Response["body"]) => void,
|
||||
onError?: (e: Error) => void,
|
||||
body?: Body,
|
||||
headers?: Headers
|
||||
): Promise<Response["body"]>;
|
||||
function postSub(
|
||||
url: string,
|
||||
body: Body | null,
|
||||
callback: (b: Response["body"]) => void,
|
||||
onError?: (e: Error) => void
|
||||
): Promise<Response["body"]>;
|
||||
function sub(url: string, callback: (b: Response["body"]) => void, onError?: (e: Error) => void, body?: Body, headers?: Headers): Promise<Response["body"]>;
|
||||
function postSub(url: string, body: Body | null, callback: (b: Response["body"]) => void, onError?: (e: Error) => void): Promise<Response["body"]>;
|
||||
function request(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
||||
function resolve(method: Method, url: string, body?: Body, headers?: Headers): Promise<Response>;
|
||||
}
|
||||
|
|
@ -786,7 +775,18 @@ declare namespace Spicetify {
|
|||
* Contains vast array of internal APIs.
|
||||
* Please explore in Devtool Console.
|
||||
*/
|
||||
const Platform: any;
|
||||
const Platform: {
|
||||
PlaybackAPI: any;
|
||||
History: {
|
||||
push: (path: Location | string) => void;
|
||||
replace: (path: Location | string) => void;
|
||||
goBack: () => void;
|
||||
goForward: () => void;
|
||||
listen: (listener: (location: Location) => void) => () => void;
|
||||
entries: Location[];
|
||||
location: Location;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Queue object contains list of queuing tracks,
|
||||
* history of played tracks and current track metadata.
|
||||
|
|
@ -1833,14 +1833,7 @@ declare namespace Spicetify {
|
|||
* Create a button on the right side of the playbar
|
||||
*/
|
||||
class Button {
|
||||
constructor(
|
||||
label: string,
|
||||
icon: Icon | string,
|
||||
onClick?: (self: Button) => void,
|
||||
disabled?: boolean,
|
||||
active?: boolean,
|
||||
registerOnCreate?: boolean
|
||||
);
|
||||
constructor(label: string, icon: Icon | string, onClick?: (self: Button) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
|
||||
label: string;
|
||||
icon: string;
|
||||
onClick: (self: Button) => void;
|
||||
|
|
@ -1856,14 +1849,7 @@ declare namespace Spicetify {
|
|||
* Create a widget next to track info
|
||||
*/
|
||||
class Widget {
|
||||
constructor(
|
||||
label: string,
|
||||
icon: Icon | string,
|
||||
onClick?: (self: Widget) => void,
|
||||
disabled?: boolean,
|
||||
active?: boolean,
|
||||
registerOnCreate?: boolean
|
||||
);
|
||||
constructor(label: string, icon: Icon | string, onClick?: (self: Widget) => void, disabled?: boolean, active?: boolean, registerOnCreate?: boolean);
|
||||
label: string;
|
||||
icon: string;
|
||||
onClick: (self: Widget) => void;
|
||||
|
|
@ -2056,7 +2042,7 @@ declare namespace Spicetify {
|
|||
* @return Function to handle GraphQL queries
|
||||
*/
|
||||
function Handler(
|
||||
context: Record<string, any>
|
||||
context: Record<string, any>,
|
||||
): (query: (typeof Definitions)[Query | string], variables?: Record<string, any>, context?: Record<string, any>) => Promise<any>;
|
||||
}
|
||||
|
||||
|
|
@ -2077,7 +2063,7 @@ declare namespace Spicetify {
|
|||
label?: string,
|
||||
contextUri?: string,
|
||||
sectionIndex?: number,
|
||||
dropOriginUri?: string
|
||||
dropOriginUri?: string,
|
||||
): (event: React.DragEvent, uris?: string[], label?: string, contextUri?: string, sectionIndex?: number) => void;
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue