vd-plugins/multi-scrobbler/index.js
2026-07-26 14:10:40 +00:00

1 line
53 KiB
JavaScript

(function(q,g,n,M,Nt,D,re,I,S,J,Qe,ne){"use strict";const d={DEFAULT_APP_NAME:"Music",DEFAULT_TIME_INTERVAL:5,APPLICATION_ID:"1054951789318909972",MIN_UPDATE_INTERVAL:3,LIBREFM_MIN_UPDATE_INTERVAL:60,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/2.0",requiresApiKey:!0,requiresToken:!1},listenbrainz:{name:"ListenBrainz",baseUrl:"https://api.listenbrainz.org/1",requiresApiKey:!0,requiresToken:!0}},DEFAULT_HEADERS:{},DEFAULT_COVER_HASHES:["2a96cbd8b46e442fc41c2b86b821562f"],DEFAULT_SETTINGS:{username:"",apiKey:"",appName:"Music",timeInterval:5,showTimestamp:!0,listeningTo:!0,showLargeText:!0,ignoreYouTubeMusic:!1,verboseLogging:!1,service:void 0,librefmUsername:"",librefmApiKey:"",listenbrainzUsername:"",listenbrainzToken:""},API_ERROR_CODES:{2:"Invalid service",3:"Invalid method",4:"Invalid format",5:"Invalid parameters",6:"Invalid resource specified",7:"Invalid session key",8:"Invalid API key",9:"Invalid session",10:"Invalid API signature",11:"Service offline",13:"Invalid method signature supplied",16:"Service temporarily unavailable",26:"Suspended API key",29:"Rate limit exceeded"}};function B(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}function pe(e,r){for(var t=0;t<r.length;t++){var a=r[t];a.enumerable=a.enumerable||!1,a.configurable=!0,"value"in a&&(a.writable=!0),Object.defineProperty(e,a.key,a)}}function O(e,r,t){return r&&pe(e.prototype,r),t&&pe(e,t),e}const{SET_ACTIVITY:$t}=M.findByProps("SET_ACTIVITY"),Ze=M.findByProps("getAssetIds"),be=M.findByProps("getAPIBaseURL","get"),et=M.findByStoreName("SelfPresenceStore"),he=M.findByStoreName("UserStore");function ae(){return ye(null)}function ye(e){E.pluginStopped&&(ue(),e=null),E.lastActivity=e,n.FluxDispatcher.dispatch({type:"LOCAL_ACTIVITY_UPDATE",activity:e,pid:2312,socketId:"Multi-Scrobbler@Vendetta"})}async function tt(e,r){const t=`${be.getAPIBaseURL()}/applications/${r}/external-assets`,a=await be.post({url:t,body:{urls:e},oldFormErrors:!0,rejectWithError:!1});console.log("[Multi-Scrobbler] External assets response:",JSON.stringify(a));const i=a?.body;return console.log("[Multi-Scrobbler] Response body:",JSON.stringify(i)),Array.isArray(i)?i.map(function(s){return`mp:${s.external_asset_path}`}):[]}async function rt(e,r=d.APPLICATION_ID){if(!e?.length)return[];try{const t=await Ze.fetchAssetIds(r,e);if(Array.isArray(t)&&t.length>0&&t[0])return t;const a=e.filter(function(i){return i&&(i.startsWith("http:")||i.startsWith("https:"))});return a.length===0?[]:await tt(a,r)}catch(t){return console.error("[Multi-Scrobbler] Failed to fetch assets:",t),[]}}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 Q=function(...e){return console.log("[Debug]",...e)},me=function(...e){return console.error("[Debug] Error:",...e)};function j(e,r){R[e]=r,e==="lastError"&&r?me("Error recorded:",r.message):e==="lastTrack"&&r?Q("Track updated:",`${r.artist} - ${r.name}`):e==="currentService"&&r&&Q("Service changed to:",r)}function we(){R.apiCallCount=(R.apiCallCount||0)+1,Q(`API call count: ${R.apiCallCount}`)}function Z(e,r){R.serviceErrors=R.serviceErrors||{lastfm:[],librefm:[],listenbrainz:[]},R.serviceErrors[e]=R.serviceErrors[e]||[],R.serviceErrors[e].push(`${new Date().toISOString()}: ${r}`),R.serviceErrors[e].length>10&&(R.serviceErrors[e]=R.serviceErrors[e].slice(-10)),me(`${e} error:`,r)}function ve(){R.lastSuccessfulUpdate=new Date().toISOString(),Q("Successful update recorded at:",R.lastSuccessfulUpdate)}function ee(e){return ee=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},ee(e)}function Se(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(Se=function(){return!!e})()}function nt(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function at(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function it(e,r){return r&&(at(r)==="object"||typeof r=="function")?r:nt(e)}function ie(e,r,t){return r=ee(r),it(e,Se()?Reflect.construct(r,t||[],ee(e).constructor):r.apply(e,t))}function se(e,r){return se=Object.setPrototypeOf||function(a,i){return a.__proto__=i,a},se(e,r)}function oe(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(r&&r.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),r&&se(e,r)}const Re=["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36","Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15","Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36","Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0","Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1"];let le=(function(){function e(){B(this,e)}return O(e,[{key:"log",value:function(...r){console.log(`[${this.getServiceName()}]`,...r)}},{key:"logError",value:function(...r){console.error(`[${this.getServiceName()}] Error:`,...r)}},{key:"logVerbose",value:function(...r){console.log(`[${this.getServiceName()}] Verbose:`,...r)}},{key:"handleError",value:async function(r){this.lastError=r.error||0;const t=this.getErrorMessage(r);throw this.logError(t),Z(this.getServiceName().toLowerCase(),t),new Error(`${this.getServiceName()} API Error: ${t}`)}},{key:"getErrorMessage",value:function(r){return r.error&&d.API_ERROR_CODES[r.error]?d.API_ERROR_CODES[r.error]:r.message||r.toString()||"Unknown error"}},{key:"makeRequest",value:async function(r,t={}){try{this.logVerbose(`Making request to: ${r}`),we();const a=await fetch(r,{...t,headers:{"User-Agent":Re[Math.floor(Math.random()*Re.length)],...t.headers}});if(!a.ok){const s=new Error(`HTTP ${a.status}: ${a.statusText}`);throw Z(this.getServiceName().toLowerCase(),s.message),s}const i=await a.json();return i.error&&await this.handleError(i),this.retryCount=0,i}catch(a){if(this.retryCount++,this.retryCount>d.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,Z(this.getServiceName().toLowerCase(),`Max retries exceeded: ${a.message}`),a;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${d.MAX_RETRY_ATTEMPTS})`),await new Promise(function(i){return setTimeout(i,d.RETRY_DELAY)}),this.makeRequest(r,t)}}},{key:"isDefaultCover",value:function(r){return r?d.DEFAULT_COVER_HASHES.some(function(t){return r.includes(t)}):!0}},{key:"processAlbumArt",value:function(r){return!r||this.isDefaultCover(r)?null:r}},{key:"getLastError",value:function(){return this.lastError}},{key:"resetRetryCount",value:function(){this.retryCount=0}}]),e})(),st=(function(e){oe(r,e);function r(){return B(this,r),ie(this,r,arguments)}return O(r,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...t){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"validateCredentials",value:async function(){try{if(!l.username||!l.apiKey)throw new Error("Username or API key not set");const t=new URLSearchParams({method:"user.getinfo",user:l.username,api_key:l.apiKey,format:"json"}),a=`${d.SERVICES.lastfm.baseUrl}?${t}`;return await this.makeRequest(a),this.log("Credentials validation successful"),!0}catch(t){return this.logError("Credentials validation failed:",t),!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 t=new URLSearchParams({method:"user.getrecenttracks",user:l.username,api_key:l.apiKey,limit:"1",extended:"1",format:"json"}),a=`${d.SERVICES.lastfm.baseUrl}?${t}`,i=(await this.makeRequest(a))?.recenttracks?.track?.[0];if(!i)throw new Error("No tracks found");this.logVerbose("Raw track data:",i);const s=!!i["@attr"]?.nowplaying,o=i.date?.uts?parseInt(i.date.uts):Math.floor(Date.now()/1e3),f=function(k,y="name"){return k?typeof k=="string"?k:k["#text"]??k[y]??"":""};let b=null,h=null;if(s)try{const k=new URLSearchParams({method:"track.getInfo",track:i.name,artist:f(i.artist),api_key:l.apiKey,format:"json"}),y=`${d.SERVICES.lastfm.baseUrl}?${k}`,v=await this.makeRequest(y);v?.track?.duration&&(b=parseInt(v.track.duration),b>0&&(b=Math.floor(b/1e3),h=o+b))}catch(k){this.logVerbose("Failed to fetch track duration:",k)}const P=this.processAlbumArt(i.image?.find(function(k){return k.size==="large"})?.["#text"]),_={name:i.name,artist:f(i.artist),album:f(i.album,"title"),albumArt:P,url:i.url,date:i.date?.["#text"]??"now",nowPlaying:s,loved:i.loved==="1",from:o,to:h,duration:b};return this.logVerbose("Processed track:",_),this.log(`${s?"Now playing":"Last played"}:`,`${_.artist} - ${_.name}`),_}catch(t){throw this.logError("Failed to fetch latest scrobble:",t),t}}}]),r})(le),ot=(function(e){oe(r,e);function r(){return B(this,r),ie(this,r,arguments)}return O(r,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...t){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"validateCredentials",value:async function(){try{const t=l.librefmUsername,a=l.librefmApiKey;if(!t||!a)throw new Error("Username or API key not set for Libre.fm");const i=new URLSearchParams({method:"user.getinfo",user:t,api_key:a,format:"json"}),s=`${d.SERVICES.librefm.baseUrl}?${i}`;return await this.makeRequest(s),this.log("Credentials validation successful"),!0}catch(t){return this.logError("Credentials validation failed:",t),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const t=l.librefmUsername,a=l.librefmApiKey;if(!t||!a)throw new Error("Username or API key not set for Libre.fm");this.logVerbose("Fetching latest scrobble for user:",t);const i=new URLSearchParams({method:"user.getrecenttracks",user:t,api_key:a,limit:"1",extended:"1",format:"json"}),s=`${d.SERVICES.librefm.baseUrl}?${i}`,o=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!o)throw new Error("No tracks found");this.logVerbose("Raw track data:",o);const f=!!o["@attr"]?.nowplaying,b=o.date?.uts?parseInt(o.date.uts):Math.floor(Date.now()/1e3),h=function(v,C="name"){return v?typeof v=="string"?v:v["#text"]??v[C]??"":""};let P=null,_=null;if(f)try{const v=new URLSearchParams({method:"track.getInfo",track:o.name,artist:h(o.artist),api_key:a,format:"json"}),C=`${d.SERVICES.librefm.baseUrl}?${v}`,$=await this.makeRequest(C);$?.track?.duration&&(P=parseInt($.track.duration),P>0&&(P=Math.floor(P/1e3),_=b+P))}catch(v){this.logVerbose("Failed to fetch track duration:",v)}const k=this.processAlbumArt(o.image?.find(function(v){return v.size==="large"})?.["#text"]),y={name:o.name,artist:h(o.artist),album:h(o.album,"title"),albumArt:k,url:o.url,date:o.date?.["#text"]??"now",nowPlaying:f,loved:o.loved==="1",from:b,to:_,duration:P};return this.logVerbose("Processed track:",y),this.log(`${f?"Now playing":"Last played"}:`,`${y.artist} - ${y.name}`),y}catch(t){throw this.logError("Failed to fetch latest scrobble:",t),t}}}]),r})(le),lt=(function(e){oe(r,e);function r(){return B(this,r),ie(this,r,arguments)}return O(r,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...t){l.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"validateCredentials",value:async function(){try{const t=l.listenbrainzUsername,a=l.listenbrainzToken;if(!t)throw new Error("Username not set for ListenBrainz");const i=`${d.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(t)}/listens?count=1`,s={};return a&&(s.Authorization=`Token ${a}`),await this.makeRequest(i,{headers:s}),this.log("Credentials validation successful"),!0}catch(t){return this.logError("Credentials validation failed:",t),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const t=l.listenbrainzUsername,a=l.listenbrainzToken;if(!t)throw new Error("Username not set for ListenBrainz");this.logVerbose("Fetching latest scrobble for user:",t);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=`${d.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(t)}/playing-now`,v={};a&&(v.Authorization=`Token ${a}`);const C=await this.makeRequest(y,{headers:v}),$=s(C);$&&$.length>0&&(i=$[0],i.playing_now=!0)}catch(y){this.logVerbose("No currently playing track or failed to fetch:",y)}let o;if(i)o=i,this.logVerbose("Using currently playing track");else{const y=`${d.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(t)}/listens?count=1`,v={};a&&(v.Authorization=`Token ${a}`);const C=await this.makeRequest(y,{headers:v}),$=(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(!$||$.length===0)throw new Error("No listens found");o=$[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",o);const f=!!o.playing_now,b=o.listened_at||Math.floor(Date.now()/1e3);let h=null,P=null;o.track_metadata.additional_info?.duration_ms&&(h=Math.floor(o.track_metadata.additional_info.duration_ms/1e3),f&&h>0&&(P=b+h));let _=null;o.track_metadata.additional_info?.release_mbid&&(_=`https://coverartarchive.org/release/${o.track_metadata.additional_info.release_mbid}/front`);const k={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/player/${o.track_metadata.additional_info?.recording_mbid||`${encodeURIComponent(o.track_metadata.artist_name)}/${encodeURIComponent(o.track_metadata.track_name)}`}`,date:f?"now":new Date(b*1e3).toISOString(),nowPlaying:f,loved:!1,from:b,to:P,duration:h};return this.logVerbose("Processed track:",k),this.log(`${f?"Now playing":"Last played"}:`,`${k.artist} - ${k.name}`),k}catch(t){throw this.logError("Failed to fetch latest listen:",t),t}}},{key:"getErrorMessage",value:function(t){return t.error?t.error:t.message?t.message:t.toString()||"Unknown error"}}]),r})(le);const K=(function(){function e(){B(this,e),this.serviceInstances=new Map}return O(e,[{key:"getService",value:function(r){this.serviceInstances||(this.serviceInstances=new Map);const t=r||l.service;if(!t)throw new Error("[ServiceFactory] No service type specified and no default service configured");return this.serviceInstances.has(t)||this.serviceInstances.set(t,this.createService(t)),this.serviceInstances.get(t)}},{key:"getCurrentService",value:function(){return this.getService(l.service)}},{key:"createService",value:function(r){switch(r){case"lastfm":return new st;case"librefm":return new ot;case"listenbrainz":return new lt;default:throw new Error(`[ServiceFactory] Unknown service type: ${r}`)}}},{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(r){try{return await this.getService(r).validateCredentials()}catch(t){return console.error(`[ServiceFactory] Failed to test ${r}:`,t),!1}}},{key:"getSupportedServices",value:function(){return["lastfm","librefm","listenbrainz"]}},{key:"getServiceDisplayName",value:function(r){return this.getService(r).getServiceName()}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance();function Ee(e){if(!e||e<0)return"0:00";const r=Math.floor(e/3600),t=Math.floor(e%3600/60),a=Math.floor(e%60);return r>0?`${r}:${t.toString().padStart(2,"0")}:${a.toString().padStart(2,"0")}`:`${t}:${a.toString().padStart(2,"0")}`}function Te(){return Math.floor(Date.now()/1e3)}var ct=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(ct||{});const L=function(...e){return console.log("[Scrobble Plugin]",...e)},z=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},N=function(...e){return l.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ce=(function(){function e(){B(this,e)}return O(e,[{key:"updateActivity",value:async function(){if(E.pluginStopped){L("Plugin is stopped; skipping activity updates and clearing timers"),N("Plugin is stopped, skipping update");try{this.stopUpdates(),N("Update timers cleared due to stopped plugin")}catch(t){z("Error while stopping updates for stopped plugin:",t)}return}const r=K.getCurrentService().getServiceName();N(`Fetching latest track from ${r}...`);try{if(l.ignoreList&&l.ignoreList.length>0){const s=et.findActivity(function(o){return o.name?l.ignoreList.some(function(f){return o.name.toLowerCase().includes(f.toLowerCase())}):!1});if(s){L(`Ignored app (${s.name}) is currently active; clearing activity and skipping updates`),N(`Ignored app (${s.name}) is currently playing, clearing activity`);try{j("ignoredActivity",s.name)}catch(o){N("Failed to set debug info for ignored activity:",o)}ae();return}}we();const t=await K.getCurrentService().fetchLatestScrobble();if(j("lastTrack",t),!t.nowPlaying){L("No currently playing track reported by service; clearing activity and skipping RPC update"),N("Track is not currently playing");try{j("lastTrack_nowPlaying",!1)}catch(s){N("Failed to set debug info for nowPlaying:",s)}ae();return}if(E.lastTrackUrl===t.url){L("Track unchanged; skipping Discord RPC update"),N("Track hasn't changed"),ve(),this.consecutiveFailures=0;return}L(`\u{1F3B5} Track changed: ${t.artist} - ${t.name}`);let a;if(t.nowPlaying&&l.showTimestamp&&t.from){const s=Te();let o=t.from;if(o<s-3600){if(t.duration&&t.duration>0){const f=Math.min(t.duration*.1,30);o=s-f}else o=s;N("had to estimate start time")}a={start:o*1e3},t.to&&(a.end=t.to*1e3)}N(`\u{1F3AF} Preparing RPC update for: ${t.artist} - ${t.name}`);const i={name:l.appName||d.DEFAULT_APP_NAME,flags:0,type:l.listeningTo?2:0,details:t.name,state:`${t.artist}`,status_display_type:1,application_id:d.APPLICATION_ID};if(i.name.includes("{{")){const s={artist:t.artist,name:t.name,album:t.album,service:r};for(const[o,f]of Object.entries(s))i.name=i.name.replace(new RegExp(`{{${o}}}`,"g"),f||"")}if(a&&(i.timestamps=a,N("Timestamps set:",{start:new Date(a.start).toISOString(),end:a.end?new Date(a.end).toISOString():"none",duration:t.duration?Ee(t.duration):"unknown"})),t.album||t.albumArt){const s=t.albumArt?[t.albumArt]:[];let o=(await rt(s))[0];if(o){if(i.assets={large_image:o},l.showLargeText){let f="";if(l.showAlbumInTooltip&&t.album&&(f+=`on ${t.album}`),l.showDurationInTooltip&&t.duration){const b=Ee(t.duration);f?f+=` \u2022 ${b}`:f=b}f&&(i.assets.large_text=f)}N("Album art set:",o),i.assets.large_text&&N("Tooltip text set:",i.assets.large_text)}else t.album&&l.showLargeText&&l.showAlbumInTooltip&&(i.assets={large_text:`on ${t.album}`})}N("Setting Discord activity:",i),j("lastActivity",i),await ye(i),E.lastTrackUrl=t.url,this.currentActivity=i,E.lastActivity=i,this.consecutiveFailures=0,this.lastUpdateTime=Te(),ve(),L(`\u2705 RPC updated successfully: ${t.artist} - ${t.name}`)}catch(t){z("Update failed:",t);try{Z(l.service,t.message)}catch(a){z("Failed to record service error:",a)}try{j("lastUpdateError",{message:t.message,service:l.service,lastTrackUrl:E.lastTrackUrl})}catch(a){N("Failed to set debug info for last update error:",a)}this.handleError(t)}}},{key:"handleError",value:function(r){this.consecutiveFailures++,j("lastError",r),z(`Failure ${this.consecutiveFailures}/${d.MAX_RETRY_ATTEMPTS}:`,r.message),this.consecutiveFailures>=d.MAX_RETRY_ATTEMPTS&&(z("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var r=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),L("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){L("Attempting to reconnect..."),r.initialize().then(function(){L("Reconnection successful!"),r.stopReconnection()}).catch(function(t){z("Reconnection attempt failed:",t.message)})},d.RETRY_DELAY))}},{key:"stopReconnection",value:function(){try{this.reconnectTimer&&(clearInterval(this.reconnectTimer),this.reconnectTimer=void 0),this.isReconnecting=!1,this.consecutiveFailures=0}catch(r){console.error("[Scrobble Plugin] Reconnection cleanup error:",r)}}},{key:"stopUpdates",value:function(){try{this.updateTimer&&(clearInterval(this.updateTimer),this.updateTimer=void 0)}catch(r){console.error("[Scrobble Plugin] Timer cleanup error:",r)}}},{key:"initialize",value:async function(){var r=this;if(E.pluginStopped)throw new Error("Plugin is stopped");const t=K.getCurrentService().getServiceName();L(`Initializing with ${t}...`);try{if(!await K.validateCurrentService())throw new Error(`Invalid credentials for ${t}`);L(`${t} credentials validated successfully`)}catch(o){throw z(`Failed to validate ${t} credentials:`,o),o}this.stopUpdates(),L("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const a=l.service;let i=d.MIN_UPDATE_INTERVAL;a==="librefm"&&(i=d.LIBREFM_MIN_UPDATE_INTERVAL,L(`Libre.fm active: enforcing ${i}s minimum update interval`));const s=Math.max((Number(l.timeInterval)||d.DEFAULT_SETTINGS.timeInterval)*1e3,i*1e3);this.updateTimer=setInterval(function(){return r.updateActivity()},s),L(`Update timer started with interval: ${s}ms (${s/1e3}s)`)}},{key:"stop",value:function(){if(!E.pluginStopped){L("Stopping plugin..."),E.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),ae(),L("Plugin stopped successfully")}catch(r){console.error("[Scrobble Plugin] Stop error:",r)}}}},{key:"switchService",value:async function(r){if(E.pluginStopped)return;L(`Switching to ${r}...`);const t=!E.pluginStopped;this.stop();try{K.clearCache(),E.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,t&&(E.pluginStopped=!1,await this.initialize())}catch(a){z("Failed to switch service:",a)}}},{key:"getStatus",value:function(){const r=K.getCurrentService().getServiceName();return{running:!E.pluginStopped,service:r,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(),ut=function(){return ce.initialize()},ue=function(){return ce.stop()},ft=function(e){return ce.switchService(e)},{ScrollView:F}=M.findByProps("ScrollView"),{TableRowGroup:T,TableSwitchRow:ke,TableCheckboxRow:W,Stack:U,TableRow:u}=M.findByProps("TableSwitchRow","TableCheckboxRow","TableRowGroup","Stack","TableRow"),{TextInput:x}=M.findByProps("TextInput");function gt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(t){return t+1},0),r=async function(){I.showToast("Testing Last.fm connection...",S.getAssetIDByName("ClockIcon"));try{await X.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(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Credentials"},n.React.createElement(U,{spacing:4},n.React.createElement(x,{placeholder:"Last.fm Username",value:c("username"),onChange:function(t){A("username",t),e()},isClearable:!0}),n.React.createElement(x,{placeholder:"Last.fm API Key",value:c("apiKey"),onChange:function(t){A("apiKey",t),e()},secureTextEntry:!0,isClearable:!0}))),n.React.createElement(T,{title:"Actions"},n.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:n.React.createElement(u.Arrow,null),onPress:r}),n.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:n.React.createElement(u.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(t){console.error("Failed to open Last.fm API URL:",t),I.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",S.getAssetIDByName("XIcon"))}}}))))}function dt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(t){return t+1},0),r=async function(){I.showToast("Testing Libre.fm connection...",S.getAssetIDByName("ClockIcon"));try{await X.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(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Credentials"},n.React.createElement(U,{spacing:4},n.React.createElement(x,{placeholder:"Libre.fm Username",value:c("librefmUsername"),onChange:function(t){A("librefmUsername",t),e()},isClearable:!0}),n.React.createElement(x,{placeholder:"Libre.fm API Key",value:c("librefmApiKey"),onChange:function(t){A("librefmApiKey",t),e()},secureTextEntry:!0,isClearable:!0}))),n.React.createElement(T,{title:"Actions"},n.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:n.React.createElement(u.Arrow,null),onPress:r}),n.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:n.React.createElement(u.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://www.last.fm/api/account/create")}catch(t){console.error("Failed to open Last.fm API URL:",t),I.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",S.getAssetIDByName("XIcon"))}}}))))}function pt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(t){return t+1},0),r=async function(){I.showToast("Testing ListenBrainz connection...",S.getAssetIDByName("ClockIcon"));try{await X.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(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Credentials"},n.React.createElement(U,{spacing:4},n.React.createElement(x,{placeholder:"ListenBrainz Username",value:c("listenbrainzUsername"),onChange:function(t){A("listenbrainzUsername",t),e()},isClearable:!0}),n.React.createElement(x,{placeholder:"ListenBrainz Token",value:c("listenbrainzToken"),onChange:function(t){A("listenbrainzToken",t),e()},secureTextEntry:!0,isClearable:!0}))),n.React.createElement(T,{title:"Actions"},n.React.createElement(u,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:n.React.createElement(u.Arrow,null),onPress:r}),n.React.createElement(u,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:n.React.createElement(u.Arrow,null),onPress:async function(){try{await re.Linking.openURL("https://listenbrainz.org/settings/")}catch(t){console.error("Failed to open ListenBrainz settings URL:",t),I.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",S.getAssetIDByName("XIcon"))}}}))))}function bt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(t){return t+1},0),r=c("service")==="librefm";return n.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Activity Display"},n.React.createElement(U,{spacing:4},n.React.createElement(x,{placeholder:`App Name (Default: ${d.DEFAULT_SETTINGS.appName})`,value:c("appName",d.DEFAULT_SETTINGS.appName),onChange:function(t){A("appName",t),e()},isClearable:!0}),r?n.React.createElement(u,{label:"Update Interval Disabled",subLabel:"Libre.fm requires a fixed 60s update interval to prevent rate limiting",disabled:!0,dimmed:!0}):n.React.createElement(x,{placeholder:`Update Interval (Default: ${d.DEFAULT_SETTINGS.timeInterval}s)`,value:String(c("timeInterval",d.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const a=Number(t);a>=d.MIN_UPDATE_INTERVAL&&(A("timeInterval",a),e())},keyboardType:"numeric",isClearable:!0}))),n.React.createElement(T,{title:"About Display Settings"},n.React.createElement(u,{label:"App Name",subLabel:"The name shown in Discord for your activity"}),n.React.createElement(u,{label:"Update Interval",subLabel:"How often the plugin checks for new tracks (in seconds)"}),n.React.createElement(u,{label:"Minimum Interval",subLabel:`The plugin will never check more frequently than ${d.MIN_UPDATE_INTERVAL} seconds`}))))}function ht(){D.useProxy(g.plugin.storage);const[e,r]=n.React.useState(null),[t,a]=n.React.useState(!0),[i,s]=n.React.useState(0),o={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},f=function(p){return p?Array.isArray(p)?p.filter(function(m){return m&&typeof m=="string"}):p["#text"]?p["#text"].split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):typeof p=="string"?p.split(/[,;&]/).map(function(m){return m.trim()}).filter(function(m){return m.length>0}):["Unknown Artist"]:["Unknown Artist"]},b=function(p){return!p||p.length===0?"Unknown Artist":p.length===1?p[0]:p.length===2?`${p[0]} & ${p[1]}`:`${p.slice(0,-1).join(", ")} & ${p[p.length-1]}`};n.React.useEffect(function(){(async function(){if(!c("username")||!c("apiKey")){r(o),a(!1);return}try{a(!0);const p=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(p.recenttracks&&p.recenttracks.track&&p.recenttracks.track.length>0){const m=p.recenttracks.track[0],G=f(m.artist);let Je=180;try{const Pt=G[0]||"Unknown Artist",de=await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=track.getInfo&api_key=${c("apiKey")}&artist=${encodeURIComponent(Pt)}&track=${encodeURIComponent(m.name)}&format=json&u[...]`)).json();de.track&&de.track.duration&&(Je=Math.floor(de.track.duration/1e3))}catch{console.log("Could not fetch track duration, using default")}r({name:m.name||"Unknown Track",artist:b(G),artists:G,album:m.album?.["#text"]||"Unknown Album",image:m.image?.[2]?.["#text"]||m.image?.[1]?.["#text"]||null,nowPlaying:m["@attr"]?.nowplaying==="true",duration:Je,startTime:m["@attr"]?.nowplaying==="true"?Math.floor(Date.now()/1e3)-60:null})}else r(o)}catch{console.error("Failed to fetch preview data, using fallback:"),r(o)}finally{a(!1)}})()},[c("username"),c("apiKey")]),n.React.useEffect(function(){if(!e?.nowPlaying||!c("showTimestamp"))return;const p=setInterval(function(){if(e.startTime&&e.duration){const m=Math.floor(Date.now()/1e3)-e.startTime,G=Math.min(m/e.duration,1);s(G)}},1e3);return function(){return clearInterval(p)}},[e,c("showTimestamp")]);const h=function(p){const m=Math.floor(p/60),G=Math.floor(p%60);return`${m}:${G.toString().padStart(2,"0")}`},P=function(){let p="";if(c("showAlbumInTooltip")&&e?.album&&(p+=`on ${e.album}`),c("showDurationInTooltip")&&e?.duration){const m=` \u2022 ${h(e.duration)}`;p?p+=m:p=h(e.duration)}return p||"No tooltip text"},_=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}},k=c("listeningTo")?"Listening to":"Playing",y=c("appName")||"Music",v=c("showLargeText",!0),C=c("showTimestamp",!1);if(t)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 $=_(),Ye=P();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},k," ",y),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),v&&Ye!=="No tooltip text"&&n.React.createElement(n.ReactNative.Text,{style:w.tooltipText,numberOfLines:1},Ye),C&&e.duration?n.React.createElement(n.ReactNative.View,{style:w.progressContainer},n.React.createElement(n.ReactNative.Text,{style:w.timeText},h($.current)),n.React.createElement(n.ReactNative.View,{style:w.progressBar},n.React.createElement(n.ReactNative.View,{style:[w.progressFill,{width:`${$.progress*100}%`}]})),n.React.createElement(n.ReactNative.Text,{style:w.timeText},h($.total))):null)))}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 yt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(s){return s+1},0),r=c("listeningTo",d.DEFAULT_SETTINGS.listeningTo),t=c("showTimestamp",d.DEFAULT_SETTINGS.showTimestamp),a=function(){const s=!r;A("listeningTo",s),!s&&t&&A("showTimestamp",!1),e()},i=function(){r&&(A("showTimestamp",!t),e())};return n.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(ht,null),n.React.createElement(U,{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:r,onPress:a}),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);A("showLargeText",!s),e()}}),!r&&n.React.createElement(u,{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:t,onPress:i,disabled:!r}),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);A("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);A("showDurationInTooltip",!s),e()}}))))}function mt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(s){return s+1},0),[r,t]=n.React.useState(""),a=function(){if(!r.trim()){I.showToast("Please enter an app name",S.getAssetIDByName("Small"));return}const s=c("ignoreList",[]);s.includes(r.trim())?I.showToast("App already in ignore list",S.getAssetIDByName("Warning")):(A("ignoreList",[...s,r.trim()]),t(""),e(),I.showToast("App added to ignore list",S.getAssetIDByName("Check")))},i=function(s){const o=c("ignoreList",[]);A("ignoreList",o.filter(function(f){return f!==s})),e(),I.showToast("App removed from ignore list",S.getAssetIDByName("Check"))};return n.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Add App to Ignore"},n.React.createElement(U,{spacing:4},n.React.createElement(x,{placeholder:"Enter app name",value:r,onChange:t,isClearable:!0,onSubmitEditing:a,returnKeyType:"done"}))),n.React.createElement(T,null,n.React.createElement(u,{label:"Add to Ignore List",subLabel:"Add the current app name to your ignore list",trailing:n.React.createElement(u.Arrow,null),onPress:a})),c("ignoreList",[]).length>0&&n.React.createElement(T,{title:"Ignored Apps"},c("ignoreList",[]).map(function(s,o){return n.React.createElement(u,{key:o,label:s,trailing:n.React.createElement(n.ReactNative.TouchableOpacity,{onPress:function(){return i(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(u,{label:"How it Works",subLabel:"When any app in your ignore list is active, your music status will be hidden"}),n.React.createElement(u,{label:"Detection",subLabel:"Apps are detected by their Discord activity name"}),n.React.createElement(u,{label:"Examples",subLabel:"Spotify, YouTube Music, Kizzy, Metrolist, echo"}))))}function wt(){D.useProxy(g.plugin.storage);const[,e]=n.React.useReducer(function(r){return r+1},0);return n.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Logging Options"},n.React.createElement(ke,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:c("verboseLogging",d.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(r){A("verboseLogging",r),e()}})),n.React.createElement(T,{title:"Debug Information"},n.React.createElement(u,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),n.React.createElement(u,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),n.React.createElement(T,{title:"Log Information"},n.React.createElement(u,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),n.React.createElement(u,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),n.React.createElement(u,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))))}var Ie,Ae,Le,Ce,Pe,Ne,$e,Ue,_e,Me,De,xe,ze,Fe,Ve,Be,Oe;(Ie=g.plugin.storage).username??(Ie.username=""),(Ae=g.plugin.storage).apiKey??(Ae.apiKey=""),(Le=g.plugin.storage).appName??(Le.appName="Music"),(Ce=g.plugin.storage).timeInterval??(Ce.timeInterval=5),(Pe=g.plugin.storage).showTimestamp??(Pe.showTimestamp=!0),(Ne=g.plugin.storage).listeningTo??(Ne.listeningTo=!0),($e=g.plugin.storage).verboseLogging??($e.verboseLogging=!1),(Ue=g.plugin.storage).service??(Ue.service="lastfm"),(_e=g.plugin.storage).librefmUsername??(_e.librefmUsername=""),(Me=g.plugin.storage).librefmApiKey??(Me.librefmApiKey=""),(De=g.plugin.storage).listenbrainzUsername??(De.listenbrainzUsername=""),(xe=g.plugin.storage).listenbrainzToken??(xe.listenbrainzToken=""),(ze=g.plugin.storage).addToSidebar??(ze.addToSidebar=!0),(Fe=g.plugin.storage).showLargeText??(Fe.showLargeText=!0),(Ve=g.plugin.storage).ignoreList??(Ve.ignoreList=[]),(Be=g.plugin.storage).showAlbumInTooltip??(Be.showAlbumInTooltip=!0),(Oe=g.plugin.storage).showDurationInTooltip??(Oe.showDurationInTooltip=!0);const c=function(e,r){return g.plugin.storage[e]??r},A=function(e,r){return g.plugin.storage[e]=r},X=(function(){function e(){B(this,e)}return O(e,null,[{key:"getServiceDisplayName",value:function(r){switch(r){case"lastfm":return"Last.fm";case"librefm":return"Libre.fm";case"listenbrainz":return"ListenBrainz";default:return"Unknown"}}},{key:"testService",value:async function(r){try{switch(r){case"lastfm":return await this.testLastFmConnection();case"librefm":return await this.testLibreFmConnection();case"listenbrainz":return await this.testListenBrainzConnection();default:return!1}}catch(t){return console.error(`Error testing ${r}:`,t),!1}}},{key:"testLastFmConnection",value:async function(){const r=c("username"),t=c("apiKey");if(!r||!t)return!1;try{return!(await(await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getinfo&user=${r}&api_key=${t}&format=json`)).json()).error}catch(a){return console.error("Last.fm connection test failed:",a),!1}}},{key:"testLibreFmConnection",value:async function(){const r=c("librefmUsername"),t=c("librefmApiKey");if(!r||!t)return!1;try{return!(await(await fetch(`https://libre.fm/2.0/?method=user.getinfo&user=${r}&api_key=${t}&format=json`)).json()).error}catch(a){return console.error("Libre.fm connection test failed:",a),!1}}},{key:"testListenBrainzConnection",value:async function(){const r=c("listenbrainzUsername"),t=c("listenbrainzToken");if(!r)return!1;try{const a={"Content-Type":"application/json"};return t&&(a.Authorization=`Token ${t}`),(await fetch(`https://api.listenbrainz.org/1/user/${r}/listen-count`,{headers:a})).status===200}catch(a){return console.error("ListenBrainz connection test failed:",a),!1}}}]),e})();function Ke(){D.useProxy(g.plugin.storage);const e=n.NavigationNative.useNavigation(),[,r]=n.React.useReducer(function(i){return i+1},0),t=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 n.React.createElement(F,{style:{flex:1},contentContainerStyle:{padding:10}},n.React.createElement(U,{spacing:8},n.React.createElement(T,{title:"Active Service"},n.React.createElement(u,{label:"Current Service",subLabel:t?`Using: ${X.getServiceDisplayName(t)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(i){return n.React.createElement(W,{key:i,label:X.getServiceDisplayName(i),subLabel:a(i),checked:t===i,onPress:function(){i!==t&&(A("service",i),r())}})})),n.React.createElement(T,{title:"Service Configuration"},n.React.createElement(u,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:gt})}}),n.React.createElement(u,{label:"Libre.fm Settings",subLabel:"Configure Libre.fm credentials and options",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:dt})}}),n.React.createElement(u,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:pt})}})),n.React.createElement(T,{title:"Plugin Configuration"},n.React.createElement(u,{label:"Display Settings",subLabel:"Customize app name and update interval",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:bt})}}),n.React.createElement(u,{label:"RPC Customization",subLabel:"Customize Discord rich presence display options",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"RPC Customization",render:yt})}}),n.React.createElement(u,{label:"Ignore List",subLabel:"Configure apps that should hide your status",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Ignore List Settings",render:mt})}}),n.React.createElement(u,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:n.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:wt})}}),n.React.createElement(ke,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:c("addToSidebar",!1),onValueChange:function(i){A("addToSidebar",i),r()}})),n.React.createElement(T,{title:"About"},n.React.createElement(u,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),n.React.createElement(u,{label:"Author",subLabel:"kmmiio99o"}),n.React.createElement(u,{label:"Version",subLabel:"1.3.2"}))))}const{FormSection:vt,FormRow:fe}=Qe.Forms,{TableRowIcon:St}=M.findByProps("TableRowIcon"),H=window.bunny,Ge=H?.metro?.findByPropsLazy("getRootNavigationRef"),ge=H?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),je=H?.metro?.findByPropsLazy("createList"),We=H?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function Rt({tabs:e}){const r=n.NavigationNative.useNavigation();return n.React.createElement(fe,{label:e.title(),leading:n.React.createElement(fe.Icon,{source:e.icon}),trailing:n.React.createElement(n.React.Fragment,{},[e.trailing?e.trailing():null,n.React.createElement(fe.Arrow,{key:"arrow"})]),onPress:function(){const t=e.page;r.navigate("VendettaCustomPage",{title:e.title(),render:function(){return n.React.createElement(t)}})}})}function Et(e,r){try{r.push(J.after("default",H?.metro?.findByPropsLazy(["renderTitle","sections"],!1),function(t,a){const i=ne.findInReactTree(a.props.children,function(s){return s.type?.name==="UserSettingsOverview"});i&&r.push(J.after("render",i.type.prototype,function(s,o){const f=ne.findInReactTree(o.props.children,function(b){return b?.children?.[1]?.type===vt})?.children;if(f){const b=f.findIndex(function(h){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(h?.props?.label)});f.splice(-~b||4,0,n.React.createElement(Rt,{key:e.key,tabs:e}))}}))},!0))}catch(t){g.logger.info("Panel UI patch failed graciously \u{1F494}",t)}}function Tt(e,r){if(!ge||!Ge){console.warn("[LastFm] Missing required constants for tabs UI patch");return}const t={[e.key]:{type:"pressable",useTitle:e.title,title:e.title,icon:e.icon,IconComponent:e.icon&&function(){return n.React.createElement(St,{source:e.icon})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=Ge.getRootNavigationRef(),o=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return n.React.createElement(o)}})},withArrow:!0}};let a=ge.SETTING_RENDERER_CONFIG;Object.defineProperty(ge,"SETTING_RENDERER_CONFIG",{enumerable:!0,configurable:!0,get:function(){return{...a,...t}},set(s){a=s}});const i=Symbol("pinToSettings meow meow");try{if(!je)return;r.push(J.after("createList",je,function(s){if(!s[0][i]){s[0][i]=!0;const[o]=s,f=o.sections?.find(function(b){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(h){return b.label===h&&b.title===h})});f?.settings&&(f.settings=[...f.settings,e.key])}}))}catch{if(!We)return;r.push(J.after("default",We,function(s,o){if(!s[0][i]){s[0][i]=!0;const{sections:f}=ne.findInReactTree(o,function(h){return h.props?.sections}).props,b=f?.find(function(h){return["Bunny","Revenge","Kettu","Vencore","ShiggyCord"].some(function(P){return h.label===P&&h.title===P})});b?.settings&&(b.settings=[...b.settings,e.key])}}))}}function kt(e){const r=[];let t=!1;const a=e.predicate||function(){return!0};return e.predicate=function(){return t?!1:a()},Et(e,r),Tt(e,r),r.push(function(){return t=!0}),function(){for(const i of r)i()}}function qe(){if(!g.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=kt({key:"LastFmScrobbler",icon:S.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return g.plugin.storage.addToSidebar===!0},page:Ke});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var Xe;const E={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0};let V;const He=d.DEFAULT_SETTINGS;Object.keys(He).forEach(function(e){g.plugin.storage[e]=g.plugin.storage[e]??He[e]}),(Xe=g.plugin.storage).addToSidebar??(Xe.addToSidebar=!1);const l=new Proxy(g.plugin.storage,{get(e,r){return e[r]},set(e,r,t){return e[r]=t,!0}});let te=0;const It=3,At=5e3;async function Y(){try{await ut(),te=0,console.log("[Multi-Scrobbler] Successfully connected")}catch(e){console.error("[Multi-Scrobbler] Initialization error:",e),te++,te<It?(console.log(`[Multi-Scrobbler] Retrying connection... (attempt ${te})`),setTimeout(Y,At)):console.error("[Multi-Scrobbler] Failed to connect after multiple attempts")}}async function Lt(){if(!l.service){console.log("[Multi-Scrobbler] No service selected. Please configure a service in settings.");return}let e="Unknown";try{e=K.getCurrentService().getServiceName()}catch(a){console.error("[Multi-Scrobbler] Failed to determine current service name:",a)}const r=l.service;let t=!1;switch(r){case"lastfm":t=!!(l.username&&l.apiKey);break;case"librefm":t=!!(l.librefmUsername&&l.librefmApiKey);break;case"listenbrainz":t=!!l.listenbrainzUsername;break}if(!t){console.error(`[Multi-Scrobbler] Missing credentials for ${e}. Please configure in settings.`);return}if(console.log(`[Multi-Scrobbler] Starting with ${e}...`),he.getCurrentUser())Y();else{const a=function(){he.getCurrentUser()&&(Y(),n.FluxDispatcher.unsubscribe("CONNECTION_OPEN",a))};n.FluxDispatcher.subscribe("CONNECTION_OPEN",a)}}var Ct={onLoad(){if(console.log("[Multi-Scrobbler] Loading..."),E.pluginStopped=!1,l.addToSidebar!==!1)try{V=qe(),console.log("[Multi-Scrobbler] Sidebar patched successfully")}catch(e){console.error("[Multi-Scrobbler] Failed to patch sidebar:",e)}Lt()},onUnload(){if(console.log("[Multi-Scrobbler] Unloading..."),E.pluginStopped=!0,V)try{V(),V=void 0,console.log("[Multi-Scrobbler] Sidebar unpatched")}catch(e){console.error("[Multi-Scrobbler] Failed to unpatch sidebar:",e)}ue()},async onSettingsUpdate(e){const r=l.service,t=e.service,a=l.addToSidebar,i=e.addToSidebar;if(Object.assign(l,e),a!==i){if(i)try{V=qe(),console.log("[Multi-Scrobbler] Sidebar enabled")}catch(s){console.error("[Multi-Scrobbler] Failed to enable sidebar:",s)}else if(V){try{V()}catch(s){console.error("[Multi-Scrobbler] Failed to unpatch sidebar:",s)}V=void 0,console.log("[Multi-Scrobbler] Sidebar disabled")}}if(r!==t&&t){console.log(`[Multi-Scrobbler] Service changed from ${r||"none"} to ${t}`);try{await ft(t)}catch(s){console.error("[Multi-Scrobbler] Failed to switch service:",s)}}else if(!E.pluginStopped&&l.service)Y();else if(!l.service){console.log("[Multi-Scrobbler] Service unselected, stopping plugin...");try{ue()}catch(s){console.error("[Multi-Scrobbler] Error while stopping due to service unselected:",s)}}},onDiscordReconnect(){E.pluginStopped||(console.log("[Multi-Scrobbler] Discord reconnected, reinitializing..."),Y())},settings:Ke};return q.currentSettings=l,q.default=Ct,q.pluginState=E,Object.defineProperty(q,"__esModule",{value:!0}),q})({},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);