homebridge-yoto 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/platform.js +41 -0
- package/lib/playerAccessory.js +38 -8
- package/lib/yotoApi.js +11 -2
- package/package.json +1 -1
package/lib/platform.js
CHANGED
|
@@ -56,6 +56,10 @@ export class YotoPlatform {
|
|
|
56
56
|
// Status polling interval
|
|
57
57
|
this.statusPollInterval = null
|
|
58
58
|
|
|
59
|
+
// Cache of user's owned MYO card IDs
|
|
60
|
+
/** @type {Set<string>} */
|
|
61
|
+
this.ownedCardIds = new Set()
|
|
62
|
+
|
|
59
63
|
// Initialize API clients
|
|
60
64
|
this.auth = new YotoAuth(log, this.config.clientId)
|
|
61
65
|
this.yotoApi = new YotoApi(log, this.auth)
|
|
@@ -108,6 +112,9 @@ export class YotoPlatform {
|
|
|
108
112
|
throw error
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
// Fetch user's owned cards for lookup optimization
|
|
116
|
+
await this.fetchOwnedCards()
|
|
117
|
+
|
|
111
118
|
// Start platform-level status polling (every 60 seconds)
|
|
112
119
|
this.startStatusPolling()
|
|
113
120
|
} catch (error) {
|
|
@@ -173,6 +180,40 @@ export class YotoPlatform {
|
|
|
173
180
|
}
|
|
174
181
|
|
|
175
182
|
/**
|
|
183
|
+
* Fetch user's owned MYO cards and cache their IDs
|
|
184
|
+
* @returns {Promise<void>}
|
|
185
|
+
*/
|
|
186
|
+
async fetchOwnedCards () {
|
|
187
|
+
try {
|
|
188
|
+
this.log.debug(LOG_PREFIX.PLATFORM, 'Fetching user\'s owned cards...')
|
|
189
|
+
const myContent = await this.yotoApi.getMyContent()
|
|
190
|
+
|
|
191
|
+
// Cache card IDs
|
|
192
|
+
if (myContent.cards && Array.isArray(myContent.cards)) {
|
|
193
|
+
this.ownedCardIds.clear()
|
|
194
|
+
for (const card of myContent.cards) {
|
|
195
|
+
if (card.cardId) {
|
|
196
|
+
this.ownedCardIds.add(card.cardId)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.log.info(LOG_PREFIX.PLATFORM, `✓ Cached ${this.ownedCardIds.size} owned card(s)`)
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.log.warn(LOG_PREFIX.PLATFORM, 'Failed to fetch owned cards, card details may be limited:', error)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Check if a card is owned by the user
|
|
208
|
+
* @param {string} cardId - Card ID to check
|
|
209
|
+
* @returns {boolean}
|
|
210
|
+
*/
|
|
211
|
+
isCardOwned (cardId) {
|
|
212
|
+
return this.ownedCardIds.has(cardId)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handle token refresh - update config
|
|
176
217
|
* Perform device authorization flow
|
|
177
218
|
* @returns {Promise<void>}
|
|
178
219
|
*/
|
package/lib/playerAccessory.js
CHANGED
|
@@ -785,10 +785,10 @@ export class YotoPlayerAccessory {
|
|
|
785
785
|
* @returns {Promise<CharacteristicValue>}
|
|
786
786
|
*/
|
|
787
787
|
async getVolume () {
|
|
788
|
-
if (!this.currentStatus) {
|
|
788
|
+
if (!this.currentStatus || this.currentStatus.userVolume === undefined) {
|
|
789
789
|
return 50
|
|
790
790
|
}
|
|
791
|
-
return this.currentStatus.userVolume
|
|
791
|
+
return Number(this.currentStatus.userVolume) || 50
|
|
792
792
|
}
|
|
793
793
|
|
|
794
794
|
/**
|
|
@@ -813,10 +813,10 @@ export class YotoPlayerAccessory {
|
|
|
813
813
|
* @returns {Promise<CharacteristicValue>}
|
|
814
814
|
*/
|
|
815
815
|
async getMute () {
|
|
816
|
-
if (!this.currentStatus) {
|
|
816
|
+
if (!this.currentStatus || this.currentStatus.userVolume === undefined) {
|
|
817
817
|
return false
|
|
818
818
|
}
|
|
819
|
-
return this.currentStatus.userVolume === 0
|
|
819
|
+
return Number(this.currentStatus.userVolume) === 0
|
|
820
820
|
}
|
|
821
821
|
|
|
822
822
|
/**
|
|
@@ -849,10 +849,10 @@ export class YotoPlayerAccessory {
|
|
|
849
849
|
* @returns {Promise<CharacteristicValue>}
|
|
850
850
|
*/
|
|
851
851
|
async getBatteryLevel () {
|
|
852
|
-
if (!this.currentStatus) {
|
|
852
|
+
if (!this.currentStatus || this.currentStatus.batteryLevel === undefined) {
|
|
853
853
|
return 100
|
|
854
854
|
}
|
|
855
|
-
return this.currentStatus.batteryLevel
|
|
855
|
+
return Number(this.currentStatus.batteryLevel) || 100
|
|
856
856
|
}
|
|
857
857
|
|
|
858
858
|
/**
|
|
@@ -1527,10 +1527,27 @@ export class YotoPlayerAccessory {
|
|
|
1527
1527
|
}
|
|
1528
1528
|
|
|
1529
1529
|
this.activeContentCardId = cardId
|
|
1530
|
+
|
|
1531
|
+
// Handle no card inserted
|
|
1532
|
+
if (!cardId || cardId === 'none' || cardId === 'null') {
|
|
1533
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] No card inserted`)
|
|
1534
|
+
this.activeContentInfo = null
|
|
1535
|
+
this.accessory.context.activeContentInfo = null
|
|
1536
|
+
return
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Check if we own this card before attempting to fetch details
|
|
1540
|
+
if (!this.platform.isCardOwned(cardId)) {
|
|
1541
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Playing card ${cardId} (store content - details not available)`)
|
|
1542
|
+
this.activeContentInfo = null
|
|
1543
|
+
this.accessory.context.activeContentInfo = null
|
|
1544
|
+
return
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1530
1547
|
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Active card changed: ${cardId}`)
|
|
1531
1548
|
|
|
1532
1549
|
try {
|
|
1533
|
-
// Fetch card details
|
|
1550
|
+
// Fetch card details for owned cards
|
|
1534
1551
|
const content = await this.platform.yotoApi.getContent(cardId)
|
|
1535
1552
|
this.activeContentInfo = content
|
|
1536
1553
|
|
|
@@ -1555,7 +1572,20 @@ export class YotoPlayerAccessory {
|
|
|
1555
1572
|
// Store in context for persistence
|
|
1556
1573
|
this.accessory.context.activeContentInfo = this.activeContentInfo
|
|
1557
1574
|
} catch (error) {
|
|
1558
|
-
|
|
1575
|
+
// Handle 403 Forbidden (store-bought cards user doesn't own)
|
|
1576
|
+
if (error instanceof Error && error.message.includes('403')) {
|
|
1577
|
+
this.log.info(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Playing card ${cardId} (store content - details not available)`)
|
|
1578
|
+
this.activeContentInfo = null
|
|
1579
|
+
this.accessory.context.activeContentInfo = null
|
|
1580
|
+
} else if (error instanceof Error && error.message.includes('404')) {
|
|
1581
|
+
// Card not found - might be deleted or invalid
|
|
1582
|
+
this.log.warn(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Card ${cardId} not found`)
|
|
1583
|
+
this.activeContentInfo = null
|
|
1584
|
+
this.accessory.context.activeContentInfo = null
|
|
1585
|
+
} else {
|
|
1586
|
+
// Other errors
|
|
1587
|
+
this.log.error(LOG_PREFIX.ACCESSORY, `[${this.device.name}] Failed to fetch content details:`, error)
|
|
1588
|
+
}
|
|
1559
1589
|
}
|
|
1560
1590
|
}
|
|
1561
1591
|
}
|
package/lib/yotoApi.js
CHANGED
|
@@ -114,7 +114,6 @@ export class YotoApi {
|
|
|
114
114
|
|
|
115
115
|
if (!response.ok) {
|
|
116
116
|
const errorText = await response.text()
|
|
117
|
-
this.log.error(LOG_PREFIX.API, `Request failed: ${response.status} ${errorText}`)
|
|
118
117
|
|
|
119
118
|
// Handle 401 by attempting token refresh once
|
|
120
119
|
if (response.status === 401 && !options._retried) {
|
|
@@ -124,6 +123,13 @@ export class YotoApi {
|
|
|
124
123
|
return this.request(endpoint, { ...options, _retried: true })
|
|
125
124
|
}
|
|
126
125
|
|
|
126
|
+
// Reduce noise for expected errors (403/404 on content endpoints)
|
|
127
|
+
if ((response.status === 403 || response.status === 404) && endpoint.startsWith('/content/')) {
|
|
128
|
+
this.log.debug(LOG_PREFIX.API, `Request failed: ${response.status} ${errorText}`)
|
|
129
|
+
} else {
|
|
130
|
+
this.log.error(LOG_PREFIX.API, `Request failed: ${response.status} ${errorText}`)
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
throw new Error(`API request failed: ${response.status} ${errorText}`)
|
|
128
134
|
}
|
|
129
135
|
|
|
@@ -134,7 +140,10 @@ export class YotoApi {
|
|
|
134
140
|
|
|
135
141
|
return await response.json()
|
|
136
142
|
} catch (error) {
|
|
137
|
-
|
|
143
|
+
// Only log non-API errors (network issues, etc.)
|
|
144
|
+
if (!(error instanceof Error && error.message.startsWith('API request failed'))) {
|
|
145
|
+
this.log.error(LOG_PREFIX.API, `${ERROR_MESSAGES.API_ERROR}:`, error)
|
|
146
|
+
}
|
|
138
147
|
throw error
|
|
139
148
|
}
|
|
140
149
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-yoto",
|
|
3
3
|
"description": "Control your Yoto players through Apple HomeKit with real-time MQTT updates",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.14",
|
|
5
5
|
"author": "Bret Comnes <bcomnes@gmail.com> (https://bret.io)",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/bcomnes/homebridge-yoto/issues"
|