From c8bdb1ba585800e22bea7fd3f886a4246a5faaac Mon Sep 17 00:00:00 2001 From: kmmiio99o Date: Sun, 1 Feb 2026 11:44:57 +0000 Subject: [PATCH] deploy: 13c1bd8f018ec571a3481c6d23cd57a22251cec8 --- multi-scrobbler/index.js | 2 +- multi-scrobbler/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/multi-scrobbler/index.js b/multi-scrobbler/index.js index 058365c..7e222bc 100644 --- a/multi-scrobbler/index.js +++ b/multi-scrobbler/index.js @@ -1 +1 @@ -(function(Y,p,r,x,Lt,D,re,T,v,Q,He,ne){"use strict";const b={DEFAULT_APP_NAME:"Music",DEFAULT_TIME_INTERVAL:5,APPLICATION_ID:"1368513179272871956",MIN_UPDATE_INTERVAL:3,MAX_RETRY_ATTEMPTS:3,RETRY_DELAY:5e3,SERVICES:{lastfm:{name:"Last.fm",baseUrl:"https://ws.audioscrobbler.com/2.0",requiresApiKey:!0,requiresToken:!1},librefm:{name:"Libre.fm",baseUrl:"https://libre.fm/api",requiresApiKey:!0,requiresToken:!1},listenbrainz:{name:"ListenBrainz",baseUrl:"https://api.listenbrainz.org/1",requiresApiKey:!1,requiresToken:!0}},DEFAULT_HEADERS:{},DEFAULT_COVER_HASHES:["2a96cbd8b46e442fc41c2b86b821562f"],DEFAULT_SETTINGS:{username:"",apiKey:"",appName:"Music",timeInterval:5,showTimestamp:!0,listeningTo:!0,showLargeText:!0,ignoreYouTubeMusic:!1,verboseLogging:!1,service:void 0,librefmUsername:"",librefmApiKey:"",listenbrainzUsername:"",listenbrainzToken:""},API_ERROR_CODES:{2:"Invalid service",3:"Invalid method",4:"Invalid format",5:"Invalid parameters",6:"Invalid resource specified",7:"Invalid session key",8:"Invalid API key",9:"Invalid session",10:"Invalid API signature",11:"Service offline",13:"Invalid method signature supplied",16:"Service temporarily unavailable",26:"Suspended API key",29:"Rate limit exceeded"}};function B(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function pe(e,t){for(var n=0;n10&&(R.serviceErrors[e]=R.serviceErrors[e].slice(-10)),ye(`${e} error:`,t)}function we(){R.lastSuccessfulUpdate=new Date().toISOString(),J("Successful update recorded at:",R.lastSuccessfulUpdate)}function ee(e){return ee=Object.setPrototypeOf?Object.getPrototypeOf:function(n){return n.__proto__||Object.getPrototypeOf(n)},ee(e)}function ve(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(ve=function(){return!!e})()}function et(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function tt(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function rt(e,t){return t&&(tt(t)==="object"||typeof t=="function")?t:et(e)}function ie(e,t,n){return t=ee(t),rt(e,ve()?Reflect.construct(t,n||[],ee(e).constructor):t.apply(e,n))}function se(e,t){return se=Object.setPrototypeOf||function(a,i){return a.__proto__=i,a},se(e,t)}function oe(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&se(e,t)}let le=(function(){function e(){B(this,e)}return O(e,[{key:"log",value:function(...t){console.log(`[${this.getServiceName()}]`,...t)}},{key:"logError",value:function(...t){console.error(`[${this.getServiceName()}] Error:`,...t)}},{key:"logVerbose",value:function(...t){console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"handleError",value:async function(t){this.lastError=t.error||0;const n=this.getErrorMessage(t);throw this.logError(n),Z(this.getServiceName().toLowerCase(),n),new Error(`${this.getServiceName()} API Error: ${n}`)}},{key:"getErrorMessage",value:function(t){return t.error&&b.API_ERROR_CODES[t.error]?b.API_ERROR_CODES[t.error]:t.message||t.toString()||"Unknown error"}},{key:"makeRequest",value:async function(t,n={}){try{this.logVerbose(`Making request to: ${t}`),me();const a=await fetch(t,{...n,headers:{"User-Agent":"Vendetta Multi-Service Scrobbler/3.0.0",...n.headers}});if(!a.ok){const s=new Error(`HTTP ${a.status}: ${a.statusText}`);throw Z(this.getServiceName().toLowerCase(),s.message),s}const i=await a.json();return i.error&&await this.handleError(i),this.retryCount=0,i}catch(a){if(this.retryCount++,this.retryCount>b.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,Z(this.getServiceName().toLowerCase(),`Max retries exceeded: ${a.message}`),a;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${b.MAX_RETRY_ATTEMPTS})`),await new Promise(function(i){return setTimeout(i,b.RETRY_DELAY)}),this.makeRequest(t,n)}}},{key:"isDefaultCover",value:function(t){return t?b.DEFAULT_COVER_HASHES.some(function(n){return t.includes(n)}):!0}},{key:"processAlbumArt",value:function(t){return!t||this.isDefaultCover(t)?null:t}},{key:"getLastError",value:function(){return this.lastError}},{key:"resetRetryCount",value:function(){this.retryCount=0}}]),e})(),nt=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{if(!l.username||!l.apiKey)throw new Error("Username or API key not set");const n=new URLSearchParams({method:"user.getinfo",user:l.username,api_key:l.apiKey,format:"json"}),a=`${b.SERVICES.lastfm.baseUrl}?${n}`;return await this.makeRequest(a),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{if(!l.username||!l.apiKey)throw new Error("Username or API key not set");this.logVerbose("Fetching latest scrobble for user:",l.username);const n=new URLSearchParams({method:"user.getrecenttracks",user:l.username,api_key:l.apiKey,limit:"1",extended:"1",format:"json"}),a=`${b.SERVICES.lastfm.baseUrl}?${n}`,i=(await this.makeRequest(a))?.recenttracks?.track?.[0];if(!i)throw new Error("No tracks found");this.logVerbose("Raw track data:",i);const s=!!i["@attr"]?.nowplaying,o=i.date?.uts?parseInt(i.date.uts):Math.floor(Date.now()/1e3);let u=null,f=null;if(s)try{const P=new URLSearchParams({method:"track.getInfo",track:i.name,artist:i.artist.name,api_key:l.apiKey,format:"json"}),$=`${b.SERVICES.lastfm.baseUrl}?${P}`,y=await this.makeRequest($);y?.track?.duration&&(u=parseInt(y.track.duration),u>0&&(u=Math.floor(u/1e3),f=o+u))}catch(P){this.logVerbose("Failed to fetch track duration:",P)}const h=this.processAlbumArt(i.image?.find(function(P){return P.size==="large"})?.["#text"]),k={name:i.name,artist:i.artist.name,album:i.album["#text"],albumArt:h,url:i.url,date:i.date?.["#text"]??"now",nowPlaying:s,loved:i.loved==="1",from:o,to:f,duration:u};return this.logVerbose("Processed track:",k),this.log(`${s?"Now playing":"Last played"}:`,`${k.artist} - ${k.name}`),k}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(le),at=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=l.librefmUsername,a=l.librefmApiKey;if(!n||!a)throw new Error("Username or API key not set for Libre.fm");const i=new URLSearchParams({method:"user.getinfo",user:n,api_key:a,format:"json"}),s=`${b.SERVICES.librefm.baseUrl}?${i}`;return await this.makeRequest(s),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=l.librefmUsername,a=l.librefmApiKey;if(!n||!a)throw new Error("Username or API key not set for Libre.fm");this.logVerbose("Fetching latest scrobble for user:",n);const i=new URLSearchParams({method:"user.getrecenttracks",user:n,api_key:a,limit:"1",extended:"1",format:"json"}),s=`${b.SERVICES.librefm.baseUrl}?${i}`,o=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!o)throw new Error("No tracks found");this.logVerbose("Raw track data:",o);const u=!!o["@attr"]?.nowplaying,f=o.date?.uts?parseInt(o.date.uts):Math.floor(Date.now()/1e3);let h=null,k=null;if(u)try{const y=new URLSearchParams({method:"track.getInfo",track:o.name,artist:o.artist.name,api_key:a,format:"json"}),M=`${b.SERVICES.librefm.baseUrl}?${y}`,L=await this.makeRequest(M);L?.track?.duration&&(h=parseInt(L.track.duration),h>0&&(h=Math.floor(h/1e3),k=f+h))}catch(y){this.logVerbose("Failed to fetch track duration:",y)}const P=this.processAlbumArt(o.image?.find(function(y){return y.size==="large"})?.["#text"]),$={name:o.name,artist:o.artist.name,album:o.album["#text"],albumArt:P,url:o.url,date:o.date?.["#text"]??"now",nowPlaying:u,loved:o.loved==="1",from:f,to:k,duration:h};return this.logVerbose("Processed track:",$),this.log(`${u?"Now playing":"Last played"}:`,`${$.artist} - ${$.name}`),$}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(le),it=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=l.listenbrainzUsername,a=l.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");const i=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,s={};return a&&(s.Authorization=`Token ${a}`),await this.makeRequest(i,{headers:s}),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=l.listenbrainzUsername,a=l.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");this.logVerbose("Fetching latest scrobble for user:",n);let i=null;const s=function(y){if(y){if(Array.isArray(y.listens))return y.listens;if(y.payload&&Array.isArray(y.payload.listens))return y.payload.listens;if(y.data&&Array.isArray(y.data.listens))return y.data.listens}};try{const y=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/playing-now`,M={};a&&(M.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:M}),U=s(L);U&&U.length>0&&(i=U[0],i.playing_now=!0)}catch(y){this.logVerbose("No currently playing track or failed to fetch:",y)}let o;if(i)o=i,this.logVerbose("Using currently playing track");else{const y=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,M={};a&&(M.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:M}),U=(function(){if(L){if(Array.isArray(L.listens))return L.listens;if(L.payload&&Array.isArray(L.payload.listens))return L.payload.listens;if(L.data&&Array.isArray(L.data.listens))return L.data.listens}})();if(!U||U.length===0)throw new Error("No listens found");o=U[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",o);const u=!!o.playing_now,f=o.listened_at||Math.floor(Date.now()/1e3);let h=null,k=null;o.track_metadata.additional_info?.duration_ms&&(h=Math.floor(o.track_metadata.additional_info.duration_ms/1e3),u&&h>0&&(k=f+h));const P=null;o.track_metadata.additional_info?.release_mbid;const $={name:o.track_metadata.track_name,artist:o.track_metadata.artist_name,album:o.track_metadata.release_name||"",albumArt:P,url:o.track_metadata.additional_info?.origin_url||`https://listenbrainz.org/user/${n}`,date:u?"now":new Date(f*1e3).toISOString(),nowPlaying:u,loved:!1,from:f,to:k,duration:h};return this.logVerbose("Processed track:",$),this.log(`${u?"Now playing":"Last played"}:`,`${$.artist} - ${$.name}`),$}catch(n){throw this.logError("Failed to fetch latest listen:",n),n}}},{key:"getErrorMessage",value:function(n){return n.error?n.error:n.message?n.message:n.toString()||"Unknown error"}}]),t})(le);const K=(function(){function e(){B(this,e),this.serviceInstances=new Map}return O(e,[{key:"getService",value:function(t){this.serviceInstances||(this.serviceInstances=new Map);const n=t||l.service;if(!n)throw new Error("[ServiceFactory] No service type specified and no default service configured");return this.serviceInstances.has(n)||this.serviceInstances.set(n,this.createService(n)),this.serviceInstances.get(n)}},{key:"getCurrentService",value:function(){return this.getService(l.service)}},{key:"createService",value:function(t){switch(t){case"lastfm":return new nt;case"librefm":return new at;case"listenbrainz":return new it;default:throw new Error(`[ServiceFactory] Unknown service type: ${t}`)}}},{key:"clearCache",value:function(){this.serviceInstances?this.serviceInstances.clear():this.serviceInstances=new Map}},{key:"validateCurrentService",value:function(){return this.getCurrentService().validateCredentials()}},{key:"testService",value:async function(t){try{return await this.getService(t).validateCredentials()}catch(n){return console.error(`[ServiceFactory] Failed to test ${t}:`,n),!1}}},{key:"getSupportedServices",value:function(){return["lastfm","librefm","listenbrainz"]}},{key:"getServiceDisplayName",value:function(t){return this.getService(t).getServiceName()}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance();function Re(e){if(!e||e<0)return"0:00";const t=Math.floor(e/3600),n=Math.floor(e%3600/60),a=Math.floor(e%60);return t>0?`${t}:${n.toString().padStart(2,"0")}:${a.toString().padStart(2,"0")}`:`${n}:${a.toString().padStart(2,"0")}`}function Se(){return Math.floor(Date.now()/1e3)}var st=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(st||{});const A=function(...e){return console.log("[Scrobble Plugin]",...e)},z=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},C=function(...e){return l.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ce=(function(){function e(){B(this,e)}return O(e,[{key:"updateActivity",value:async function(){if(S.pluginStopped){A("Plugin is stopped; skipping activity updates and clearing timers"),C("Plugin is stopped, skipping update");try{this.stopUpdates(),C("Update timers cleared due to stopped plugin")}catch(a){z("Error while stopping updates for stopped plugin:",a)}return}const t=K.getCurrentService().getServiceName();C(`Fetching latest track from ${t}...`);let n=!1;try{if(l.ignoreList&&l.ignoreList.length>0){const o=Je.findActivity(function(u){return u.name?l.ignoreList.some(function(f){return u.name.toLowerCase().includes(f.toLowerCase())}):!1});if(o){A(`Ignored app (${o.name}) is currently active; clearing activity and skipping updates`),C(`Ignored app (${o.name}) is currently playing, clearing activity`);try{G("ignoredActivity",o.name)}catch(u){C("Failed to set debug info for ignored activity:",u)}ae();return}}me();const a=await K.getCurrentService().fetchLatestScrobble();if(G("lastTrack",a),!a.nowPlaying){A("No currently playing track reported by service; clearing activity and skipping RPC update"),C("Track is not currently playing");try{G("lastTrack_nowPlaying",!1)}catch(o){C("Failed to set debug info for nowPlaying:",o)}ae();return}if(S.lastTrackUrl===a.url){A("Track unchanged; skipping Discord RPC update"),C("Track hasn't changed"),we(),this.consecutiveFailures=0;return}n=!0,A(`\u{1F3B5} Track changed: ${a.artist} - ${a.name}`);let i;if(a.nowPlaying&&l.showTimestamp&&a.from){const o=Se();let u=a.from;if(u0){const f=Math.min(a.duration*.1,30);u=o-f}else u=o;C("had to estimate start time")}i={start:u*1e3},a.to&&(i.end=a.to*1e3)}C(`\u{1F3AF} Preparing RPC update for: ${a.artist} - ${a.name}`);const s={name:l.appName||b.DEFAULT_APP_NAME,flags:0,type:l.listeningTo?2:0,details:a.name,state:`${a.artist}`,status_display_type:1,application_id:b.APPLICATION_ID};if(s.name.includes("{{")){const o={artist:a.artist,name:a.name,album:a.album,service:t};for(const[u,f]of Object.entries(o))s.name=s.name.replace(new RegExp(`{{${u}}}`,"g"),f||"")}if(i&&(s.timestamps=i,C("Timestamps set:",{start:new Date(i.start).toISOString(),end:i.end?new Date(i.end).toISOString():"none",duration:a.duration?Re(a.duration):"unknown"})),a.album||a.albumArt){const o=a.albumArt?[a.albumArt]:[];let u=(await Ze(o))[0];if(u){if(s.assets={large_image:u},l.showLargeText){let f="";if(l.showAlbumInTooltip&&a.album&&(f+=`on ${a.album}`),l.showDurationInTooltip&&a.duration){const h=Re(a.duration);f?f+=` \u2022 ${h}`:f=h}f&&(s.assets.large_text=f)}C("Album art set:",u),s.assets.large_text&&C("Tooltip text set:",s.assets.large_text)}else a.album&&l.showLargeText&&l.showAlbumInTooltip&&(s.assets={large_text:`on ${a.album}`})}C("Setting Discord activity:",s),G("lastActivity",s),await he(s),S.lastTrackUrl=a.url,this.currentActivity=s,S.lastActivity=s,this.consecutiveFailures=0,this.lastUpdateTime=Se(),we(),A(`\u2705 RPC updated successfully: ${a.artist} - ${a.name}`)}catch(a){z("Update failed:",a);try{Z(l.service,a.message)}catch(i){z("Failed to record service error:",i)}try{G("lastUpdateError",{message:a.message,service:l.service,lastTrackUrl:S.lastTrackUrl})}catch(i){C("Failed to set debug info for last update error:",i)}this.handleError(a)}}},{key:"handleError",value:function(t){this.consecutiveFailures++,G("lastError",t),z(`Failure ${this.consecutiveFailures}/${b.MAX_RETRY_ATTEMPTS}:`,t.message),this.consecutiveFailures>=b.MAX_RETRY_ATTEMPTS&&(z("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var t=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),A("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){A("Attempting to reconnect..."),t.initialize().then(function(){A("Reconnection successful!"),t.stopReconnection()}).catch(function(n){z("Reconnection attempt failed:",n.message)})},b.RETRY_DELAY))}},{key:"stopReconnection",value:function(){try{this.reconnectTimer&&(clearInterval(this.reconnectTimer),this.reconnectTimer=void 0),this.isReconnecting=!1,this.consecutiveFailures=0}catch(t){console.error("[Scrobble Plugin] Reconnection cleanup error:",t)}}},{key:"stopUpdates",value:function(){try{this.updateTimer&&(clearInterval(this.updateTimer),this.updateTimer=void 0)}catch(t){console.error("[Scrobble Plugin] Timer cleanup error:",t)}}},{key:"initialize",value:async function(){var t=this;if(S.pluginStopped)throw new Error("Plugin is stopped");const n=K.getCurrentService().getServiceName();A(`Initializing with ${n}...`);try{if(!await K.validateCurrentService())throw new Error(`Invalid credentials for ${n}`);A(`${n} credentials validated successfully`)}catch(i){throw z(`Failed to validate ${n} credentials:`,i),i}this.stopUpdates(),A("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const a=Math.max((Number(l.timeInterval)||b.DEFAULT_SETTINGS.timeInterval)*1e3,b.MIN_UPDATE_INTERVAL*1e3);this.updateTimer=setInterval(function(){return t.updateActivity()},a),A(`Update timer started with interval: ${a}ms (${a/1e3}s)`)}},{key:"stop",value:function(){if(!S.pluginStopped){A("Stopping plugin..."),S.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),ae(),A("Plugin stopped successfully")}catch(t){console.error("[Scrobble Plugin] Stop error:",t)}}}},{key:"switchService",value:async function(t){if(S.pluginStopped)return;A(`Switching to ${t}...`);const n=!S.pluginStopped;this.stop();try{K.clearCache(),S.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,n&&(S.pluginStopped=!1,await this.initialize())}catch(a){z("Failed to switch service:",a)}}},{key:"getStatus",value:function(){const t=K.getCurrentService().getServiceName();return{running:!S.pluginStopped,service:t,consecutiveFailures:this.consecutiveFailures,isReconnecting:this.isReconnecting,lastTrackUrl:S.lastTrackUrl,updateInterval:this.updateTimer?"Active":"Inactive"}}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance(),ot=function(){return ce.initialize()},ue=function(){return ce.stop()},lt=function(e){return ce.switchService(e)},{ScrollView:F}=x.findByProps("ScrollView"),{TableRowGroup:E,TableSwitchRow:Ee,TableCheckboxRow:q,Stack:N,TableRow:g}=x.findByProps("TableSwitchRow","TableCheckboxRow","TableRowGroup","Stack","TableRow"),{TextInput:_}=x.findByProps("TextInput");function ct(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing Last.fm connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("lastfm")?T.showToast("\u2705 Last.fm connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C Last.fm connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Last.fm Username",value:c("username"),onChange:function(n){I("username",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"Last.fm API Key",value:c("apiKey"),onChange:function(n){I("apiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),T.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",v.getAssetIDByName("XIcon"))}}}))))}function ut(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing Libre.fm connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("librefm")?T.showToast("\u2705 Libre.fm connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C Libre.fm connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Libre.fm Username",value:c("librefmUsername"),onChange:function(n){I("librefmUsername",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"Libre.fm API Key",value:c("librefmApiKey"),onChange:function(n){I("librefmApiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),T.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",v.getAssetIDByName("XIcon"))}}}))))}function gt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing ListenBrainz connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("listenbrainz")?T.showToast("\u2705 ListenBrainz connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C ListenBrainz connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"ListenBrainz Username",value:c("listenbrainzUsername"),onChange:function(n){I("listenbrainzUsername",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"ListenBrainz Token (for private profiles)",value:c("listenbrainzToken"),onChange:function(n){I("listenbrainzToken",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://listenbrainz.org/settings/")}catch(n){console.error("Failed to open ListenBrainz settings URL:",n),T.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",v.getAssetIDByName("XIcon"))}}}))))}function ft(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Activity Display"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:`App Name (Default: ${b.DEFAULT_SETTINGS.appName})`,value:c("appName",b.DEFAULT_SETTINGS.appName),onChange:function(t){I("appName",t),e()},isClearable:!0}),r.React.createElement(_,{placeholder:`Update Interval (Default: ${b.DEFAULT_SETTINGS.timeInterval}s)`,value:String(c("timeInterval",b.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const n=Number(t);n>=b.MIN_UPDATE_INTERVAL&&(I("timeInterval",n),e())},keyboardType:"numeric",isClearable:!0}))),r.React.createElement(E,{title:"About Display Settings"},r.React.createElement(g,{label:"App Name",subLabel:"The name shown in Discord for your activity"}),r.React.createElement(g,{label:"Update Interval",subLabel:"How often the plugin checks for new tracks (in seconds)"}),r.React.createElement(g,{label:"Minimum Interval",subLabel:`The plugin will never check more frequently than ${b.MIN_UPDATE_INTERVAL} seconds`}))))}function dt(){D.useProxy(p.plugin.storage);const[e,t]=r.React.useState(null),[n,a]=r.React.useState(!0),[i,s]=r.React.useState(0),[o,u]=r.React.useState(!1),f={name:"Bohemian Rhapsody",artist:"Queen",artists:["Queen"],album:"A Night at the Opera",image:null,nowPlaying:!0,duration:354,startTime:Math.floor(Date.now()/1e3)-120},h=function(d){return d?Array.isArray(d)?d.filter(function(m){return m&&typeof m=="string"}):d["#text"]?d["#text"].split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):typeof d=="string"?d.split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):["Unknown Artist"]:["Unknown Artist"]},k=function(d){return!d||d.length===0?"Unknown Artist":d.length===1?d[0]:d.length===2?`${d[0]} & ${d[1]}`:`${d.slice(0,-1).join(", ")} & ${d[d.length-1]}`};r.React.useEffect(function(){(async function(){if(!c("username")||!c("apiKey")){t(f),u(!0),a(!1);return}try{a(!0);const d=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${c("username")}&api_key=${c("apiKey")}&format=json&limit=1`)).json();if(d.recenttracks&&d.recenttracks.track&&d.recenttracks.track.length>0){const m=d.recenttracks.track[0],j=h(m.artist);let Xe=180;try{const At=j[0]||"Unknown Artist",de=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=${c("apiKey")}&artist=${encodeURIComponent(At)}&track=${encodeURIComponent(m.name)}&format=json&username=${c("username")}`)).json();de.track&&de.track.duration&&(Xe=Math.floor(de.track.duration/1e3))}catch{console.log("Could not fetch track duration, using default")}t({name:m.name||"Unknown Track",artist:k(j),artists:j,album:m.album?.["#text"]||"Unknown Album",image:m.image?.[2]?.["#text"]||m.image?.[1]?.["#text"]||null,nowPlaying:m["@attr"]?.nowplaying==="true",duration:Xe,startTime:m["@attr"]?.nowplaying==="true"?Math.floor(Date.now()/1e3)-60:null}),u(!1)}else t(f),u(!0)}catch(d){console.error("Failed to fetch preview data, using fallback:",d),t(f),u(!0)}finally{a(!1)}})()},[c("username"),c("apiKey")]),r.React.useEffect(function(){if(!e?.nowPlaying||!c("showTimestamp"))return;const d=setInterval(function(){if(e.startTime&&e.duration){const m=Math.floor(Date.now()/1e3)-e.startTime,j=Math.min(m/e.duration,1);s(j)}},1e3);return function(){return clearInterval(d)}},[e,c("showTimestamp")]);const P=function(d){const m=Math.floor(d/60),j=Math.floor(d%60);return`${m}:${j.toString().padStart(2,"0")}`},$=function(){let d="";if(c("showAlbumInTooltip")&&e?.album&&(d+=`on ${e.album}`),c("showDurationInTooltip")&&e?.duration){const m=` \u2022 ${P(e.duration)}`;d?d+=m:d=P(e.duration)}return d||"No tooltip text"},y=function(){return e?.duration?e.nowPlaying?{current:i*e.duration,total:e.duration,progress:i}:{current:e.duration*.3,total:e.duration,progress:.3}:{current:0,total:0,progress:0}},M=c("listeningTo")?"Listening to":"Playing",L=c("appName","Music");if(n)return r.React.createElement(r.ReactNative.View,{style:w.container},r.React.createElement(r.ReactNative.View,{style:w.loadingContent},r.React.createElement(r.ReactNative.View,{style:w.loadingSpinner}),r.React.createElement(r.ReactNative.Text,{style:w.loadingText},"Loading preview...")));if(!e)return r.React.createElement(r.ReactNative.View,{style:w.container},r.React.createElement(r.ReactNative.Text,{style:w.centeredText},"Unable to load preview"));const U=y(),We=$();return r.React.createElement(r.ReactNative.View,{style:w.previewContainer},r.React.createElement(r.ReactNative.View,{style:w.header},r.React.createElement(r.ReactNative.Text,{style:w.activityType},M," ",L),r.React.createElement(r.ReactNative.Text,{style:w.rpcPreviewText,numberOfLines:1},"RPC Preview")),r.React.createElement(r.ReactNative.View,{style:w.content},r.React.createElement(r.ReactNative.View,{style:w.albumArt},e.image?r.React.createElement(r.ReactNative.Image,{source:{uri:e.image},style:w.albumImage,resizeMode:"cover"}):r.React.createElement(r.ReactNative.Text,{style:w.musicIcon},"\u{1F3B5}")),r.React.createElement(r.ReactNative.View,{style:w.trackInfo},r.React.createElement(r.ReactNative.Text,{style:w.trackName,numberOfLines:1},e.name),r.React.createElement(r.ReactNative.Text,{style:w.artistName,numberOfLines:1},e.artist),c("showLargeText")&&We!=="No tooltip text"&&r.React.createElement(r.ReactNative.Text,{style:w.tooltipText,numberOfLines:1},We),c("showTimestamp")&&e.duration&&r.React.createElement(r.ReactNative.View,{style:w.progressContainer},r.React.createElement(r.ReactNative.Text,{style:w.timeText},P(U.current)),r.React.createElement(r.ReactNative.View,{style:w.progressBar},r.React.createElement(r.ReactNative.View,{style:[w.progressFill,{width:`${U.progress*100}%`}]})),r.React.createElement(r.ReactNative.Text,{style:w.timeText},P(U.total))))))}const w=r.ReactNative.StyleSheet.create({container:{backgroundColor:"#1e1f22",borderRadius:12,padding:16,marginHorizontal:10,marginBottom:16,borderWidth:1,borderColor:"#3a3c41",alignItems:"center",justifyContent:"center",minHeight:120},loadingContent:{alignItems:"center",justifyContent:"center"},loadingSpinner:{width:20,height:20,borderRadius:10,borderWidth:2,borderColor:"#5865f2",borderTopColor:"transparent",marginBottom:8},previewContainer:{backgroundColor:"#1e1f22",borderRadius:12,padding:16,marginHorizontal:10,marginBottom:16,borderWidth:1,borderColor:"#3a3c41"},header:{flexDirection:"row",justifyContent:"space-between",alignItems:"center",marginBottom:12,gap:8},content:{flexDirection:"row",alignItems:"center"},albumArt:{width:80,height:80,backgroundColor:"#2b2d31",borderRadius:12,justifyContent:"center",alignItems:"center",marginRight:16,borderWidth:1,borderColor:"#40444b",overflow:"hidden",flexShrink:0},trackInfo:{flex:1,minWidth:0},progressContainer:{flexDirection:"row",alignItems:"center",gap:8,marginTop:6},progressBar:{flex:1,height:2,backgroundColor:"#2b2d31",borderRadius:3,overflow:"hidden"},progressFill:{height:2,backgroundColor:"#5865f2",borderRadius:3},loadingText:{color:"#949ba4",fontSize:14,fontWeight:"500"},centeredText:{color:"#949ba4",fontSize:14,fontWeight:"500",textAlign:"center"},activityType:{color:"#dbdee1",fontSize:14,fontWeight:"600",flex:1},rpcPreviewText:{color:"#80848e",fontSize:12,fontStyle:"italic",flexShrink:0},albumImage:{width:80,height:80,borderRadius:12},musicIcon:{color:"#80848e",fontSize:32},trackName:{color:"#ffffff",fontSize:16,fontWeight:"700",marginBottom:4},artistName:{color:"#b5bac1",fontSize:14,fontWeight:"500",marginBottom:4},tooltipText:{color:"#80848e",fontSize:12,fontStyle:"italic",marginBottom:6},timeText:{color:"#80848e",fontSize:11,fontWeight:"500",minWidth:35,textAlign:"center"}});function pt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(s){return s+1},0),t=c("listeningTo",b.DEFAULT_SETTINGS.listeningTo),n=c("showTimestamp",b.DEFAULT_SETTINGS.showTimestamp),a=function(){const s=!t;I("listeningTo",s),!s&&n&&I("showTimestamp",!1),e()},i=function(){t&&(I("showTimestamp",!n),e())};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(dt,null),r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"RPC Display Options"},r.React.createElement(q,{label:"Show as Listening",subLabel:"Display as 'Listening to' instead of 'Playing'",checked:t,onPress:a}),r.React.createElement(q,{label:"Show Tooltip Text",subLabel:"Show album name and track duration in Discord activity tooltip",checked:c("showLargeText",!0),onPress:function(){const s=c("showLargeText",!0);I("showLargeText",!s),e()}}),!t&&r.React.createElement(g,{label:"Timestamp Unavailable",subLabel:"Enable 'Show as Listening' to use timestamp feature",disabled:!0,dimmed:!0}),r.React.createElement(q,{label:"Show Timestamp",subLabel:"Display track progress and duration",checked:n,onPress:i,disabled:!t}),r.React.createElement(q,{label:"Show Album in Tooltip",subLabel:"Include album name in the tooltip text",checked:c("showAlbumInTooltip",!0),onPress:function(){const s=c("showAlbumInTooltip",!0);I("showAlbumInTooltip",!s),e()}}),r.React.createElement(q,{label:"Show Duration in Tooltip",subLabel:"Include track duration in the tooltip text",checked:c("showDurationInTooltip",!0),onPress:function(){const s=c("showDurationInTooltip",!0);I("showDurationInTooltip",!s),e()}}))))}function bt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(s){return s+1},0),[t,n]=r.React.useState(""),a=function(){if(!t.trim()){T.showToast("Please enter an app name",v.getAssetIDByName("Small"));return}const s=c("ignoreList",[]);s.includes(t.trim())?T.showToast("App already in ignore list",v.getAssetIDByName("Warning")):(I("ignoreList",[...s,t.trim()]),n(""),e(),T.showToast("App added to ignore list",v.getAssetIDByName("Check")))},i=function(s){const o=c("ignoreList",[]);I("ignoreList",o.filter(function(u){return u!==s})),e(),T.showToast("App removed from ignore list",v.getAssetIDByName("Check"))};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Add App to Ignore"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Enter app name",value:t,onChange:n,isClearable:!0,onSubmitEditing:a,returnKeyType:"done"}))),r.React.createElement(E,null,r.React.createElement(g,{label:"Add to Ignore List",subLabel:"Add the current app name to your ignore list",trailing:r.React.createElement(g.Arrow,null),onPress:a})),c("ignoreList",[]).length>0&&r.React.createElement(E,{title:"Ignored Apps"},c("ignoreList",[]).map(function(s,o){return r.React.createElement(g,{key:o,label:s,trailing:r.React.createElement(r.ReactNative.TouchableOpacity,{onPress:function(){return i(s)},style:{padding:8,backgroundColor:"#ff4d4d",borderRadius:12,width:24,height:24,justifyContent:"center",alignItems:"center"}},r.React.createElement(r.ReactNative.Image,{source:v.getAssetIDByName("TrashIcon"),style:{width:14,height:14,tintColor:"#ffffff"}}))})})),r.React.createElement(E,{title:"About Ignore List"},r.React.createElement(g,{label:"How it Works",subLabel:"When any app in your ignore list is active, your music status will be hidden"}),r.React.createElement(g,{label:"Detection",subLabel:"Apps are detected by their Discord activity name"}),r.React.createElement(g,{label:"Examples",subLabel:"Spotify, YouTube Music, Kizzy, Metrolist, echo"}))))}function ht(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Logging Options"},r.React.createElement(Ee,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:c("verboseLogging",b.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(t){I("verboseLogging",t),e()}})),r.React.createElement(E,{title:"Debug Information"},r.React.createElement(g,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),r.React.createElement(g,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),r.React.createElement(E,{title:"Log Information"},r.React.createElement(g,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),r.React.createElement(g,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),r.React.createElement(g,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))))}var Te,Ie,ke,Ae,Le,Ce,Pe,Ne,$e,Ue,De,_e,Me,xe,ze,Fe,Ve;(Te=p.plugin.storage).username??(Te.username=""),(Ie=p.plugin.storage).apiKey??(Ie.apiKey=""),(ke=p.plugin.storage).appName??(ke.appName="Music"),(Ae=p.plugin.storage).timeInterval??(Ae.timeInterval=5),(Le=p.plugin.storage).showTimestamp??(Le.showTimestamp=!0),(Ce=p.plugin.storage).listeningTo??(Ce.listeningTo=!0),(Pe=p.plugin.storage).verboseLogging??(Pe.verboseLogging=!1),(Ne=p.plugin.storage).service??(Ne.service="lastfm"),($e=p.plugin.storage).librefmUsername??($e.librefmUsername=""),(Ue=p.plugin.storage).librefmApiKey??(Ue.librefmApiKey=""),(De=p.plugin.storage).listenbrainzUsername??(De.listenbrainzUsername=""),(_e=p.plugin.storage).listenbrainzToken??(_e.listenbrainzToken=""),(Me=p.plugin.storage).addToSidebar??(Me.addToSidebar=!0),(xe=p.plugin.storage).showLargeText??(xe.showLargeText=!0),(ze=p.plugin.storage).ignoreList??(ze.ignoreList=[]),(Fe=p.plugin.storage).showAlbumInTooltip??(Fe.showAlbumInTooltip=!0),(Ve=p.plugin.storage).showDurationInTooltip??(Ve.showDurationInTooltip=!0);const c=function(e,t){return p.plugin.storage[e]??t},I=function(e,t){return p.plugin.storage[e]=t},W=(function(){function e(){B(this,e)}return O(e,null,[{key:"getServiceDisplayName",value:function(t){switch(t){case"lastfm":return"Last.fm";case"librefm":return"Libre.fm";case"listenbrainz":return"ListenBrainz";default:return"Unknown"}}},{key:"testService",value:async function(t){try{switch(t){case"lastfm":return await this.testLastFmConnection();case"librefm":return await this.testLibreFmConnection();case"listenbrainz":return await this.testListenBrainzConnection();default:return!1}}catch(n){return console.error(`Error testing ${t}:`,n),!1}}},{key:"testLastFmConnection",value:async function(){const t=c("username"),n=c("apiKey");if(!t||!n)return!1;try{return!(await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=${t}&api_key=${n}&format=json`)).json()).error}catch(a){return console.error("Last.fm connection test failed:",a),!1}}},{key:"testLibreFmConnection",value:async function(){const t=c("librefmUsername"),n=c("librefmApiKey");if(!t||!n)return!1;try{return!(await(await fetch(`https://libre.fm/2.0/?method=user.getinfo&user=${t}&api_key=${n}&format=json`)).json()).error}catch(a){return console.error("Libre.fm connection test failed:",a),!1}}},{key:"testListenBrainzConnection",value:async function(){const t=c("listenbrainzUsername"),n=c("listenbrainzToken");if(!t)return!1;try{const a={"Content-Type":"application/json"};return n&&(a.Authorization=`Token ${n}`),(await fetch(`https://api.listenbrainz.org/1/user/${t}/listen-count`,{headers:a})).status===200}catch(a){return console.error("ListenBrainz connection test failed:",a),!1}}}]),e})();function Be(){D.useProxy(p.plugin.storage);const e=r.NavigationNative.useNavigation(),[,t]=r.React.useReducer(function(i){return i+1},0),n=c("service"),a=function(i){switch(i){case"lastfm":return c("username")&&c("apiKey")?"\u2705 Configured":"\u274C Missing credentials";case"librefm":return c("librefmUsername")&&c("librefmApiKey")?"\u2705 Configured":"\u274C Missing credentials";case"listenbrainz":return c("listenbrainzUsername")?"\u2705 Configured":"\u274C Missing username";default:return"\u2753 Unknown"}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Active Service"},r.React.createElement(g,{label:"Current Service",subLabel:n?`Using: ${W.getServiceDisplayName(n)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(i){return r.React.createElement(q,{key:i,label:W.getServiceDisplayName(i),subLabel:a(i),checked:n===i,onPress:function(){i!==n&&(I("service",i),t())}})})),r.React.createElement(E,{title:"Service Configuration"},r.React.createElement(g,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:ct})}}),r.React.createElement(g,{label:"Libre.fm Settings",subLabel:"Configure Libre.fm credentials and options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:ut})}}),r.React.createElement(g,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:gt})}})),r.React.createElement(E,{title:"Plugin Configuration"},r.React.createElement(g,{label:"Display Settings",subLabel:"Customize app name and update interval",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:ft})}}),r.React.createElement(g,{label:"RPC Customization",subLabel:"Customize Discord rich presence display options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"RPC Customization",render:pt})}}),r.React.createElement(g,{label:"Ignore List",subLabel:"Configure apps that should hide your status",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Ignore List Settings",render:bt})}}),r.React.createElement(g,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:ht})}}),r.React.createElement(Ee,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:c("addToSidebar",!1),onValueChange:function(i){I("addToSidebar",i),t()}})),r.React.createElement(E,{title:"About"},r.React.createElement(g,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),r.React.createElement(g,{label:"Author",subLabel:"kmmiio99o"}),r.React.createElement(g,{label:"Version",subLabel:"1.3.2"}))))}const{FormSection:yt,FormRow:ge}=He.Forms,{TableRowIcon:mt}=x.findByProps("TableRowIcon"),X=window.bunny,Oe=X?.metro?.findByPropsLazy("getRootNavigationRef"),fe=X?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),Ke=X?.metro?.findByPropsLazy("createList"),je=X?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function wt({tabs:e}){const t=r.NavigationNative.useNavigation();return r.React.createElement(ge,{label:e.title(),leading:r.React.createElement(ge.Icon,{source:e.icon}),trailing:r.React.createElement(r.React.Fragment,{},[e.trailing?e.trailing():null,r.React.createElement(ge.Arrow,{key:"arrow"})]),onPress:function(){const n=e.page;t.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(n)}})}})}function vt(e,t){try{t.push(Q.after("default",X?.metro?.findByPropsLazy(["renderTitle","sections"],!1),function(n,a){const i=ne.findInReactTree(a.props.children,function(s){return s.type?.name==="UserSettingsOverview"});i&&t.push(Q.after("render",i.type.prototype,function(s,o){const u=ne.findInReactTree(o.props.children,function(f){return f?.children?.[1]?.type===yt})?.children;if(u){const f=u.findIndex(function(h){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(h?.props?.label)});u.splice(-~f||4,0,r.React.createElement(wt,{key:e.key,tabs:e}))}}))},!0))}catch(n){p.logger.info("Panel UI patch failed graciously \u{1F494}",n)}}function Rt(e,t){if(!fe||!Oe){console.warn("[LastFm] Missing required constants for tabs UI patch");return}const n={[e.key]:{type:"pressable",title:e.title,icon:e.icon,IconComponent:e.icon&&function(){return r.React.createElement(mt,{source:e.icon})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=Oe.getRootNavigationRef(),o=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(o)}})},withArrow:!0}};let a=fe.SETTING_RENDERER_CONFIG;Object.defineProperty(fe,"SETTING_RENDERER_CONFIG",{enumerable:!0,configurable:!0,get:function(){return{...a,...n}},set:function(s){return a=s}});const i=Symbol("pinToSettings meow meow");try{if(!Ke)return;t.push(Q.after("createList",Ke,function(s,o){if(!s[0][i]){s[0][i]=!0;const[u]=s,f=u.sections?.find(function(h){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(k){return h.label===k&&h.title===k})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}catch{if(!je)return;t.push(Q.after("default",je,function(s,o){if(!s[0][i]){s[0][i]=!0;const{sections:u}=ne.findInReactTree(o,function(h){return h.props?.sections}).props,f=u?.find(function(h){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(k){return h.label===k&&h.title===k})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}}function St(e){const t=[];let n=!1;const a=e.predicate||function(){return!0};return e.predicate=function(){return n?!1:a()},vt(e,t),Rt(e,t),t.push(function(){return n=!0}),function(){for(const i of t)i()}}function Ge(){if(!p.plugin.storage.addToSidebar)return console.log("[Multi Scrobbler] Sidebar disabled in settings"),function(){};console.log("[Multi Scrobbler] Patching sidebar using custom patchSettingsPin...");try{const e=St({key:"LastFmScrobbler",icon:v.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return p.plugin.storage.addToSidebar===!0},page:Be});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var qe;const S={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0};let V;const Ye=b.DEFAULT_SETTINGS;Object.keys(Ye).forEach(function(e){p.plugin.storage[e]=p.plugin.storage[e]??Ye[e]}),(qe=p.plugin.storage).addToSidebar??(qe.addToSidebar=!1);const l=new Proxy(p.plugin.storage,{get(e,t){return e[t]},set(e,t,n){return e[t]=n,!0}});let te=0;const Et=3,Tt=5e3;async function H(){try{await ot(),te=0,console.log("[Multi-Scrobbler] Successfully connected")}catch(e){console.error("[Multi-Scrobbler] Initialization error:",e),te++,te10&&(S.serviceErrors[e]=S.serviceErrors[e].slice(-10)),ye(`${e} error:`,t)}function we(){S.lastSuccessfulUpdate=new Date().toISOString(),J("Successful update recorded at:",S.lastSuccessfulUpdate)}function ee(e){return ee=Object.setPrototypeOf?Object.getPrototypeOf:function(n){return n.__proto__||Object.getPrototypeOf(n)},ee(e)}function ve(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(ve=function(){return!!e})()}function et(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function tt(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function rt(e,t){return t&&(tt(t)==="object"||typeof t=="function")?t:et(e)}function ie(e,t,n){return t=ee(t),rt(e,ve()?Reflect.construct(t,n||[],ee(e).constructor):t.apply(e,n))}function se(e,t){return se=Object.setPrototypeOf||function(a,i){return a.__proto__=i,a},se(e,t)}function oe(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&se(e,t)}let le=(function(){function e(){B(this,e)}return O(e,[{key:"log",value:function(...t){console.log(`[${this.getServiceName()}]`,...t)}},{key:"logError",value:function(...t){console.error(`[${this.getServiceName()}] Error:`,...t)}},{key:"logVerbose",value:function(...t){console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"handleError",value:async function(t){this.lastError=t.error||0;const n=this.getErrorMessage(t);throw this.logError(n),Z(this.getServiceName().toLowerCase(),n),new Error(`${this.getServiceName()} API Error: ${n}`)}},{key:"getErrorMessage",value:function(t){return t.error&&b.API_ERROR_CODES[t.error]?b.API_ERROR_CODES[t.error]:t.message||t.toString()||"Unknown error"}},{key:"makeRequest",value:async function(t,n={}){try{this.logVerbose(`Making request to: ${t}`),me();const a=await fetch(t,{...n,headers:{"User-Agent":"Vendetta Multi-Service Scrobbler/3.0.0",...n.headers}});if(!a.ok){const s=new Error(`HTTP ${a.status}: ${a.statusText}`);throw Z(this.getServiceName().toLowerCase(),s.message),s}const i=await a.json();return i.error&&await this.handleError(i),this.retryCount=0,i}catch(a){if(this.retryCount++,this.retryCount>b.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,Z(this.getServiceName().toLowerCase(),`Max retries exceeded: ${a.message}`),a;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${b.MAX_RETRY_ATTEMPTS})`),await new Promise(function(i){return setTimeout(i,b.RETRY_DELAY)}),this.makeRequest(t,n)}}},{key:"isDefaultCover",value:function(t){return t?b.DEFAULT_COVER_HASHES.some(function(n){return t.includes(n)}):!0}},{key:"processAlbumArt",value:function(t){return!t||this.isDefaultCover(t)?null:t}},{key:"getLastError",value:function(){return this.lastError}},{key:"resetRetryCount",value:function(){this.retryCount=0}}]),e})(),nt=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{if(!l.username||!l.apiKey)throw new Error("Username or API key not set");const n=new URLSearchParams({method:"user.getinfo",user:l.username,api_key:l.apiKey,format:"json"}),a=`${b.SERVICES.lastfm.baseUrl}?${n}`;return await this.makeRequest(a),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{if(!l.username||!l.apiKey)throw new Error("Username or API key not set");this.logVerbose("Fetching latest scrobble for user:",l.username);const n=new URLSearchParams({method:"user.getrecenttracks",user:l.username,api_key:l.apiKey,limit:"1",extended:"1",format:"json"}),a=`${b.SERVICES.lastfm.baseUrl}?${n}`,i=(await this.makeRequest(a))?.recenttracks?.track?.[0];if(!i)throw new Error("No tracks found");this.logVerbose("Raw track data:",i);const s=!!i["@attr"]?.nowplaying,o=i.date?.uts?parseInt(i.date.uts):Math.floor(Date.now()/1e3);let u=null,f=null;if(s)try{const P=new URLSearchParams({method:"track.getInfo",track:i.name,artist:i.artist.name,api_key:l.apiKey,format:"json"}),$=`${b.SERVICES.lastfm.baseUrl}?${P}`,y=await this.makeRequest($);y?.track?.duration&&(u=parseInt(y.track.duration),u>0&&(u=Math.floor(u/1e3),f=o+u))}catch(P){this.logVerbose("Failed to fetch track duration:",P)}const h=this.processAlbumArt(i.image?.find(function(P){return P.size==="large"})?.["#text"]),k={name:i.name,artist:i.artist.name,album:i.album["#text"],albumArt:h,url:i.url,date:i.date?.["#text"]??"now",nowPlaying:s,loved:i.loved==="1",from:o,to:f,duration:u};return this.logVerbose("Processed track:",k),this.log(`${s?"Now playing":"Last played"}:`,`${k.artist} - ${k.name}`),k}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(le),at=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=l.librefmUsername,a=l.librefmApiKey;if(!n||!a)throw new Error("Username or API key not set for Libre.fm");const i=new URLSearchParams({method:"user.getinfo",user:n,api_key:a,format:"json"}),s=`${b.SERVICES.librefm.baseUrl}?${i}`;return await this.makeRequest(s),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=l.librefmUsername,a=l.librefmApiKey;if(!n||!a)throw new Error("Username or API key not set for Libre.fm");this.logVerbose("Fetching latest scrobble for user:",n);const i=new URLSearchParams({method:"user.getrecenttracks",user:n,api_key:a,limit:"1",extended:"1",format:"json"}),s=`${b.SERVICES.librefm.baseUrl}?${i}`,o=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!o)throw new Error("No tracks found");this.logVerbose("Raw track data:",o);const u=!!o["@attr"]?.nowplaying,f=o.date?.uts?parseInt(o.date.uts):Math.floor(Date.now()/1e3);let h=null,k=null;if(u)try{const y=new URLSearchParams({method:"track.getInfo",track:o.name,artist:o.artist.name,api_key:a,format:"json"}),M=`${b.SERVICES.librefm.baseUrl}?${y}`,L=await this.makeRequest(M);L?.track?.duration&&(h=parseInt(L.track.duration),h>0&&(h=Math.floor(h/1e3),k=f+h))}catch(y){this.logVerbose("Failed to fetch track duration:",y)}const P=this.processAlbumArt(o.image?.find(function(y){return y.size==="large"})?.["#text"]),$={name:o.name,artist:o.artist.name,album:o.album["#text"],albumArt:P,url:o.url,date:o.date?.["#text"]??"now",nowPlaying:u,loved:o.loved==="1",from:f,to:k,duration:h};return this.logVerbose("Processed track:",$),this.log(`${u?"Now playing":"Last played"}:`,`${$.artist} - ${$.name}`),$}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(le),it=(function(e){oe(t,e);function t(){return B(this,t),ie(this,t,arguments)}return O(t,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...n){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=l.listenbrainzUsername,a=l.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");const i=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,s={};return a&&(s.Authorization=`Token ${a}`),await this.makeRequest(i,{headers:s}),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=l.listenbrainzUsername,a=l.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");this.logVerbose("Fetching latest scrobble for user:",n);let i=null;const s=function(y){if(y){if(Array.isArray(y.listens))return y.listens;if(y.payload&&Array.isArray(y.payload.listens))return y.payload.listens;if(y.data&&Array.isArray(y.data.listens))return y.data.listens}};try{const y=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/playing-now`,M={};a&&(M.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:M}),U=s(L);U&&U.length>0&&(i=U[0],i.playing_now=!0)}catch(y){this.logVerbose("No currently playing track or failed to fetch:",y)}let o;if(i)o=i,this.logVerbose("Using currently playing track");else{const y=`${b.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,M={};a&&(M.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:M}),U=(function(){if(L){if(Array.isArray(L.listens))return L.listens;if(L.payload&&Array.isArray(L.payload.listens))return L.payload.listens;if(L.data&&Array.isArray(L.data.listens))return L.data.listens}})();if(!U||U.length===0)throw new Error("No listens found");o=U[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",o);const u=!!o.playing_now,f=o.listened_at||Math.floor(Date.now()/1e3);let h=null,k=null;o.track_metadata.additional_info?.duration_ms&&(h=Math.floor(o.track_metadata.additional_info.duration_ms/1e3),u&&h>0&&(k=f+h));const P=null;o.track_metadata.additional_info?.release_mbid;const $={name:o.track_metadata.track_name,artist:o.track_metadata.artist_name,album:o.track_metadata.release_name||"",albumArt:P,url:o.track_metadata.additional_info?.origin_url||`https://listenbrainz.org/user/${n}`,date:u?"now":new Date(f*1e3).toISOString(),nowPlaying:u,loved:!1,from:f,to:k,duration:h};return this.logVerbose("Processed track:",$),this.log(`${u?"Now playing":"Last played"}:`,`${$.artist} - ${$.name}`),$}catch(n){throw this.logError("Failed to fetch latest listen:",n),n}}},{key:"getErrorMessage",value:function(n){return n.error?n.error:n.message?n.message:n.toString()||"Unknown error"}}]),t})(le);const K=(function(){function e(){B(this,e),this.serviceInstances=new Map}return O(e,[{key:"getService",value:function(t){this.serviceInstances||(this.serviceInstances=new Map);const n=t||l.service;if(!n)throw new Error("[ServiceFactory] No service type specified and no default service configured");return this.serviceInstances.has(n)||this.serviceInstances.set(n,this.createService(n)),this.serviceInstances.get(n)}},{key:"getCurrentService",value:function(){return this.getService(l.service)}},{key:"createService",value:function(t){switch(t){case"lastfm":return new nt;case"librefm":return new at;case"listenbrainz":return new it;default:throw new Error(`[ServiceFactory] Unknown service type: ${t}`)}}},{key:"clearCache",value:function(){this.serviceInstances?this.serviceInstances.clear():this.serviceInstances=new Map}},{key:"validateCurrentService",value:function(){return this.getCurrentService().validateCredentials()}},{key:"testService",value:async function(t){try{return await this.getService(t).validateCredentials()}catch(n){return console.error(`[ServiceFactory] Failed to test ${t}:`,n),!1}}},{key:"getSupportedServices",value:function(){return["lastfm","librefm","listenbrainz"]}},{key:"getServiceDisplayName",value:function(t){return this.getService(t).getServiceName()}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance();function Se(e){if(!e||e<0)return"0:00";const t=Math.floor(e/3600),n=Math.floor(e%3600/60),a=Math.floor(e%60);return t>0?`${t}:${n.toString().padStart(2,"0")}:${a.toString().padStart(2,"0")}`:`${n}:${a.toString().padStart(2,"0")}`}function Re(){return Math.floor(Date.now()/1e3)}var st=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(st||{});const A=function(...e){return console.log("[Scrobble Plugin]",...e)},z=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},C=function(...e){return l.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ce=(function(){function e(){B(this,e)}return O(e,[{key:"updateActivity",value:async function(){if(R.pluginStopped){A("Plugin is stopped; skipping activity updates and clearing timers"),C("Plugin is stopped, skipping update");try{this.stopUpdates(),C("Update timers cleared due to stopped plugin")}catch(a){z("Error while stopping updates for stopped plugin:",a)}return}const t=K.getCurrentService().getServiceName();C(`Fetching latest track from ${t}...`);let n=!1;try{if(l.ignoreList&&l.ignoreList.length>0){const o=Je.findActivity(function(u){return u.name?l.ignoreList.some(function(f){return u.name.toLowerCase().includes(f.toLowerCase())}):!1});if(o){A(`Ignored app (${o.name}) is currently active; clearing activity and skipping updates`),C(`Ignored app (${o.name}) is currently playing, clearing activity`);try{G("ignoredActivity",o.name)}catch(u){C("Failed to set debug info for ignored activity:",u)}ae();return}}me();const a=await K.getCurrentService().fetchLatestScrobble();if(G("lastTrack",a),!a.nowPlaying){A("No currently playing track reported by service; clearing activity and skipping RPC update"),C("Track is not currently playing");try{G("lastTrack_nowPlaying",!1)}catch(o){C("Failed to set debug info for nowPlaying:",o)}ae();return}if(R.lastTrackUrl===a.url){A("Track unchanged; skipping Discord RPC update"),C("Track hasn't changed"),we(),this.consecutiveFailures=0;return}n=!0,A(`\u{1F3B5} Track changed: ${a.artist} - ${a.name}`);let i;if(a.nowPlaying&&l.showTimestamp&&a.from){const o=Re();let u=a.from;if(u0){const f=Math.min(a.duration*.1,30);u=o-f}else u=o;C("had to estimate start time")}i={start:u*1e3},a.to&&(i.end=a.to*1e3)}C(`\u{1F3AF} Preparing RPC update for: ${a.artist} - ${a.name}`);const s={name:l.appName||b.DEFAULT_APP_NAME,flags:0,type:l.listeningTo?2:0,details:a.name,state:`${a.artist}`,status_display_type:1,application_id:b.APPLICATION_ID};if(s.name.includes("{{")){const o={artist:a.artist,name:a.name,album:a.album,service:t};for(const[u,f]of Object.entries(o))s.name=s.name.replace(new RegExp(`{{${u}}}`,"g"),f||"")}if(i&&(s.timestamps=i,C("Timestamps set:",{start:new Date(i.start).toISOString(),end:i.end?new Date(i.end).toISOString():"none",duration:a.duration?Se(a.duration):"unknown"})),a.album||a.albumArt){const o=a.albumArt?[a.albumArt]:[];let u=(await Ze(o))[0];if(u){if(s.assets={large_image:u},l.showLargeText){let f="";if(l.showAlbumInTooltip&&a.album&&(f+=`on ${a.album}`),l.showDurationInTooltip&&a.duration){const h=Se(a.duration);f?f+=` \u2022 ${h}`:f=h}f&&(s.assets.large_text=f)}C("Album art set:",u),s.assets.large_text&&C("Tooltip text set:",s.assets.large_text)}else a.album&&l.showLargeText&&l.showAlbumInTooltip&&(s.assets={large_text:`on ${a.album}`})}C("Setting Discord activity:",s),G("lastActivity",s),await he(s),R.lastTrackUrl=a.url,this.currentActivity=s,R.lastActivity=s,this.consecutiveFailures=0,this.lastUpdateTime=Re(),we(),A(`\u2705 RPC updated successfully: ${a.artist} - ${a.name}`)}catch(a){z("Update failed:",a);try{Z(l.service,a.message)}catch(i){z("Failed to record service error:",i)}try{G("lastUpdateError",{message:a.message,service:l.service,lastTrackUrl:R.lastTrackUrl})}catch(i){C("Failed to set debug info for last update error:",i)}this.handleError(a)}}},{key:"handleError",value:function(t){this.consecutiveFailures++,G("lastError",t),z(`Failure ${this.consecutiveFailures}/${b.MAX_RETRY_ATTEMPTS}:`,t.message),this.consecutiveFailures>=b.MAX_RETRY_ATTEMPTS&&(z("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var t=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),A("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){A("Attempting to reconnect..."),t.initialize().then(function(){A("Reconnection successful!"),t.stopReconnection()}).catch(function(n){z("Reconnection attempt failed:",n.message)})},b.RETRY_DELAY))}},{key:"stopReconnection",value:function(){try{this.reconnectTimer&&(clearInterval(this.reconnectTimer),this.reconnectTimer=void 0),this.isReconnecting=!1,this.consecutiveFailures=0}catch(t){console.error("[Scrobble Plugin] Reconnection cleanup error:",t)}}},{key:"stopUpdates",value:function(){try{this.updateTimer&&(clearInterval(this.updateTimer),this.updateTimer=void 0)}catch(t){console.error("[Scrobble Plugin] Timer cleanup error:",t)}}},{key:"initialize",value:async function(){var t=this;if(R.pluginStopped)throw new Error("Plugin is stopped");const n=K.getCurrentService().getServiceName();A(`Initializing with ${n}...`);try{if(!await K.validateCurrentService())throw new Error(`Invalid credentials for ${n}`);A(`${n} credentials validated successfully`)}catch(i){throw z(`Failed to validate ${n} credentials:`,i),i}this.stopUpdates(),A("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const a=Math.max((Number(l.timeInterval)||b.DEFAULT_SETTINGS.timeInterval)*1e3,b.MIN_UPDATE_INTERVAL*1e3);this.updateTimer=setInterval(function(){return t.updateActivity()},a),A(`Update timer started with interval: ${a}ms (${a/1e3}s)`)}},{key:"stop",value:function(){if(!R.pluginStopped){A("Stopping plugin..."),R.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),ae(),A("Plugin stopped successfully")}catch(t){console.error("[Scrobble Plugin] Stop error:",t)}}}},{key:"switchService",value:async function(t){if(R.pluginStopped)return;A(`Switching to ${t}...`);const n=!R.pluginStopped;this.stop();try{K.clearCache(),R.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,n&&(R.pluginStopped=!1,await this.initialize())}catch(a){z("Failed to switch service:",a)}}},{key:"getStatus",value:function(){const t=K.getCurrentService().getServiceName();return{running:!R.pluginStopped,service:t,consecutiveFailures:this.consecutiveFailures,isReconnecting:this.isReconnecting,lastTrackUrl:R.lastTrackUrl,updateInterval:this.updateTimer?"Active":"Inactive"}}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance(),ot=function(){return ce.initialize()},ue=function(){return ce.stop()},lt=function(e){return ce.switchService(e)},{ScrollView:F}=x.findByProps("ScrollView"),{TableRowGroup:E,TableSwitchRow:Ee,TableCheckboxRow:q,Stack:N,TableRow:g}=x.findByProps("TableSwitchRow","TableCheckboxRow","TableRowGroup","Stack","TableRow"),{TextInput:_}=x.findByProps("TextInput");function ct(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing Last.fm connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("lastfm")?T.showToast("\u2705 Last.fm connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C Last.fm connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Last.fm Username",value:c("username"),onChange:function(n){I("username",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"Last.fm API Key",value:c("apiKey"),onChange:function(n){I("apiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),T.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",v.getAssetIDByName("XIcon"))}}}))))}function ut(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing Libre.fm connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("librefm")?T.showToast("\u2705 Libre.fm connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C Libre.fm connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Libre.fm Username",value:c("librefmUsername"),onChange:function(n){I("librefmUsername",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"Libre.fm API Key",value:c("librefmApiKey"),onChange:function(n){I("librefmApiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),T.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",v.getAssetIDByName("XIcon"))}}}))))}function gt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){T.showToast("Testing ListenBrainz connection...",v.getAssetIDByName("ClockIcon"));try{await W.testService("listenbrainz")?T.showToast("\u2705 ListenBrainz connection successful!",v.getAssetIDByName("CheckIcon")):T.showToast("\u274C ListenBrainz connection failed",v.getAssetIDByName("XIcon"))}catch{T.showToast("\u274C Connection error",v.getAssetIDByName("XIcon"))}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"ListenBrainz Username",value:c("listenbrainzUsername"),onChange:function(n){I("listenbrainzUsername",n),e()},isClearable:!0}),r.React.createElement(_,{placeholder:"ListenBrainz Token",value:c("listenbrainzToken"),onChange:function(n){I("listenbrainzToken",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(g,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:r.React.createElement(g.Arrow,null),onPress:t}),r.React.createElement(g,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:r.React.createElement(g.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://listenbrainz.org/settings/")}catch(n){console.error("Failed to open ListenBrainz settings URL:",n),T.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",v.getAssetIDByName("XIcon"))}}}))))}function ft(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Activity Display"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:`App Name (Default: ${b.DEFAULT_SETTINGS.appName})`,value:c("appName",b.DEFAULT_SETTINGS.appName),onChange:function(t){I("appName",t),e()},isClearable:!0}),r.React.createElement(_,{placeholder:`Update Interval (Default: ${b.DEFAULT_SETTINGS.timeInterval}s)`,value:String(c("timeInterval",b.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const n=Number(t);n>=b.MIN_UPDATE_INTERVAL&&(I("timeInterval",n),e())},keyboardType:"numeric",isClearable:!0}))),r.React.createElement(E,{title:"About Display Settings"},r.React.createElement(g,{label:"App Name",subLabel:"The name shown in Discord for your activity"}),r.React.createElement(g,{label:"Update Interval",subLabel:"How often the plugin checks for new tracks (in seconds)"}),r.React.createElement(g,{label:"Minimum Interval",subLabel:`The plugin will never check more frequently than ${b.MIN_UPDATE_INTERVAL} seconds`}))))}function dt(){D.useProxy(p.plugin.storage);const[e,t]=r.React.useState(null),[n,a]=r.React.useState(!0),[i,s]=r.React.useState(0),[o,u]=r.React.useState(!1),f={name:"Bohemian Rhapsody",artist:"Queen",artists:["Queen"],album:"A Night at the Opera",image:null,nowPlaying:!0,duration:354,startTime:Math.floor(Date.now()/1e3)-120},h=function(d){return d?Array.isArray(d)?d.filter(function(m){return m&&typeof m=="string"}):d["#text"]?d["#text"].split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):typeof d=="string"?d.split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):["Unknown Artist"]:["Unknown Artist"]},k=function(d){return!d||d.length===0?"Unknown Artist":d.length===1?d[0]:d.length===2?`${d[0]} & ${d[1]}`:`${d.slice(0,-1).join(", ")} & ${d[d.length-1]}`};r.React.useEffect(function(){(async function(){if(!c("username")||!c("apiKey")){t(f),u(!0),a(!1);return}try{a(!0);const d=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${c("username")}&api_key=${c("apiKey")}&format=json&limit=1`)).json();if(d.recenttracks&&d.recenttracks.track&&d.recenttracks.track.length>0){const m=d.recenttracks.track[0],j=h(m.artist);let Xe=180;try{const At=j[0]||"Unknown Artist",de=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=${c("apiKey")}&artist=${encodeURIComponent(At)}&track=${encodeURIComponent(m.name)}&format=json&username=${c("username")}`)).json();de.track&&de.track.duration&&(Xe=Math.floor(de.track.duration/1e3))}catch{console.log("Could not fetch track duration, using default")}t({name:m.name||"Unknown Track",artist:k(j),artists:j,album:m.album?.["#text"]||"Unknown Album",image:m.image?.[2]?.["#text"]||m.image?.[1]?.["#text"]||null,nowPlaying:m["@attr"]?.nowplaying==="true",duration:Xe,startTime:m["@attr"]?.nowplaying==="true"?Math.floor(Date.now()/1e3)-60:null}),u(!1)}else t(f),u(!0)}catch(d){console.error("Failed to fetch preview data, using fallback:",d),t(f),u(!0)}finally{a(!1)}})()},[c("username"),c("apiKey")]),r.React.useEffect(function(){if(!e?.nowPlaying||!c("showTimestamp"))return;const d=setInterval(function(){if(e.startTime&&e.duration){const m=Math.floor(Date.now()/1e3)-e.startTime,j=Math.min(m/e.duration,1);s(j)}},1e3);return function(){return clearInterval(d)}},[e,c("showTimestamp")]);const P=function(d){const m=Math.floor(d/60),j=Math.floor(d%60);return`${m}:${j.toString().padStart(2,"0")}`},$=function(){let d="";if(c("showAlbumInTooltip")&&e?.album&&(d+=`on ${e.album}`),c("showDurationInTooltip")&&e?.duration){const m=` \u2022 ${P(e.duration)}`;d?d+=m:d=P(e.duration)}return d||"No tooltip text"},y=function(){return e?.duration?e.nowPlaying?{current:i*e.duration,total:e.duration,progress:i}:{current:e.duration*.3,total:e.duration,progress:.3}:{current:0,total:0,progress:0}},M=c("listeningTo")?"Listening to":"Playing",L=c("appName","Music");if(n)return r.React.createElement(r.ReactNative.View,{style:w.container},r.React.createElement(r.ReactNative.View,{style:w.loadingContent},r.React.createElement(r.ReactNative.View,{style:w.loadingSpinner}),r.React.createElement(r.ReactNative.Text,{style:w.loadingText},"Loading preview...")));if(!e)return r.React.createElement(r.ReactNative.View,{style:w.container},r.React.createElement(r.ReactNative.Text,{style:w.centeredText},"Unable to load preview"));const U=y(),We=$();return r.React.createElement(r.ReactNative.View,{style:w.previewContainer},r.React.createElement(r.ReactNative.View,{style:w.header},r.React.createElement(r.ReactNative.Text,{style:w.activityType},M," ",L),r.React.createElement(r.ReactNative.Text,{style:w.rpcPreviewText,numberOfLines:1},"RPC Preview")),r.React.createElement(r.ReactNative.View,{style:w.content},r.React.createElement(r.ReactNative.View,{style:w.albumArt},e.image?r.React.createElement(r.ReactNative.Image,{source:{uri:e.image},style:w.albumImage,resizeMode:"cover"}):r.React.createElement(r.ReactNative.Text,{style:w.musicIcon},"\u{1F3B5}")),r.React.createElement(r.ReactNative.View,{style:w.trackInfo},r.React.createElement(r.ReactNative.Text,{style:w.trackName,numberOfLines:1},e.name),r.React.createElement(r.ReactNative.Text,{style:w.artistName,numberOfLines:1},e.artist),c("showLargeText")&&We!=="No tooltip text"&&r.React.createElement(r.ReactNative.Text,{style:w.tooltipText,numberOfLines:1},We),c("showTimestamp")&&e.duration&&r.React.createElement(r.ReactNative.View,{style:w.progressContainer},r.React.createElement(r.ReactNative.Text,{style:w.timeText},P(U.current)),r.React.createElement(r.ReactNative.View,{style:w.progressBar},r.React.createElement(r.ReactNative.View,{style:[w.progressFill,{width:`${U.progress*100}%`}]})),r.React.createElement(r.ReactNative.Text,{style:w.timeText},P(U.total))))))}const w=r.ReactNative.StyleSheet.create({container:{backgroundColor:"#1e1f22",borderRadius:12,padding:16,marginHorizontal:10,marginBottom:16,borderWidth:1,borderColor:"#3a3c41",alignItems:"center",justifyContent:"center",minHeight:120},loadingContent:{alignItems:"center",justifyContent:"center"},loadingSpinner:{width:20,height:20,borderRadius:10,borderWidth:2,borderColor:"#5865f2",borderTopColor:"transparent",marginBottom:8},previewContainer:{backgroundColor:"#1e1f22",borderRadius:12,padding:16,marginHorizontal:10,marginBottom:16,borderWidth:1,borderColor:"#3a3c41"},header:{flexDirection:"row",justifyContent:"space-between",alignItems:"center",marginBottom:12,gap:8},content:{flexDirection:"row",alignItems:"center"},albumArt:{width:80,height:80,backgroundColor:"#2b2d31",borderRadius:12,justifyContent:"center",alignItems:"center",marginRight:16,borderWidth:1,borderColor:"#40444b",overflow:"hidden",flexShrink:0},trackInfo:{flex:1,minWidth:0},progressContainer:{flexDirection:"row",alignItems:"center",gap:8,marginTop:6},progressBar:{flex:1,height:2,backgroundColor:"#2b2d31",borderRadius:3,overflow:"hidden"},progressFill:{height:2,backgroundColor:"#5865f2",borderRadius:3},loadingText:{color:"#949ba4",fontSize:14,fontWeight:"500"},centeredText:{color:"#949ba4",fontSize:14,fontWeight:"500",textAlign:"center"},activityType:{color:"#dbdee1",fontSize:14,fontWeight:"600",flex:1},rpcPreviewText:{color:"#80848e",fontSize:12,fontStyle:"italic",flexShrink:0},albumImage:{width:80,height:80,borderRadius:12},musicIcon:{color:"#80848e",fontSize:32},trackName:{color:"#ffffff",fontSize:16,fontWeight:"700",marginBottom:4},artistName:{color:"#b5bac1",fontSize:14,fontWeight:"500",marginBottom:4},tooltipText:{color:"#80848e",fontSize:12,fontStyle:"italic",marginBottom:6},timeText:{color:"#80848e",fontSize:11,fontWeight:"500",minWidth:35,textAlign:"center"}});function pt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(s){return s+1},0),t=c("listeningTo",b.DEFAULT_SETTINGS.listeningTo),n=c("showTimestamp",b.DEFAULT_SETTINGS.showTimestamp),a=function(){const s=!t;I("listeningTo",s),!s&&n&&I("showTimestamp",!1),e()},i=function(){t&&(I("showTimestamp",!n),e())};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(dt,null),r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"RPC Display Options"},r.React.createElement(q,{label:"Show as Listening",subLabel:"Display as 'Listening to' instead of 'Playing'",checked:t,onPress:a}),r.React.createElement(q,{label:"Show Tooltip Text",subLabel:"Show album name and track duration in Discord activity tooltip",checked:c("showLargeText",!0),onPress:function(){const s=c("showLargeText",!0);I("showLargeText",!s),e()}}),!t&&r.React.createElement(g,{label:"Timestamp Unavailable",subLabel:"Enable 'Show as Listening' to use timestamp feature",disabled:!0,dimmed:!0}),r.React.createElement(q,{label:"Show Timestamp",subLabel:"Display track progress and duration",checked:n,onPress:i,disabled:!t}),r.React.createElement(q,{label:"Show Album in Tooltip",subLabel:"Include album name in the tooltip text",checked:c("showAlbumInTooltip",!0),onPress:function(){const s=c("showAlbumInTooltip",!0);I("showAlbumInTooltip",!s),e()}}),r.React.createElement(q,{label:"Show Duration in Tooltip",subLabel:"Include track duration in the tooltip text",checked:c("showDurationInTooltip",!0),onPress:function(){const s=c("showDurationInTooltip",!0);I("showDurationInTooltip",!s),e()}}))))}function bt(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(s){return s+1},0),[t,n]=r.React.useState(""),a=function(){if(!t.trim()){T.showToast("Please enter an app name",v.getAssetIDByName("Small"));return}const s=c("ignoreList",[]);s.includes(t.trim())?T.showToast("App already in ignore list",v.getAssetIDByName("Warning")):(I("ignoreList",[...s,t.trim()]),n(""),e(),T.showToast("App added to ignore list",v.getAssetIDByName("Check")))},i=function(s){const o=c("ignoreList",[]);I("ignoreList",o.filter(function(u){return u!==s})),e(),T.showToast("App removed from ignore list",v.getAssetIDByName("Check"))};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Add App to Ignore"},r.React.createElement(N,{spacing:4},r.React.createElement(_,{placeholder:"Enter app name",value:t,onChange:n,isClearable:!0,onSubmitEditing:a,returnKeyType:"done"}))),r.React.createElement(E,null,r.React.createElement(g,{label:"Add to Ignore List",subLabel:"Add the current app name to your ignore list",trailing:r.React.createElement(g.Arrow,null),onPress:a})),c("ignoreList",[]).length>0&&r.React.createElement(E,{title:"Ignored Apps"},c("ignoreList",[]).map(function(s,o){return r.React.createElement(g,{key:o,label:s,trailing:r.React.createElement(r.ReactNative.TouchableOpacity,{onPress:function(){return i(s)},style:{padding:8,backgroundColor:"#ff4d4d",borderRadius:12,width:24,height:24,justifyContent:"center",alignItems:"center"}},r.React.createElement(r.ReactNative.Image,{source:v.getAssetIDByName("TrashIcon"),style:{width:14,height:14,tintColor:"#ffffff"}}))})})),r.React.createElement(E,{title:"About Ignore List"},r.React.createElement(g,{label:"How it Works",subLabel:"When any app in your ignore list is active, your music status will be hidden"}),r.React.createElement(g,{label:"Detection",subLabel:"Apps are detected by their Discord activity name"}),r.React.createElement(g,{label:"Examples",subLabel:"Spotify, YouTube Music, Kizzy, Metrolist, echo"}))))}function ht(){D.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Logging Options"},r.React.createElement(Ee,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:c("verboseLogging",b.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(t){I("verboseLogging",t),e()}})),r.React.createElement(E,{title:"Debug Information"},r.React.createElement(g,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),r.React.createElement(g,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),r.React.createElement(E,{title:"Log Information"},r.React.createElement(g,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),r.React.createElement(g,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),r.React.createElement(g,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))))}var Te,Ie,ke,Ae,Le,Ce,Pe,Ne,$e,Ue,De,_e,Me,xe,ze,Fe,Ve;(Te=p.plugin.storage).username??(Te.username=""),(Ie=p.plugin.storage).apiKey??(Ie.apiKey=""),(ke=p.plugin.storage).appName??(ke.appName="Music"),(Ae=p.plugin.storage).timeInterval??(Ae.timeInterval=5),(Le=p.plugin.storage).showTimestamp??(Le.showTimestamp=!0),(Ce=p.plugin.storage).listeningTo??(Ce.listeningTo=!0),(Pe=p.plugin.storage).verboseLogging??(Pe.verboseLogging=!1),(Ne=p.plugin.storage).service??(Ne.service="lastfm"),($e=p.plugin.storage).librefmUsername??($e.librefmUsername=""),(Ue=p.plugin.storage).librefmApiKey??(Ue.librefmApiKey=""),(De=p.plugin.storage).listenbrainzUsername??(De.listenbrainzUsername=""),(_e=p.plugin.storage).listenbrainzToken??(_e.listenbrainzToken=""),(Me=p.plugin.storage).addToSidebar??(Me.addToSidebar=!0),(xe=p.plugin.storage).showLargeText??(xe.showLargeText=!0),(ze=p.plugin.storage).ignoreList??(ze.ignoreList=[]),(Fe=p.plugin.storage).showAlbumInTooltip??(Fe.showAlbumInTooltip=!0),(Ve=p.plugin.storage).showDurationInTooltip??(Ve.showDurationInTooltip=!0);const c=function(e,t){return p.plugin.storage[e]??t},I=function(e,t){return p.plugin.storage[e]=t},W=(function(){function e(){B(this,e)}return O(e,null,[{key:"getServiceDisplayName",value:function(t){switch(t){case"lastfm":return"Last.fm";case"librefm":return"Libre.fm";case"listenbrainz":return"ListenBrainz";default:return"Unknown"}}},{key:"testService",value:async function(t){try{switch(t){case"lastfm":return await this.testLastFmConnection();case"librefm":return await this.testLibreFmConnection();case"listenbrainz":return await this.testListenBrainzConnection();default:return!1}}catch(n){return console.error(`Error testing ${t}:`,n),!1}}},{key:"testLastFmConnection",value:async function(){const t=c("username"),n=c("apiKey");if(!t||!n)return!1;try{return!(await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=${t}&api_key=${n}&format=json`)).json()).error}catch(a){return console.error("Last.fm connection test failed:",a),!1}}},{key:"testLibreFmConnection",value:async function(){const t=c("librefmUsername"),n=c("librefmApiKey");if(!t||!n)return!1;try{return!(await(await fetch(`https://libre.fm/2.0/?method=user.getinfo&user=${t}&api_key=${n}&format=json`)).json()).error}catch(a){return console.error("Libre.fm connection test failed:",a),!1}}},{key:"testListenBrainzConnection",value:async function(){const t=c("listenbrainzUsername"),n=c("listenbrainzToken");if(!t)return!1;try{const a={"Content-Type":"application/json"};return n&&(a.Authorization=`Token ${n}`),(await fetch(`https://api.listenbrainz.org/1/user/${t}/listen-count`,{headers:a})).status===200}catch(a){return console.error("ListenBrainz connection test failed:",a),!1}}}]),e})();function Be(){D.useProxy(p.plugin.storage);const e=r.NavigationNative.useNavigation(),[,t]=r.React.useReducer(function(i){return i+1},0),n=c("service"),a=function(i){switch(i){case"lastfm":return c("username")&&c("apiKey")?"\u2705 Configured":"\u274C Missing credentials";case"librefm":return"\u274C Service temporarily disabled";case"listenbrainz":return c("listenbrainzUsername")?"\u2705 Configured":"\u274C Missing username";default:return"\u2753 Unknown"}};return r.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(N,{spacing:8},r.React.createElement(E,{title:"Active Service"},r.React.createElement(g,{label:"Current Service",subLabel:n?`Using: ${W.getServiceDisplayName(n)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(i){return r.React.createElement(q,{key:i,label:W.getServiceDisplayName(i),subLabel:a(i),checked:n===i,onPress:function(){i!==n&&(I("service",i),t())},disabled:i==="librefm"})})),r.React.createElement(E,{title:"Service Configuration"},r.React.createElement(g,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:ct})}}),r.React.createElement(g,{label:"Libre.fm Settings",subLabel:"Disabled - not being able to test it out",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:ut})},disabled:!0}),r.React.createElement(g,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:gt})}})),r.React.createElement(E,{title:"Plugin Configuration"},r.React.createElement(g,{label:"Display Settings",subLabel:"Customize app name and update interval",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:ft})}}),r.React.createElement(g,{label:"RPC Customization",subLabel:"Customize Discord rich presence display options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"RPC Customization",render:pt})}}),r.React.createElement(g,{label:"Ignore List",subLabel:"Configure apps that should hide your status",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Ignore List Settings",render:bt})}}),r.React.createElement(g,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:r.React.createElement(g.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:ht})}}),r.React.createElement(Ee,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:c("addToSidebar",!1),onValueChange:function(i){I("addToSidebar",i),t()}})),r.React.createElement(E,{title:"About"},r.React.createElement(g,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),r.React.createElement(g,{label:"Author",subLabel:"kmmiio99o"}),r.React.createElement(g,{label:"Version",subLabel:"1.3.2"}))))}const{FormSection:yt,FormRow:ge}=He.Forms,{TableRowIcon:mt}=x.findByProps("TableRowIcon"),X=window.bunny,Oe=X?.metro?.findByPropsLazy("getRootNavigationRef"),fe=X?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),Ke=X?.metro?.findByPropsLazy("createList"),je=X?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function wt({tabs:e}){const t=r.NavigationNative.useNavigation();return r.React.createElement(ge,{label:e.title(),leading:r.React.createElement(ge.Icon,{source:e.icon}),trailing:r.React.createElement(r.React.Fragment,{},[e.trailing?e.trailing():null,r.React.createElement(ge.Arrow,{key:"arrow"})]),onPress:function(){const n=e.page;t.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(n)}})}})}function vt(e,t){try{t.push(Q.after("default",X?.metro?.findByPropsLazy(["renderTitle","sections"],!1),function(n,a){const i=ne.findInReactTree(a.props.children,function(s){return s.type?.name==="UserSettingsOverview"});i&&t.push(Q.after("render",i.type.prototype,function(s,o){const u=ne.findInReactTree(o.props.children,function(f){return f?.children?.[1]?.type===yt})?.children;if(u){const f=u.findIndex(function(h){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(h?.props?.label)});u.splice(-~f||4,0,r.React.createElement(wt,{key:e.key,tabs:e}))}}))},!0))}catch(n){p.logger.info("Panel UI patch failed graciously \u{1F494}",n)}}function St(e,t){if(!fe||!Oe){console.warn("[LastFm] Missing required constants for tabs UI patch");return}const n={[e.key]:{type:"pressable",title:e.title,icon:e.icon,IconComponent:e.icon&&function(){return r.React.createElement(mt,{source:e.icon})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=Oe.getRootNavigationRef(),o=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(o)}})},withArrow:!0}};let a=fe.SETTING_RENDERER_CONFIG;Object.defineProperty(fe,"SETTING_RENDERER_CONFIG",{enumerable:!0,configurable:!0,get:function(){return{...a,...n}},set:function(s){return a=s}});const i=Symbol("pinToSettings meow meow");try{if(!Ke)return;t.push(Q.after("createList",Ke,function(s,o){if(!s[0][i]){s[0][i]=!0;const[u]=s,f=u.sections?.find(function(h){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(k){return h.label===k&&h.title===k})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}catch{if(!je)return;t.push(Q.after("default",je,function(s,o){if(!s[0][i]){s[0][i]=!0;const{sections:u}=ne.findInReactTree(o,function(h){return h.props?.sections}).props,f=u?.find(function(h){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(k){return h.label===k&&h.title===k})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}}function Rt(e){const t=[];let n=!1;const a=e.predicate||function(){return!0};return e.predicate=function(){return n?!1:a()},vt(e,t),St(e,t),t.push(function(){return n=!0}),function(){for(const i of t)i()}}function Ge(){if(!p.plugin.storage.addToSidebar)return console.log("[Multi Scrobbler] Sidebar disabled in settings"),function(){};console.log("[Multi Scrobbler] Patching sidebar using custom patchSettingsPin...");try{const e=Rt({key:"LastFmScrobbler",icon:v.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return p.plugin.storage.addToSidebar===!0},page:Be});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var qe;const R={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0};let V;const Ye=b.DEFAULT_SETTINGS;Object.keys(Ye).forEach(function(e){p.plugin.storage[e]=p.plugin.storage[e]??Ye[e]}),(qe=p.plugin.storage).addToSidebar??(qe.addToSidebar=!1);const l=new Proxy(p.plugin.storage,{get(e,t){return e[t]},set(e,t,n){return e[t]=n,!0}});let te=0;const Et=3,Tt=5e3;async function H(){try{await ot(),te=0,console.log("[Multi-Scrobbler] Successfully connected")}catch(e){console.error("[Multi-Scrobbler] Initialization error:",e),te++,te