vd-plugins/multi-scrobbler/index.js
2026-01-09 20:24:33 +00:00

1 line
50 KiB
JavaScript

(function(Y,p,r,F,Dt,_,ne,I,w,J,rt,ae){"use strict";const h={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,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 O(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function ve(e,t){for(var n=0;n<t.length;n++){var a=t[n];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}function K(e,t,n){return t&&ve(e.prototype,t),n&&ve(e,n),e}const{SET_ACTIVITY:_t}=F.findByProps("SET_ACTIVITY"),nt=F.findByProps("getAssetIds"),at=F.findByStoreName("SelfPresenceStore"),we=F.findByStoreName("UserStore"),Re=function(...e){return console.log("[Activity]",...e)},it=function(...e){return console.error("[Activity] Error:",...e)},W=function(...e){try{console.log("[Activity] Verbose:",...e)}catch{console.log("[Activity] Verbose:",...e)}};function ie(){return Re("Clearing Discord activity"),Ee(null)}function Ee(e){T.pluginStopped&&(W("Plugin is stopped, clearing activity"),fe(),e=null),T.lastActivity=e,W("Dispatching activity update:",e?"Setting activity":"Clearing activity"),r.FluxDispatcher.dispatch({type:"LOCAL_ACTIVITY_UPDATE",activity:e,pid:2312,socketId:"Multi-Service-Scrobbler@Vendetta"}),Re(e?`Activity set: ${e.details} - ${e.state}`:"Activity cleared")}async function st(e,t=h.APPLICATION_ID){if(!e?.length)return W("No assets to fetch"),[];try{W(`Fetching ${e.length} asset(s):`,e);const n=await nt.fetchAssetIds(t,e);return W(`Successfully fetched ${n.length} asset(s)`),n}catch(n){return it("Failed to fetch assets:",n),[]}}const R={};R.componentMountErrors=[],R.componentMountCount=0,R.settingsLoadAttempts=0,R.serviceErrors={lastfm:[],librefm:[],listenbrainz:[]},R.apiCallCount=0,R.connectionAttempts=0,R.lastCredentialValidation={lastfm:!1,librefm:!1,listenbrainz:!1};const Z=function(...e){return console.log("[Debug]",...e)},Se=function(...e){return console.error("[Debug] Error:",...e)};function se(e,t){R[e]=t,e==="lastError"&&t?Se("Error recorded:",t.message):e==="lastTrack"&&t?Z("Track updated:",`${t.artist} - ${t.name}`):e==="currentService"&&t&&Z("Service changed to:",t)}function Te(){R.apiCallCount=(R.apiCallCount||0)+1,Z(`API call count: ${R.apiCallCount}`)}function ee(e,t){R.serviceErrors=R.serviceErrors||{lastfm:[],librefm:[],listenbrainz:[]},R.serviceErrors[e]=R.serviceErrors[e]||[],R.serviceErrors[e].push(`${new Date().toISOString()}: ${t}`),R.serviceErrors[e].length>10&&(R.serviceErrors[e]=R.serviceErrors[e].slice(-10)),Se(`${e} error:`,t)}function Ie(){R.lastSuccessfulUpdate=new Date().toISOString(),Z("Successful update recorded at:",R.lastSuccessfulUpdate)}function te(e){return te=Object.setPrototypeOf?Object.getPrototypeOf:function(n){return n.__proto__||Object.getPrototypeOf(n)},te(e)}function ke(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(ke=function(){return!!e})()}function ot(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function lt(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function ct(e,t){return t&&(lt(t)==="object"||typeof t=="function")?t:ot(e)}function oe(e,t,n){return t=te(t),ct(e,ke()?Reflect.construct(t,n||[],te(e).constructor):t.apply(e,n))}function le(e,t){return le=Object.setPrototypeOf||function(a,i){return a.__proto__=i,a},le(e,t)}function ce(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&&le(e,t)}let ue=(function(){function e(){O(this,e)}return K(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),ee(this.getServiceName().toLowerCase(),n),new Error(`${this.getServiceName()} API Error: ${n}`)}},{key:"getErrorMessage",value:function(t){return t.error&&h.API_ERROR_CODES[t.error]?h.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}`),Te();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 ee(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>h.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,ee(this.getServiceName().toLowerCase(),`Max retries exceeded: ${a.message}`),a;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${h.MAX_RETRY_ATTEMPTS})`),await new Promise(function(i){return setTimeout(i,h.RETRY_DELAY)}),this.makeRequest(t,n)}}},{key:"isDefaultCover",value:function(t){return t?h.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})(),ut=(function(e){ce(t,e);function t(){return O(this,t),oe(this,t,arguments)}return K(t,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{if(!o.username||!o.apiKey)throw new Error("Username or API key not set");const n=new URLSearchParams({method:"user.getinfo",user:o.username,api_key:o.apiKey,format:"json"}),a=`${h.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(!o.username||!o.apiKey)throw new Error("Username or API key not set");this.logVerbose("Fetching latest scrobble for user:",o.username);const n=new URLSearchParams({method:"user.getrecenttracks",user:o.username,api_key:o.apiKey,limit:"1",extended:"1",format:"json"}),a=`${h.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,l=i.date?.uts?parseInt(i.date.uts):Math.floor(Date.now()/1e3);let g=null,f=null;if(s)try{const C=new URLSearchParams({method:"track.getInfo",track:i.name,artist:i.artist.name,api_key:o.apiKey,format:"json"}),N=`${h.SERVICES.lastfm.baseUrl}?${C}`,y=await this.makeRequest(N);y?.track?.duration&&(g=parseInt(y.track.duration),g>0&&(g=Math.floor(g/1e3),f=l+g))}catch(C){this.logVerbose("Failed to fetch track duration:",C)}const b=this.processAlbumArt(i.image?.find(function(C){return C.size==="large"})?.["#text"]),A={name:i.name,artist:i.artist.name,album:i.album["#text"],albumArt:b,url:i.url,date:i.date?.["#text"]??"now",nowPlaying:s,loved:i.loved==="1",from:l,to:f,duration:g};return this.logVerbose("Processed track:",A),this.log(`${s?"Now playing":"Last played"}:`,`${A.artist} - ${A.name}`),A}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(ue),gt=(function(e){ce(t,e);function t(){return O(this,t),oe(this,t,arguments)}return K(t,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.librefmUsername,a=o.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=`${h.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=o.librefmUsername,a=o.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=`${h.SERVICES.librefm.baseUrl}?${i}`,l=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!l)throw new Error("No tracks found");this.logVerbose("Raw track data:",l);const g=!!l["@attr"]?.nowplaying,f=l.date?.uts?parseInt(l.date.uts):Math.floor(Date.now()/1e3);let b=null,A=null;if(g)try{const y=new URLSearchParams({method:"track.getInfo",track:l.name,artist:l.artist.name,api_key:a,format:"json"}),x=`${h.SERVICES.librefm.baseUrl}?${y}`,L=await this.makeRequest(x);L?.track?.duration&&(b=parseInt(L.track.duration),b>0&&(b=Math.floor(b/1e3),A=f+b))}catch(y){this.logVerbose("Failed to fetch track duration:",y)}const C=this.processAlbumArt(l.image?.find(function(y){return y.size==="large"})?.["#text"]),N={name:l.name,artist:l.artist.name,album:l.album["#text"],albumArt:C,url:l.url,date:l.date?.["#text"]??"now",nowPlaying:g,loved:l.loved==="1",from:f,to:A,duration:b};return this.logVerbose("Processed track:",N),this.log(`${g?"Now playing":"Last played"}:`,`${N.artist} - ${N.name}`),N}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(ue),ft=(function(e){ce(t,e);function t(){return O(this,t),oe(this,t,arguments)}return K(t,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.listenbrainzUsername,a=o.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");const i=`${h.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=o.listenbrainzUsername,a=o.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=`${h.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/playing-now`,x={};a&&(x.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:x}),D=s(L);D&&D.length>0&&(i=D[0],i.playing_now=!0)}catch(y){this.logVerbose("No currently playing track or failed to fetch:",y)}let l;if(i)l=i,this.logVerbose("Using currently playing track");else{const y=`${h.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,x={};a&&(x.Authorization=`Token ${a}`);const L=await this.makeRequest(y,{headers:x}),D=(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(!D||D.length===0)throw new Error("No listens found");l=D[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",l);const g=!!l.playing_now,f=l.listened_at||Math.floor(Date.now()/1e3);let b=null,A=null;l.track_metadata.additional_info?.duration_ms&&(b=Math.floor(l.track_metadata.additional_info.duration_ms/1e3),g&&b>0&&(A=f+b));const C=null;l.track_metadata.additional_info?.release_mbid;const N={name:l.track_metadata.track_name,artist:l.track_metadata.artist_name,album:l.track_metadata.release_name||"",albumArt:C,url:l.track_metadata.additional_info?.origin_url||`https://listenbrainz.org/user/${n}`,date:g?"now":new Date(f*1e3).toISOString(),nowPlaying:g,loved:!1,from:f,to:A,duration:b};return this.logVerbose("Processed track:",N),this.log(`${g?"Now playing":"Last played"}:`,`${N.artist} - ${N.name}`),N}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})(ue);const V=(function(){function e(){O(this,e),this.serviceInstances=new Map}return K(e,[{key:"getService",value:function(t){this.serviceInstances||(this.serviceInstances=new Map);const n=t||o.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(o.service)}},{key:"createService",value:function(t){switch(t){case"lastfm":return new ut;case"librefm":return new gt;case"listenbrainz":return new ft;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 Ae(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 Le(){return Math.floor(Date.now()/1e3)}var dt=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(dt||{});const $=function(...e){return console.log("[Scrobble Plugin]",...e)},G=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},U=function(...e){return o.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ge=(function(){function e(){O(this,e)}return K(e,[{key:"updateActivity",value:async function(){if(T.pluginStopped){U("Plugin is stopped, skipping update"),this.stopUpdates();return}const t=V.getCurrentService().getServiceName();U(`Fetching latest track from ${t}...`);let n=!1;try{if(o.ignoreList&&o.ignoreList.length>0){const l=at.findActivity(function(g){return g.name?o.ignoreList.some(function(f){return g.name.toLowerCase().includes(f.toLowerCase())}):!1});if(l){U(`Ignored app (${l.name}) is currently playing, clearing activity`),ie();return}}Te();const a=await V.getCurrentService().fetchLatestScrobble();if(se("lastTrack",a),!a.nowPlaying){U("Track is not currently playing"),ie();return}if(T.lastTrackUrl===a.url){U("Track hasn't changed"),Ie(),this.consecutiveFailures=0;return}n=!0,$(`\u{1F3B5} Track changed: ${a.artist} - ${a.name}`);let i;if(a.nowPlaying&&o.showTimestamp&&a.from){const l=Le();let g=a.from;if(g<l-3600){if(a.duration&&a.duration>0){const f=Math.min(a.duration*.1,30);g=l-f}else g=l;U("had to estimate start time")}i={start:g*1e3},a.to&&(i.end=a.to*1e3)}U(`\u{1F3AF} Preparing RPC update for: ${a.artist} - ${a.name}`);const s={name:o.appName||h.DEFAULT_APP_NAME,flags:0,type:o.listeningTo?2:0,details:a.name,state:`${a.artist}`,status_display_type:1,application_id:h.APPLICATION_ID};if(s.name.includes("{{")){const l={artist:a.artist,name:a.name,album:a.album,service:t};for(const[g,f]of Object.entries(l))s.name=s.name.replace(new RegExp(`{{${g}}}`,"g"),f||"")}if(i&&(s.timestamps=i,U("Timestamps set:",{start:new Date(i.start).toISOString(),end:i.end?new Date(i.end).toISOString():"none",duration:a.duration?Ae(a.duration):"unknown"})),a.album||a.albumArt){const l=a.albumArt?[a.albumArt]:[];let g=(await st(l))[0];if(g){if(s.assets={large_image:g},o.showLargeText){let f="";if(o.showAlbumInTooltip&&a.album&&(f+=`on ${a.album}`),o.showDurationInTooltip&&a.duration){const b=Ae(a.duration);f?f+=` \u2022 ${b}`:f=b}f&&(s.assets.large_text=f)}U("Album art set:",g),s.assets.large_text&&U("Tooltip text set:",s.assets.large_text)}else a.album&&o.showLargeText&&o.showAlbumInTooltip&&(s.assets={large_text:`on ${a.album}`})}U("Setting Discord activity:",s),se("lastActivity",s),await Ee(s),T.lastTrackUrl=a.url,this.currentActivity=s,T.lastActivity=s,this.consecutiveFailures=0,this.lastUpdateTime=Le(),Ie(),$(`\u2705 RPC updated successfully: ${a.artist} - ${a.name}`)}catch(a){G("Update failed:",a),ee(o.service,a.message),this.handleError(a)}}},{key:"handleError",value:function(t){this.consecutiveFailures++,se("lastError",t),G(`Failure ${this.consecutiveFailures}/${h.MAX_RETRY_ATTEMPTS}:`,t.message),this.consecutiveFailures>=h.MAX_RETRY_ATTEMPTS&&(G("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var t=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),$("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){$("Attempting to reconnect..."),t.initialize().then(function(){$("Reconnection successful!"),t.stopReconnection()}).catch(function(n){G("Reconnection attempt failed:",n.message)})},h.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(T.pluginStopped)throw new Error("Plugin is stopped");const n=V.getCurrentService().getServiceName();$(`Initializing with ${n}...`);try{if(!await V.validateCurrentService())throw new Error(`Invalid credentials for ${n}`);$(`${n} credentials validated successfully`)}catch(i){throw G(`Failed to validate ${n} credentials:`,i),i}this.stopUpdates(),$("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const a=Math.max((Number(o.timeInterval)||h.DEFAULT_SETTINGS.timeInterval)*1e3,h.MIN_UPDATE_INTERVAL*1e3);this.updateTimer=setInterval(function(){return t.updateActivity()},a),$(`Update timer started with interval: ${a}ms (${a/1e3}s)`)}},{key:"stop",value:function(){if(!T.pluginStopped){$("Stopping plugin..."),T.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),ie(),$("Plugin stopped successfully")}catch(t){console.error("[Scrobble Plugin] Stop error:",t)}}}},{key:"switchService",value:async function(t){if(T.pluginStopped)return;$(`Switching to ${t}...`);const n=!T.pluginStopped;this.stop();try{V.clearCache(),T.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,n&&(T.pluginStopped=!1,await this.initialize())}catch(a){G("Failed to switch service:",a)}}},{key:"getStatus",value:function(){const t=V.getCurrentService().getServiceName();return{running:!T.pluginStopped,service:t,consecutiveFailures:this.consecutiveFailures,isReconnecting:this.isReconnecting,lastTrackUrl:T.lastTrackUrl,updateInterval:this.updateTimer?"Active":"Inactive"}}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance(),pt=function(){return ge.initialize()},fe=function(){return ge.stop()},ht=function(e){return ge.switchService(e)},{ScrollView:M}=F.findByProps("ScrollView"),{TableRowGroup:S,TableSwitchRow:Ce,TableCheckboxRow:q,Stack:P,TableRow:u}=F.findByProps("TableSwitchRow","TableCheckboxRow","TableRowGroup","Stack","TableRow"),{TextInput:z}=F.findByProps("TextInput");function bt(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){I.showToast("Testing Last.fm connection...",w.getAssetIDByName("ClockIcon"));try{await X.testService("lastfm")?I.showToast("\u2705 Last.fm connection successful!",w.getAssetIDByName("CheckIcon")):I.showToast("\u274C Last.fm connection failed",w.getAssetIDByName("XIcon"))}catch{I.showToast("\u274C Connection error",w.getAssetIDByName("XIcon"))}};return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(z,{placeholder:"Last.fm Username",value:c("username"),onChange:function(n){k("username",n),e()},isClearable:!0}),r.React.createElement(z,{placeholder:"Last.fm API Key",value:c("apiKey"),onChange:function(n){k("apiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(S,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await ne.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),I.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",w.getAssetIDByName("XIcon"))}}}))))}function yt(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){I.showToast("Testing Libre.fm connection...",w.getAssetIDByName("ClockIcon"));try{await X.testService("librefm")?I.showToast("\u2705 Libre.fm connection successful!",w.getAssetIDByName("CheckIcon")):I.showToast("\u274C Libre.fm connection failed",w.getAssetIDByName("XIcon"))}catch{I.showToast("\u274C Connection error",w.getAssetIDByName("XIcon"))}};return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(z,{placeholder:"Libre.fm Username",value:c("librefmUsername"),onChange:function(n){k("librefmUsername",n),e()},isClearable:!0}),r.React.createElement(z,{placeholder:"Libre.fm API Key",value:c("librefmApiKey"),onChange:function(n){k("librefmApiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(S,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await ne.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),I.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",w.getAssetIDByName("XIcon"))}}}))))}function mt(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){I.showToast("Testing ListenBrainz connection...",w.getAssetIDByName("ClockIcon"));try{await X.testService("listenbrainz")?I.showToast("\u2705 ListenBrainz connection successful!",w.getAssetIDByName("CheckIcon")):I.showToast("\u274C ListenBrainz connection failed",w.getAssetIDByName("XIcon"))}catch{I.showToast("\u274C Connection error",w.getAssetIDByName("XIcon"))}};return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(z,{placeholder:"ListenBrainz Username",value:c("listenbrainzUsername"),onChange:function(n){k("listenbrainzUsername",n),e()},isClearable:!0}),r.React.createElement(z,{placeholder:"ListenBrainz Token (for private profiles)",value:c("listenbrainzToken"),onChange:function(n){k("listenbrainzToken",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(S,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await ne.Linking.openURL("https://listenbrainz.org/settings/")}catch(n){console.error("Failed to open ListenBrainz settings URL:",n),I.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",w.getAssetIDByName("XIcon"))}}}))))}function vt(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Activity Display"},r.React.createElement(P,{spacing:4},r.React.createElement(z,{placeholder:`App Name (Default: ${h.DEFAULT_SETTINGS.appName})`,value:c("appName",h.DEFAULT_SETTINGS.appName),onChange:function(t){k("appName",t),e()},isClearable:!0}),r.React.createElement(z,{placeholder:`Update Interval (Default: ${h.DEFAULT_SETTINGS.timeInterval}s)`,value:String(c("timeInterval",h.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const n=Number(t);n>=h.MIN_UPDATE_INTERVAL&&(k("timeInterval",n),e())},keyboardType:"numeric",isClearable:!0}))),r.React.createElement(S,{title:"About Display Settings"},r.React.createElement(u,{label:"App Name",subLabel:"The name shown in Discord for your activity"}),r.React.createElement(u,{label:"Update Interval",subLabel:"How often the plugin checks for new tracks (in seconds)"}),r.React.createElement(u,{label:"Minimum Interval",subLabel:`The plugin will never check more frequently than ${h.MIN_UPDATE_INTERVAL} seconds`}))))}function wt(){_.useProxy(p.plugin.storage);const[e,t]=r.React.useState(null),[n,a]=r.React.useState(!0),[i,s]=r.React.useState(0),[l,g]=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},b=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"]},A=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),g(!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=b(m.artist);let tt=180;try{const Ut=j[0]||"Unknown Artist",me=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=${c("apiKey")}&artist=${encodeURIComponent(Ut)}&track=${encodeURIComponent(m.name)}&format=json&username=${c("username")}`)).json();me.track&&me.track.duration&&(tt=Math.floor(me.track.duration/1e3))}catch{console.log("Could not fetch track duration, using default")}t({name:m.name||"Unknown Track",artist:A(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:tt,startTime:m["@attr"]?.nowplaying==="true"?Math.floor(Date.now()/1e3)-60:null}),g(!1)}else t(f),g(!0)}catch(d){console.error("Failed to fetch preview data, using fallback:",d),t(f),g(!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 C=function(d){const m=Math.floor(d/60),j=Math.floor(d%60);return`${m}:${j.toString().padStart(2,"0")}`},N=function(){let d="";if(c("showAlbumInTooltip")&&e?.album&&(d+=`on ${e.album}`),c("showDurationInTooltip")&&e?.duration){const m=` \u2022 ${C(e.duration)}`;d?d+=m:d=C(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}},x=c("listeningTo")?"Listening to":"Playing",L=c("appName","Music");if(n)return r.React.createElement(r.ReactNative.View,{style:v.container},r.React.createElement(r.ReactNative.View,{style:v.loadingContent},r.React.createElement(r.ReactNative.View,{style:v.loadingSpinner}),r.React.createElement(r.ReactNative.Text,{style:v.loadingText},"Loading preview...")));if(!e)return r.React.createElement(r.ReactNative.View,{style:v.container},r.React.createElement(r.ReactNative.Text,{style:v.centeredText},"Unable to load preview"));const D=y(),et=N();return r.React.createElement(r.ReactNative.View,{style:v.previewContainer},r.React.createElement(r.ReactNative.View,{style:v.header},r.React.createElement(r.ReactNative.Text,{style:v.activityType},x," ",L),r.React.createElement(r.ReactNative.Text,{style:v.rpcPreviewText,numberOfLines:1},"RPC Preview")),r.React.createElement(r.ReactNative.View,{style:v.content},r.React.createElement(r.ReactNative.View,{style:v.albumArt},e.image?r.React.createElement(r.ReactNative.Image,{source:{uri:e.image},style:v.albumImage,resizeMode:"cover"}):r.React.createElement(r.ReactNative.Text,{style:v.musicIcon},"\u{1F3B5}")),r.React.createElement(r.ReactNative.View,{style:v.trackInfo},r.React.createElement(r.ReactNative.Text,{style:v.trackName,numberOfLines:1},e.name),r.React.createElement(r.ReactNative.Text,{style:v.artistName,numberOfLines:1},e.artist),c("showLargeText")&&et!=="No tooltip text"&&r.React.createElement(r.ReactNative.Text,{style:v.tooltipText,numberOfLines:1},et),c("showTimestamp")&&e.duration&&r.React.createElement(r.ReactNative.View,{style:v.progressContainer},r.React.createElement(r.ReactNative.Text,{style:v.timeText},C(D.current)),r.React.createElement(r.ReactNative.View,{style:v.progressBar},r.React.createElement(r.ReactNative.View,{style:[v.progressFill,{width:`${D.progress*100}%`}]})),r.React.createElement(r.ReactNative.Text,{style:v.timeText},C(D.total))))))}const v=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 Rt(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(s){return s+1},0),t=c("listeningTo",h.DEFAULT_SETTINGS.listeningTo),n=c("showTimestamp",h.DEFAULT_SETTINGS.showTimestamp),a=function(){const s=!t;k("listeningTo",s),!s&&n&&k("showTimestamp",!1),e()},i=function(){t&&(k("showTimestamp",!n),e())};return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(wt,null),r.React.createElement(P,{spacing:8},r.React.createElement(S,{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);k("showLargeText",!s),e()}}),!t&&r.React.createElement(u,{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);k("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);k("showDurationInTooltip",!s),e()}}))))}function Et(){_.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()){I.showToast("Please enter an app name",w.getAssetIDByName("Small"));return}const s=c("ignoreList",[]);s.includes(t.trim())?I.showToast("App already in ignore list",w.getAssetIDByName("Warning")):(k("ignoreList",[...s,t.trim()]),n(""),e(),I.showToast("App added to ignore list",w.getAssetIDByName("Check")))},i=function(s){const l=c("ignoreList",[]);k("ignoreList",l.filter(function(g){return g!==s})),e(),I.showToast("App removed from ignore list",w.getAssetIDByName("Check"))};return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Add App to Ignore"},r.React.createElement(P,{spacing:4},r.React.createElement(z,{placeholder:"Enter app name",value:t,onChange:n,isClearable:!0,onSubmitEditing:a,returnKeyType:"done"}))),r.React.createElement(S,null,r.React.createElement(u,{label:"Add to Ignore List",subLabel:"Add the current app name to your ignore list",trailing:r.React.createElement(u.Arrow,null),onPress:a})),c("ignoreList",[]).length>0&&r.React.createElement(S,{title:"Ignored Apps"},c("ignoreList",[]).map(function(s,l){return r.React.createElement(u,{key:l,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:w.getAssetIDByName("TrashIcon"),style:{width:14,height:14,tintColor:"#ffffff"}}))})})),r.React.createElement(S,{title:"About Ignore List"},r.React.createElement(u,{label:"How it Works",subLabel:"When any app in your ignore list is active, your music status will be hidden"}),r.React.createElement(u,{label:"Detection",subLabel:"Apps are detected by their Discord activity name"}),r.React.createElement(u,{label:"Examples",subLabel:"Spotify, YouTube Music, Kizzy, Metrolist, echo"}))))}function St(){_.useProxy(p.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Logging Options"},r.React.createElement(Ce,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:c("verboseLogging",h.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(t){k("verboseLogging",t),e()}})),r.React.createElement(S,{title:"Debug Information"},r.React.createElement(u,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),r.React.createElement(u,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),r.React.createElement(S,{title:"Log Information"},r.React.createElement(u,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),r.React.createElement(u,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),r.React.createElement(u,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))))}var Pe,Ne,$e,Ue,De,_e,ze,xe,Fe,Ve,Me,Be,Oe,Ke,je,Ge,qe;(Pe=p.plugin.storage).username??(Pe.username=""),(Ne=p.plugin.storage).apiKey??(Ne.apiKey=""),($e=p.plugin.storage).appName??($e.appName="Music"),(Ue=p.plugin.storage).timeInterval??(Ue.timeInterval=5),(De=p.plugin.storage).showTimestamp??(De.showTimestamp=!0),(_e=p.plugin.storage).listeningTo??(_e.listeningTo=!0),(ze=p.plugin.storage).verboseLogging??(ze.verboseLogging=!1),(xe=p.plugin.storage).service??(xe.service="lastfm"),(Fe=p.plugin.storage).librefmUsername??(Fe.librefmUsername=""),(Ve=p.plugin.storage).librefmApiKey??(Ve.librefmApiKey=""),(Me=p.plugin.storage).listenbrainzUsername??(Me.listenbrainzUsername=""),(Be=p.plugin.storage).listenbrainzToken??(Be.listenbrainzToken=""),(Oe=p.plugin.storage).addToSidebar??(Oe.addToSidebar=!0),(Ke=p.plugin.storage).showLargeText??(Ke.showLargeText=!0),(je=p.plugin.storage).ignoreList??(je.ignoreList=[]),(Ge=p.plugin.storage).showAlbumInTooltip??(Ge.showAlbumInTooltip=!0),(qe=p.plugin.storage).showDurationInTooltip??(qe.showDurationInTooltip=!0);const c=function(e,t){return p.plugin.storage[e]??t},k=function(e,t){return p.plugin.storage[e]=t},X=(function(){function e(){O(this,e)}return K(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 Ye(){_.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(M,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(S,{title:"Active Service"},r.React.createElement(u,{label:"Current Service",subLabel:n?`Using: ${X.getServiceDisplayName(n)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(i){return r.React.createElement(q,{key:i,label:X.getServiceDisplayName(i),subLabel:a(i),checked:n===i,onPress:function(){i!==n&&(k("service",i),t())}})})),r.React.createElement(S,{title:"Service Configuration"},r.React.createElement(u,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:bt})}}),r.React.createElement(u,{label:"Libre.fm Settings",subLabel:"Configure Libre.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:yt})}}),r.React.createElement(u,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:mt})}})),r.React.createElement(S,{title:"Plugin Configuration"},r.React.createElement(u,{label:"Display Settings",subLabel:"Customize app name and update interval",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:vt})}}),r.React.createElement(u,{label:"RPC Customization",subLabel:"Customize Discord rich presence display options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"RPC Customization",render:Rt})}}),r.React.createElement(u,{label:"Ignore List",subLabel:"Configure apps that should hide your status",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Ignore List Settings",render:Et})}}),r.React.createElement(u,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:St})}}),r.React.createElement(Ce,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:c("addToSidebar",!1),onValueChange:function(i){k("addToSidebar",i),t()}})),r.React.createElement(S,{title:"About"},r.React.createElement(u,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),r.React.createElement(u,{label:"Author",subLabel:"kmmiio99o"}),r.React.createElement(u,{label:"Version",subLabel:"1.3.2"}))))}const{FormSection:Tt,FormRow:de}=rt.Forms,{TableRowIcon:It}=F.findByProps("TableRowIcon"),H=window.bunny,We=H?.metro?.findByPropsLazy("getRootNavigationRef"),pe=H?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),Xe=H?.metro?.findByPropsLazy("createList"),He=H?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function kt({tabs:e}){const t=r.NavigationNative.useNavigation();return r.React.createElement(de,{label:e.title(),leading:r.React.createElement(de.Icon,{source:e.icon}),trailing:r.React.createElement(r.React.Fragment,{},[e.trailing?e.trailing():null,r.React.createElement(de.Arrow,{key:"arrow"})]),onPress:function(){const n=e.page;t.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(n)}})}})}function At(e,t){try{t.push(J.after("default",H?.metro?.findByPropsLazy(["renderTitle","sections"],!1),function(n,a){const i=ae.findInReactTree(a.props.children,function(s){return s.type?.name==="UserSettingsOverview"});i&&t.push(J.after("render",i.type.prototype,function(s,l){const g=ae.findInReactTree(l.props.children,function(f){return f?.children?.[1]?.type===Tt})?.children;if(g){const f=g.findIndex(function(b){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(b?.props?.label)});g.splice(-~f||4,0,r.React.createElement(kt,{key:e.key,tabs:e}))}}))},!0))}catch(n){p.logger.info("Panel UI patch failed graciously \u{1F494}",n)}}function Lt(e,t){if(!pe||!We){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(It,{source:e.icon})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=We.getRootNavigationRef(),l=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(l)}})},withArrow:!0}};let a=pe.SETTING_RENDERER_CONFIG;Object.defineProperty(pe,"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(!Xe)return;t.push(J.after("createList",Xe,function(s,l){if(!s[0][i]){s[0][i]=!0;const[g]=s,f=g.sections?.find(function(b){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(A){return b.label===A&&b.title===A})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}catch{if(!He)return;t.push(J.after("default",He,function(s,l){if(!s[0][i]){s[0][i]=!0;const{sections:g}=ae.findInReactTree(l,function(b){return b.props?.sections}).props,f=g?.find(function(b){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(A){return b.label===A&&b.title===A})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}}function Ct(e){const t=[];let n=!1;const a=e.predicate||function(){return!0};return e.predicate=function(){return n?!1:a()},At(e,t),Lt(e,t),t.push(function(){return n=!0}),function(){for(const i of t)i()}}function Qe(){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=Ct({key:"LastFmScrobbler",icon:w.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return p.plugin.storage.addToSidebar===!0},page:Ye});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var Je;const T={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0},he=[];let B;const Ze=h.DEFAULT_SETTINGS;for(const e of Object.keys(Ze))p.plugin.storage[e]=p.plugin.storage[e]??Ze[e];(Je=p.plugin.storage).addToSidebar??(Je.addToSidebar=!1);const o=new Proxy(p.plugin.storage,{get(e,t){return e[t]},set(e,t,n){return e[t]=n,!0}});let re=0;const be=3,Pt=5e3,E=function(...e){return console.log("[Scrobble Plugin]",...e)},ye=function(...e){return console.error("[Scrobble Plugin] Error:",...e)};async function Q(){try{await pt(),re=0,E("Plugin initialized successfully")}catch(e){ye("Initialization failed:",e),re++,re<be?(E(`Retrying connection (${re}/${be})...`),setTimeout(Q,Pt)):ye(`Failed to connect after ${be} attempts`)}}async function Nt(){if(!o.service){E("No service selected. Please configure a service in plugin settings.");return}const e=V.getCurrentService().getServiceName(),t=o.service;let n=!1;switch(t){case"lastfm":n=!!(o.username&&o.apiKey);break;case"librefm":n=!!(o.librefmUsername&&o.librefmApiKey);break;case"listenbrainz":n=!!o.listenbrainzUsername;break}if(!n){ye(`Missing credentials for ${e}. Please configure in plugin settings.`);return}if(E(`Starting with ${e}...`),we.getCurrentUser())E("Discord is already connected, initializing immediately..."),await Q();else{E("Waiting for Discord connection...");const a=function(){we.getCurrentUser()&&(E("Discord connection established"),Q(),r.FluxDispatcher.unsubscribe("CONNECTION_OPEN",a))};r.FluxDispatcher.subscribe("CONNECTION_OPEN",a)}}var $t={onLoad(){if(E("Plugin loading..."),T.pluginStopped=!1,!o.service)E("No service configured. Please select a service in plugin settings.");else{const e=V.getCurrentService().getServiceName();E(`Configuration: Service=${e}, Update Interval=${o.timeInterval}s, Verbose=${o.verboseLogging}`)}try{B=Qe(),he.push(function(){B&&(B(),B=void 0)})}catch(e){E("Sidebar setup failed:",e)}Nt()},onUnload(){E("Plugin unloading..."),T.pluginStopped=!0,he.forEach(function(e){try{e()}catch{}}),he.length=0,fe(),E("Plugin unloaded")},async onSettingsUpdate(e){const t=o.service,n=e.service,a=o.addToSidebar,i=e.addToSidebar;if(Object.assign(o,e),E("Settings updated:",Object.keys(e)),a!==i)if(i)try{B=Qe(),E("Sidebar enabled")}catch(s){E("Failed to enable sidebar:",s)}else B&&(B(),B=void 0,E("Sidebar disabled"));t!==n&&n?(E(`Service changed from ${t||"none"} to ${n}`),await ht(n)):!T.pluginStopped&&o.service?(E("Restarting with updated settings..."),await Q()):o.service||(E("Service unselected, stopping plugin..."),fe())},onDiscordReconnect(){T.pluginStopped||(E("Discord reconnected, reinitializing plugin..."),Q())},settings:Ye};return Y.currentSettings=o,Y.default=$t,Y.pluginState=T,Object.defineProperty(Y,"__esModule",{value:!0}),Y})({},vendetta,vendetta.metro.common,vendetta.metro,window.React,vendetta.storage,vendetta.metro.common.ReactNative,vendetta.ui.toasts,vendetta.ui.assets,vendetta.patcher,vendetta.ui.components,vendetta.utils);