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