diff --git a/404.md b/404.md index 11dd9a9..f029ecf 100644 --- a/404.md +++ b/404.md @@ -7,8 +7,7 @@ permalink: /404.html Currently available plugins: - Commands -- Last.fm (Suppresed by Multi Scrobbler) -- Multi Scrobbler +- Last.fm # How to install? Paste a plugin URL into the Plugins page of Vendetta, following a basic format of: diff --git a/BetterBetterChatGesturesV3/index.js b/BetterBetterChatGesturesV3/index.js deleted file mode 100644 index 8396a6d..0000000 --- a/BetterBetterChatGesturesV3/index.js +++ /dev/null @@ -1 +0,0 @@ -(function(P,g,e,w,t,S,z,p,m,c){"use strict";var y={Failed:p.getAssetIDByName("Small"),Delete:p.getAssetIDByName("ic_message_delete"),Copy:p.getAssetIDByName("toast_copy_link"),Open:p.getAssetIDByName("ic_leave_stage"),Clipboard:p.getAssetIDByName("pending-alert"),Clock:p.getAssetIDByName("clock"),Forum:p.getAssetIDByName("ic_forum_channel"),Settings:{Toasts:{Settings:p.getAssetIDByName("ic_selection_checked_24px"),Failed:p.getAssetIDByName("ic_close_circle_24px")},Initial:p.getAssetIDByName("coffee"),Update:p.getAssetIDByName("discover"),Locale:p.getAssetIDByName("ic_locale_24px"),External:p.getAssetIDByName("ic_raised_hand_list"),Edit:p.getAssetIDByName("ic_edit_24px"),Reply:p.getAssetIDByName("ic_reply_24px"),Keyboard:p.getAssetIDByName("ic_keyboard_24px")}},b={shadow:function(a=.1){return{shadowColor:"#000",shadowOffset:{width:1,height:4},shadowOpacity:a,shadowRadius:4.65,elevation:8}},displayToast:function(a,r){e.toasts.open({content:r=="clipboard"?`Copied ${a} to clipboard.`:a,source:r=="clipboard"?y.Clipboard:y.Settings.Initial})}},A={plugin:{source:"https://github.com/MYSTRAVIL/vendetta-plugins"},author:{profile:{"Rosie<3":"https://github.com/acquitelol"}}};const{TouchableOpacity:v,View:N,Image:q,Text:L,Animated:C}=S.General,H=g.findByProps("transitionToGuild"),_=g.findByStoreName("UserStore"),X=g.findByProps("showUserProfile"),R=e.stylesheet.createThemedStyleSheet({container:{marginTop:25,marginLeft:"5%",marginBottom:-15,flexDirection:"row"},textContainer:{paddingLeft:15,paddingTop:5,flexDirection:"column",flexWrap:"wrap",...b.shadow()},image:{width:75,height:75,borderRadius:10,...b.shadow()},mainText:{opacity:.975,letterSpacing:.25},header:{color:m.semanticColors.HEADER_PRIMARY,fontFamily:e.constants.Fonts.DISPLAY_BOLD,fontSize:25,letterSpacing:.25},subHeader:{color:m.semanticColors.HEADER_SECONDARY,fontSize:12.75}});function J({name:a,authors:r}){const s=e.React.useRef(new C.Value(1)).current,n=function(){return C.spring(s,{toValue:1.1,duration:10,useNativeDriver:!0}).start()},i=function(){return C.spring(s,{toValue:1,duration:250,useNativeDriver:!0}).start()},l=function(){return X.showUserProfile({userId:_.getCurrentUser().id})},d={transform:[{scale:s}]};return e.React.createElement(e.React.Fragment,null,e.React.createElement(N,{style:R.container},e.React.createElement(v,{onPress:l,onPressIn:n,onPressOut:i},e.React.createElement(C.View,{style:d},e.React.createElement(q,{style:[R.image],source:{uri:_?.getCurrentUser()?.getAvatarURL()?.replace("webp","png")}}))),e.React.createElement(N,{style:R.textContainer},e.React.createElement(v,{onPress:function(){return H.openURL(A.plugin.source)}},e.React.createElement(L,{style:[R.mainText,R.header]},a)),e.React.createElement(N,{style:{flexDirection:"row"}},e.React.createElement(L,{style:[R.mainText,R.subHeader]},"A project by"),r.map(function(u,o,h){return e.React.createElement(v,{onPress:function(){return H.openURL(A.author.profile[u.name]??"https://github.com/")}},e.React.createElement(L,{style:[R.mainText,R.subHeader,{paddingLeft:4,fontFamily:e.constants.Fonts.DISPLAY_BOLD,flexDirection:"row"}]},u.name,o=200&&(h=$)}r.timeoutTap&&clearTimeout(r.timeoutTap),r.timeoutTap=setTimeout(function(){r.resetTapState()},h);const E=F?.getCurrentUser(),j=E&&o.author?o.author.id===E.id:!1,k={...i,taps:r.currentTapIndex,content:o.content||"",authorId:o.author?.id,isAuthor:j};if(r.currentTapIndex!==2){r.doubleTapState({state:"INCOMPLETE",nativeEvent:k});return}const oe=r.currentMessageID;r.resetTapState(),j?t.storage.userEdit?W?.startEditMessage(l,oe,k.content):t.storage.reply&&u&&G?.createPendingReply({channel:u,message:o,shouldMention:!0}):t.storage.reply&&u&&G?.createPendingReply({channel:u,message:o,shouldMention:!0}),r.openKeyboard(),r.doubleTapState({state:"COMPLETE",nativeEvent:k})}catch(i){c.logger.error("BetterChatGestures: Error in handleTapMessage patch",i),r.resetTapState()}});this.patches.push(s)}this.unpatchHandlers=function(){try{r.patches.forEach(function(s){typeof s=="function"&&s()}),r.patches=[],r.handlersInstances=new WeakSet}catch(s){c.logger.error("BetterChatGestures: Error in unpatchHandlers",s)}}}catch(s){c.logger.error("BetterChatGestures: Error in patchHandlers",s)}}},onLoad(){var a,r,s,n,i,l;try{if(!D){c.logger.error("BetterChatGestures: MessagesHandlers module not found! Plugin will not work.");return}(a=t.storage).tapUsernameMention??(a.tapUsernameMention=e.ReactNative.Platform.select({android:!1,ios:!0,default:!0})),e.ReactNative.Platform.OS==="android"&&(t.storage.tapUsernameMention=!1),(r=t.storage).reply??(r.reply=!0),(s=t.storage).userEdit??(s.userEdit=!0),(n=t.storage).keyboardPopup??(n.keyboardPopup=!0),(i=t.storage).delay??(i.delay="1000"),(l=t.storage).debugMode??(l.debugMode=!1),(!t.storage.delay||t.storage.delay===""||isNaN(parseInt(t.storage.delay,10))||parseInt(t.storage.delay,10)<200)&&(t.storage.delay="1000"),c.logger.log("BetterChatGestures: initialized with delay =",t.storage.delay);const d=this,u=["params","handlers","_params","messageHandlers"];let o=null,h=null;for(const E of u)if(o=Object.getOwnPropertyDescriptor(D.prototype,E)?.get,o){h=E,c.logger.log(`BetterChatGestures: Found property '${E}'`);break}o&&h?(Object.defineProperty(D.prototype,h,{configurable:!0,get(){return this&&d.patchHandlers.call(d,this),o.call(this)}}),this.unpatchGetter=function(){try{o&&h&&Object.defineProperty(D.prototype,h,{configurable:!0,get:o})}catch(E){c.logger.error("BetterChatGestures: Error in unpatchGetter",E)}}):c.logger.error("BetterChatGestures: Could not find params getter!")}catch(d){c.logger.error("BetterChatGestures: Error in onLoad",d)}},onUnload(){try{this.resetTapState(),this.unpatchGetter&&this.unpatchGetter(),this.unpatchHandlers&&this.unpatchHandlers(),this.timeoutTap&&(clearTimeout(this.timeoutTap),this.timeoutTap=null)}catch(a){c.logger.error("BetterChatGestures: Error in onUnload",a)}},settings:se};return P.default=ne,Object.defineProperty(P,"__esModule",{value:!0}),P})({},vendetta.metro,vendetta.metro.common,vendetta.patcher,vendetta.plugin,vendetta.ui.components,vendetta.storage,vendetta.ui.assets,vendetta.ui,vendetta); diff --git a/BetterBetterChatGesturesV3/manifest.json b/BetterBetterChatGesturesV3/manifest.json deleted file mode 100644 index 14d94d1..0000000 --- a/BetterBetterChatGesturesV3/manifest.json +++ /dev/null @@ -1 +0,0 @@ -{"authors":[{"name":"Rosie<3, MYSTRAVIL, forked by S-cript-kiddie02","id":"457924482024079362"}],"main":"index.js","vendetta":{"icon":"ic_chat"},"name":"BetterBetterChatGesturesV3","description":"Adds several new gestures in the chat.","hash":"fb5a8cf3b3448d2e23f2510fbb03b695c144a174f066cae6198655faf90e235e"} \ No newline at end of file diff --git a/README.md b/README.md index 1bc318f..792cfbe 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ Currently available plugins: - Commands -- Last.fm (Suppresed by Multi Scrobbler) -- Multi Scrobbler +- Last.fm # How to install? Paste a plugin URL into the Plugins page of Vendetta, following a basic format of: diff --git a/multi-scrobbler/index.js b/multi-scrobbler/index.js index f20ddb1..a78948c 100644 --- a/multi-scrobbler/index.js +++ b/multi-scrobbler/index.js @@ -1 +1 @@ -(function(x,f,r,$,ht,F,J,S,m,Q,Ge,Z){"use strict";const l={DEFAULT_APP_NAME:"Music",DEFAULT_TIME_INTERVAL:5,APPLICATION_ID:"1054951789318909972",MIN_UPDATE_INTERVAL:3,MAX_RETRY_ATTEMPTS:3,RETRY_DELAY:5e3,SERVICES:{lastfm:{name:"Last.fm",baseUrl:"http://ws.audioscrobbler.com/2.0",requiresApiKey:!0,requiresToken:!1},librefm:{name:"Libre.fm",baseUrl:"http://libre.fm/api",requiresApiKey:!0,requiresToken:!1},listenbrainz:{name:"ListenBrainz",baseUrl:"http://api.listenbrainz.org/1",requiresApiKey:!1,requiresToken:!0}},DEFAULT_COVER_HASHES:["2a96cbd8b46e442fc41c2b86b821562f","c6f59c1e5e7240a4c0d427abd71f3dbb"],DEFAULT_SETTINGS:{username:"",apiKey:"",appName:"Music",timeInterval:5,showTimestamp:!0,listeningTo:!0,ignoreSpotify:!0,ignoreYouTubeMusic:!0,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 M(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function ge(e,t){for(var n=0;n10&&(p.serviceErrors[e]=p.serviceErrors[e].slice(-10)),he(`${e} error:`,t)}function ve(){p.lastSuccessfulUpdate=new Date().toISOString(),q("Successful update recorded at:",p.lastSuccessfulUpdate)}function X(e){return X=Object.setPrototypeOf?Object.getPrototypeOf:function(n){return n.__proto__||Object.getPrototypeOf(n)},X(e)}function me(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(me=function(){return!!e})()}function je(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function Xe(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function He(e,t){return t&&(Xe(t)==="object"||typeof t=="function")?t:je(e)}function ee(e,t,n){return t=X(t),He(e,me()?Reflect.construct(t,n||[],X(e).constructor):t.apply(e,n))}function te(e,t){return te=Object.setPrototypeOf||function(i,a){return i.__proto__=a,i},te(e,t)}function ne(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&&te(e,t)}let re=(function(){function e(){M(this,e)}return z(e,[{key:"log",value:function(...t){console.log(`[${this.getServiceName()}]`,...t)}},{key:"logError",value:function(...t){console.error(`[${this.getServiceName()}] Error:`,...t)}},{key:"logVerbose",value:function(...t){console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"handleError",value:async function(t){this.lastError=t.error||0;const n=this.getErrorMessage(t);throw this.logError(n),j(this.getServiceName().toLowerCase(),n),new Error(`${this.getServiceName()} API Error: ${n}`)}},{key:"getErrorMessage",value:function(t){return t.error&&l.API_ERROR_CODES[t.error]?l.API_ERROR_CODES[t.error]:t.message||t.toString()||"Unknown error"}},{key:"makeRequest",value:async function(t,n={}){try{this.logVerbose(`Making request to: ${t}`),ye();const i=await fetch(t,{...n,headers:{"User-Agent":"Vendetta Multi-Service Scrobbler/3.0.0",...n.headers}});if(!i.ok){const s=new Error(`HTTP ${i.status}: ${i.statusText}`);throw j(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>l.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,j(this.getServiceName().toLowerCase(),`Max retries exceeded: ${i.message}`),i;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${l.MAX_RETRY_ATTEMPTS})`),await new Promise(function(a){return setTimeout(a,l.RETRY_DELAY)}),this.makeRequest(t,n)}}},{key:"isDefaultCover",value:function(t){return t?l.DEFAULT_COVER_HASHES.some(function(n){return t.includes(n)}):!0}},{key:"processAlbumArt",value:function(t){return!t||this.isDefaultCover(t)?null:t}},{key:"getLastError",value:function(){return this.lastError}},{key:"resetRetryCount",value:function(){this.retryCount=0}}]),e})(),We=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{if(!o.username||!o.apiKey)throw new Error("Username or API key not set");const n=new URLSearchParams({method:"user.getinfo",user:o.username,api_key:o.apiKey,format:"json"}),i=`${l.SERVICES.lastfm.baseUrl}?${n}`;return await this.makeRequest(i),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{if(!o.username||!o.apiKey)throw new Error("Username or API key not set");this.logVerbose("Fetching latest scrobble for user:",o.username);const n=new URLSearchParams({method:"user.getrecenttracks",user:o.username,api_key:o.apiKey,limit:"1",extended:"1",format:"json"}),i=`${l.SERVICES.lastfm.baseUrl}?${n}`,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,c=a.date?.uts?parseInt(a.date.uts):Math.floor(Date.now()/1e3);let g=null,d=null;if(s)try{const k=new URLSearchParams({method:"track.getInfo",track:a.name,artist:a.artist.name,api_key:o.apiKey,format:"json"}),L=`${l.SERVICES.lastfm.baseUrl}?${k}`,R=await this.makeRequest(L);R?.track?.duration&&(g=parseInt(R.track.duration),g>0&&(g=Math.floor(g/1e3),d=c+g))}catch(k){this.logVerbose("Failed to fetch track duration:",k)}const v=this.processAlbumArt(a.image?.find(function(k){return k.size==="large"})?.["#text"]),A={name:a.name,artist:a.artist.name,album:a.album["#text"],albumArt:v,url:a.url,date:a.date?.["#text"]??"now",nowPlaying:s,loved:a.loved==="1",from:c,to:d,duration:g};return this.logVerbose("Processed track:",A),this.log(`${s?"Now playing":"Last played"}:`,`${A.artist} - ${A.name}`),A}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(re),Je=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.librefmUsername,i=o.librefmApiKey;if(!n||!i)throw new Error("Username or API key not set for Libre.fm");const a=new URLSearchParams({method:"user.getinfo",user:n,api_key:i,format:"json"}),s=`${l.SERVICES.librefm.baseUrl}?${a}`;return await this.makeRequest(s),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=o.librefmUsername,i=o.librefmApiKey;if(!n||!i)throw new Error("Username or API key not set for Libre.fm");this.logVerbose("Fetching latest scrobble for user:",n);const a=new URLSearchParams({method:"user.getrecenttracks",user:n,api_key:i,limit:"1",extended:"1",format:"json"}),s=`${l.SERVICES.librefm.baseUrl}?${a}`,c=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!c)throw new Error("No tracks found");this.logVerbose("Raw track data:",c);const g=!!c["@attr"]?.nowplaying,d=c.date?.uts?parseInt(c.date.uts):Math.floor(Date.now()/1e3);let v=null,A=null;if(g)try{const R=new URLSearchParams({method:"track.getInfo",track:c.name,artist:c.artist.name,api_key:i,format:"json"}),N=`${l.SERVICES.librefm.baseUrl}?${R}`,xe=await this.makeRequest(N);xe?.track?.duration&&(v=parseInt(xe.track.duration),v>0&&(v=Math.floor(v/1e3),A=d+v))}catch(R){this.logVerbose("Failed to fetch track duration:",R)}const k=this.processAlbumArt(c.image?.find(function(R){return R.size==="large"})?.["#text"]),L={name:c.name,artist:c.artist.name,album:c.album["#text"],albumArt:k,url:c.url,date:c.date?.["#text"]??"now",nowPlaying:g,loved:c.loved==="1",from:d,to:A,duration:v};return this.logVerbose("Processed track:",L),this.log(`${g?"Now playing":"Last played"}:`,`${L.artist} - ${L.name}`),L}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(re),Qe=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.listenbrainzUsername,i=o.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");const a=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,s={};return i&&(s.Authorization=`Token ${i}`),await this.makeRequest(a,{headers:s}),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=o.listenbrainzUsername,i=o.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");this.logVerbose("Fetching latest scrobble for user:",n);let a=null;try{const L=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/playing-now`,R={};i&&(R.Authorization=`Token ${i}`);const N=await this.makeRequest(L,{headers:R});N.listens&&N.listens.length>0&&(a=N.listens[0],a.playing_now=!0)}catch(L){this.logVerbose("No currently playing track or failed to fetch:",L)}let s;if(a)s=a,this.logVerbose("Using currently playing track");else{const L=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,R={};i&&(R.Authorization=`Token ${i}`);const N=await this.makeRequest(L,{headers:R});if(!N.listens||N.listens.length===0)throw new Error("No listens found");s=N.listens[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",s);const c=!!s.playing_now,g=s.listened_at||Math.floor(Date.now()/1e3);let d=null,v=null;s.track_metadata.additional_info?.duration_ms&&(d=Math.floor(s.track_metadata.additional_info.duration_ms/1e3),c&&d>0&&(v=g+d));const A=null;s.track_metadata.additional_info?.release_mbid;const k={name:s.track_metadata.track_name,artist:s.track_metadata.artist_name,album:s.track_metadata.release_name||"",albumArt:A,url:s.track_metadata.additional_info?.origin_url||`https://listenbrainz.org/user/${n}`,date:c?"now":new Date(g*1e3).toISOString(),nowPlaying:c,loved:!1,from:g,to:v,duration:d};return this.logVerbose("Processed track:",k),this.log(`${c?"Now playing":"Last played"}:`,`${k.artist} - ${k.name}`),k}catch(n){throw this.logError("Failed to fetch latest listen:",n),n}}},{key:"getErrorMessage",value:function(n){return n.error?n.error:n.message?n.message:n.toString()||"Unknown error"}}]),t})(re);const T=(function(){function e(){M(this,e),this.serviceInstances=new Map}return z(e,[{key:"getService",value:function(t){this.serviceInstances||(this.serviceInstances=new Map);const n=t||o.service;if(!n)throw new Error("[ServiceFactory] No service type specified and no default service configured");return this.serviceInstances.has(n)||this.serviceInstances.set(n,this.createService(n)),this.serviceInstances.get(n)}},{key:"getCurrentService",value:function(){return this.getService(o.service)}},{key:"createService",value:function(t){switch(t){case"lastfm":return new We;case"librefm":return new Je;case"listenbrainz":return new Qe;default:throw new Error(`[ServiceFactory] Unknown service type: ${t}`)}}},{key:"clearCache",value:function(){this.serviceInstances?this.serviceInstances.clear():this.serviceInstances=new Map}},{key:"validateCurrentService",value:function(){return this.getCurrentService().validateCredentials()}},{key:"testService",value:async function(t){try{return await this.getService(t).validateCredentials()}catch(n){return console.error(`[ServiceFactory] Failed to test ${t}:`,n),!1}}},{key:"getSupportedServices",value:function(){return["lastfm","librefm","listenbrainz"]}},{key:"getServiceDisplayName",value:function(t){return this.getService(t).getServiceName()}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance();function Se(e){if(!e||e<0)return"0:00";const t=Math.floor(e/3600),n=Math.floor(e%3600/60),i=Math.floor(e%60);return t>0?`${t}:${n.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${n}:${i.toString().padStart(2,"0")}`}function Ee(){return Math.floor(Date.now()/1e3)}var Ze=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(Ze||{});const I=function(...e){return console.log("[Scrobble Plugin]",...e)},O=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},C=function(...e){return o.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ie=(function(){function e(){M(this,e)}return z(e,[{key:"updateActivity",value:async function(){if(y.pluginStopped){C("Plugin is stopped, skipping update"),this.stopUpdates();return}const t=T.getCurrentService().getServiceName();C(`Fetching latest track from ${t}...`);let n=!1;try{if(o.ignoreSpotify){if(fe.findActivity(function(c){return c.sync_id})){C("Spotify is currently playing, clearing activity"),D("isSpotifyIgnored",!0),Y();return}D("isSpotifyIgnored",!1)}if(o.ignoreYouTubeMusic){if(fe.findActivity(function(c){return c.name==="YouTube Music"||c.application_id==="463097721130188830"||c.name&&c.name.toLowerCase().includes("youtube music")})){C("YouTube Music is currently playing, clearing activity"),D("isYouTubeMusicIgnored",!0),Y();return}D("isYouTubeMusicIgnored",!1)}ye();const i=await T.getCurrentService().fetchLatestScrobble();if(D("lastTrack",i),!i.nowPlaying){C("Track is not currently playing"),Y();return}if(y.lastTrackUrl===i.url){C("Track hasn't changed"),ve(),this.consecutiveFailures=0;return}n=!0,I(`\u{1F3B5} Track changed: ${i.artist} - ${i.name}`);let a;if(i.nowPlaying&&o.showTimestamp&&i.from){const c=Ee();let g=i.from;if(g0){const d=Math.min(i.duration*.1,30);g=c-d}else g=c;C("had to estimate start time")}a={start:g*1e3},i.to&&(a.end=i.to*1e3)}C(`\u{1F3AF} Preparing RPC update for: ${i.artist} - ${i.name}`);const s={name:o.appName||l.DEFAULT_APP_NAME,flags:0,type:o.listeningTo?2:0,details:i.name,state:`by ${i.artist}`,status_display_type:1,application_id:l.APPLICATION_ID};if(s.name.includes("{{")){const c={artist:i.artist,name:i.name,album:i.album,service:t};for(const[g,d]of Object.entries(c))s.name=s.name.replace(new RegExp(`{{${g}}}`,"g"),d||"")}if(a&&(s.timestamps=a,C("Timestamps set:",{start:new Date(a.start).toISOString(),end:a.end?new Date(a.end).toISOString():"none",duration:i.duration?Se(i.duration):"unknown"})),i.album||i.albumArt){const c=i.albumArt?[i.albumArt]:[],g=await qe(c);if(g[0]){const d=i.album?`on ${i.album}`:`${i.artist} - ${i.name}`,v=o.showTimestamp&&i.duration?` \u2022 ${Se(i.duration)}`:"";s.assets={large_image:g[0],large_text:d+v},C("Album art set:",g[0])}else i.album&&(s.assets={large_text:`on ${i.album}`})}C("Setting Discord activity:",s),D("lastActivity",s),await be(s),y.lastTrackUrl=i.url,this.currentActivity=s,y.lastActivity=s,this.consecutiveFailures=0,this.lastUpdateTime=Ee(),ve(),I(`\u2705 RPC updated successfully: ${i.artist} - ${i.name}`)}catch(i){O("Update failed:",i),j(o.service,i.message),this.handleError(i)}}},{key:"handleError",value:function(t){this.consecutiveFailures++,D("lastError",t),O(`Failure ${this.consecutiveFailures}/${l.MAX_RETRY_ATTEMPTS}:`,t.message),this.consecutiveFailures>=l.MAX_RETRY_ATTEMPTS&&(O("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var t=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),I("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){I("Attempting to reconnect..."),t.initialize().then(function(){I("Reconnection successful!"),t.stopReconnection()}).catch(function(n){O("Reconnection attempt failed:",n.message)})},l.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(y.pluginStopped)throw new Error("Plugin is stopped");const n=T.getCurrentService().getServiceName();I(`Initializing with ${n}...`);try{if(!await T.validateCurrentService())throw new Error(`Invalid credentials for ${n}`);I(`${n} credentials validated successfully`)}catch(a){throw O(`Failed to validate ${n} credentials:`,a),a}this.stopUpdates(),I("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const i=Math.max((Number(o.timeInterval)||l.DEFAULT_SETTINGS.timeInterval)*1e3,l.MIN_UPDATE_INTERVAL*1e3);this.updateTimer=setInterval(function(){return t.updateActivity()},i),I(`Update timer started with interval: ${i}ms (${i/1e3}s)`)}},{key:"stop",value:function(){if(!y.pluginStopped){I("Stopping plugin..."),y.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),Y(),I("Plugin stopped successfully")}catch(t){console.error("[Scrobble Plugin] Stop error:",t)}}}},{key:"switchService",value:async function(t){if(y.pluginStopped)return;I(`Switching to ${t}...`);const n=!y.pluginStopped;this.stop();try{T.clearCache(),y.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,n&&(y.pluginStopped=!1,await this.initialize())}catch(i){O("Failed to switch service:",i)}}},{key:"getStatus",value:function(){const t=T.getCurrentService().getServiceName();return{running:!y.pluginStopped,service:t,consecutiveFailures:this.consecutiveFailures,isReconnecting:this.isReconnecting,lastTrackUrl:y.lastTrackUrl,updateInterval:this.updateTimer?"Active":"Inactive"}}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance(),et=function(){return ie.initialize()},ae=function(){return ie.stop()},tt=function(e){return ie.switchService(e)};var we,Re,Te,Ie,Ae,ke,Le,Ce,Pe,Ne,$e,Ue,_e,De;const{ScrollView:B}=$.findByProps("ScrollView"),{TableRowGroup:E,TableSwitchRow:V,Stack:P,TableRow:u}=$.findByProps("TableSwitchRow","TableRowGroup","Stack","TableRow"),{TextInput:U}=$.findByProps("TextInput");(we=f.plugin.storage).username??(we.username=""),(Re=f.plugin.storage).apiKey??(Re.apiKey=""),(Te=f.plugin.storage).appName??(Te.appName="Music"),(Ie=f.plugin.storage).timeInterval??(Ie.timeInterval=5),(Ae=f.plugin.storage).showTimestamp??(Ae.showTimestamp=!0),(ke=f.plugin.storage).listeningTo??(ke.listeningTo=!0),(Le=f.plugin.storage).ignoreSpotify??(Le.ignoreSpotify=!0),(Ce=f.plugin.storage).verboseLogging??(Ce.verboseLogging=!1),(Pe=f.plugin.storage).service??(Pe.service="lastfm"),(Ne=f.plugin.storage).librefmUsername??(Ne.librefmUsername=""),($e=f.plugin.storage).librefmApiKey??($e.librefmApiKey=""),(Ue=f.plugin.storage).listenbrainzUsername??(Ue.listenbrainzUsername=""),(_e=f.plugin.storage).listenbrainzToken??(_e.listenbrainzToken=""),(De=f.plugin.storage).addToSidebar??(De.addToSidebar=!0);const h=function(e,t){return f.plugin.storage[e]??t},w=function(e,t){return f.plugin.storage[e]=t};function nt(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing Last.fm connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("lastfm")?S.showToast("\u2705 Last.fm connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C Last.fm connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"Last.fm Username",value:h("username"),onChange:function(n){w("username",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"Last.fm API Key",value:h("apiKey"),onChange:function(n){w("apiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),S.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function rt(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing Libre.fm connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("librefm")?S.showToast("\u2705 Libre.fm connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C Libre.fm connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"Libre.fm Username",value:h("librefmUsername"),onChange:function(n){w("librefmUsername",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"Libre.fm API Key",value:h("librefmApiKey"),onChange:function(n){w("librefmApiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),S.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function it(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing ListenBrainz connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("listenbrainz")?S.showToast("\u2705 ListenBrainz connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C ListenBrainz connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"ListenBrainz Username",value:h("listenbrainzUsername"),onChange:function(n){w("listenbrainzUsername",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"ListenBrainz Token (for private profiles)",value:h("listenbrainzToken"),onChange:function(n){w("listenbrainzToken",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://listenbrainz.org/settings/")}catch(n){console.error("Failed to open ListenBrainz settings URL:",n),S.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function at(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Activity Display"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:`App Name (Default: ${l.DEFAULT_SETTINGS.appName})`,value:h("appName",l.DEFAULT_SETTINGS.appName),onChange:function(t){w("appName",t),e()},isClearable:!0}),r.React.createElement(U,{placeholder:`Update Interval (Default: ${l.DEFAULT_SETTINGS.timeInterval}s)`,value:String(h("timeInterval",l.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const n=Number(t);n>=l.MIN_UPDATE_INTERVAL&&(w("timeInterval",n),e())},keyboardType:"numeric",isClearable:!0}))),r.React.createElement(E,{title:"Activity Options"},r.React.createElement(V,{label:"Show as Listening",subLabel:"Display as 'Listening to' instead of 'Playing'",value:h("listeningTo",l.DEFAULT_SETTINGS.listeningTo),onValueChange:function(t){w("listeningTo",t),e()}}),r.React.createElement(V,{label:"Show Timestamp",subLabel:"Display track progress and duration",value:h("showTimestamp",l.DEFAULT_SETTINGS.showTimestamp),onValueChange:function(t){w("showTimestamp",t),e()}}),r.React.createElement(V,{label:"Ignore Spotify",subLabel:"Don't show activity when Spotify is playing",value:h("ignoreSpotify",l.DEFAULT_SETTINGS.ignoreSpotify),onValueChange:function(t){w("ignoreSpotify",t),e()}}),r.React.createElement(V,{label:"Ignore YouTube Music",subLabel:"Don't show activity when YouTube Music is playing (PC client)",value:h("ignoreYouTubeMusic",l.DEFAULT_SETTINGS.ignoreYouTubeMusic),onValueChange:function(t){w("ignoreYouTubeMusic",t),e()}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function st(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Logging Options"},r.React.createElement(V,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:h("verboseLogging",l.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(t){w("verboseLogging",t),e()}})),r.React.createElement(E,{title:"Debug Information"},r.React.createElement(u,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),r.React.createElement(u,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),r.React.createElement(E,{title:"Log Information"},r.React.createElement(u,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),r.React.createElement(u,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),r.React.createElement(u,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function Ve(){F.useProxy(f.plugin.storage);const e=r.NavigationNative.useNavigation(),[,t]=r.React.useReducer(function(a){return a+1},0),n=h("service"),i=function(a){switch(a){case"lastfm":return h("username")&&h("apiKey")?"\u2705 Configured":"\u274C Missing credentials";case"librefm":return h("librefmUsername")&&h("librefmApiKey")?"\u2705 Configured":"\u274C Missing credentials";case"listenbrainz":return h("listenbrainzUsername")?"\u2705 Configured":"\u274C Missing username";default:return"\u2753 Unknown"}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Active Service"},r.React.createElement(u,{label:"Current Service",subLabel:n?`Using: ${T.getServiceDisplayName(n)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(a){return r.React.createElement(V,{key:a,label:T.getServiceDisplayName(a),subLabel:i(a),value:n===a,onValueChange:function(s){s&&a!==n&&(w("service",a),t())}})})),r.React.createElement(E,{title:"Service Configuration"},r.React.createElement(u,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:nt})}}),r.React.createElement(u,{label:"Libre.fm Settings",subLabel:"Configure Libre.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:rt})}}),r.React.createElement(u,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:it})}})),r.React.createElement(E,{title:"Plugin Configuration"},r.React.createElement(u,{label:"Display Settings",subLabel:"Customize how music activity appears",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:at})}}),r.React.createElement(u,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:st})}}),r.React.createElement(V,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:h("addToSidebar",!1),onValueChange:function(a){w("addToSidebar",a),t()}})),r.React.createElement(E,{title:"About"},r.React.createElement(u,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),r.React.createElement(u,{label:"Author",subLabel:"kmmiio99o"}),r.React.createElement(u,{label:"Version",subLabel:"1.1.1"}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}const{FormSection:ot,FormRow:se}=Ge.Forms,{TableRowIcon:ct}=$.findByProps("TableRowIcon"),H=window.bunny,Fe=H?.metro?.findByPropsLazy("getRootNavigationRef"),oe=H?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),Me=H?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function lt({tabs:e}){const t=r.NavigationNative.useNavigation();return r.React.createElement(se,{label:e.title(),leading:r.React.createElement(se.Icon,{source:e.icon}),trailing:r.React.createElement(r.React.Fragment,{},[e.trailing?e.trailing():null,r.React.createElement(se.Arrow,{key:"arrow"})]),onPress:function(){const n=e.page;t.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(n)}})}})}function ut(e,t){try{t.push(Q.after("default",H?.metro?.findByNameLazy("UserSettingsOverviewWrapper",!1),function(n,i){const a=Z.findInReactTree(i.props.children,function(s){return s.type?.name==="UserSettingsOverview"});a&&t.push(Q.after("render",a.type.prototype,function(s,c){const g=Z.findInReactTree(c.props.children,function(d){return d?.children?.[1]?.type===ot})?.children;if(g){const d=g.findIndex(function(v){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(v?.props?.label)});g.splice(-~d||4,0,r.React.createElement(lt,{key:e.key,tabs:e}))}}))},!0))}catch(n){f.logger.info("Panel UI patch failed graciously \u{1F494}",n)}}function gt(e,t){if(!oe||!Me||!Fe){console.warn("[LastFm] Missing required constants for tabs UI patch");return}const n={};n[e.key]={type:"pressable",title:e.title,icon:e.icon,IconComponent:e.icon&&function(){const s=typeof e.icon=="object"&&e.icon.uri!==void 0?e.icon.uri:e.icon;return r.React.createElement(ct,{source:s})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=Fe.getRootNavigationRef(),c=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(c)}})},withArrow:!0};let i=oe.SETTING_RENDERER_CONFIG;Object.defineProperty(oe,"SETTING_RENDERER_CONFIG",{enumerable:!0,configurable:!0,get:function(){return{...i,...n}},set:function(s){return i=s}});const a=Symbol("LastFm-pinToSettings");t.push(Q.after("default",Me,function(s,c){if(!s[0][a]){s[0][a]=!0;const{sections:g}=Z.findInReactTree(c,function(v){return v.props?.sections}).props,d=g?.find(function(v){return["Bunny","Revenge","Kettu","Vencore"].some(function(A){return v.label===A&&v.title===A})});d?.settings&&(d.settings=[...d.settings,e.key])}}))}function ft(e){const t=[];let n=!1;const i=e.predicate||function(){return!0};return e.predicate=function(){return n?!1:i()},ut(e,t),gt(e,t),t.push(function(){return n=!0}),function(){for(const a of t)a()}}function ze(){if(!f.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=ft({key:"LastFmScrobbler",icon:m.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return f.plugin.storage.addToSidebar===!0},page:Ve});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var Oe;const y={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0},ce=[];let _;const Be=l.DEFAULT_SETTINGS;for(const e of Object.keys(Be))f.plugin.storage[e]=f.plugin.storage[e]??Be[e];(Oe=f.plugin.storage).addToSidebar??(Oe.addToSidebar=!1);const o=new Proxy(f.plugin.storage,{get(e,t){return e[t]},set(e,t,n){return e[t]=n,!0}});let W=0;const le=3,dt=5e3,b=function(...e){return console.log("[Scrobble Plugin]",...e)},ue=function(...e){return console.error("[Scrobble Plugin] Error:",...e)};async function K(){try{await et(),W=0,b("Plugin initialized successfully")}catch(e){ue("Initialization failed:",e),W++,W10&&(p.serviceErrors[e]=p.serviceErrors[e].slice(-10)),he(`${e} error:`,t)}function ve(){p.lastSuccessfulUpdate=new Date().toISOString(),q("Successful update recorded at:",p.lastSuccessfulUpdate)}function X(e){return X=Object.setPrototypeOf?Object.getPrototypeOf:function(n){return n.__proto__||Object.getPrototypeOf(n)},X(e)}function me(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(me=function(){return!!e})()}function je(e){if(e===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function Xe(e){"@swc/helpers - typeof";return e&&typeof Symbol<"u"&&e.constructor===Symbol?"symbol":typeof e}function He(e,t){return t&&(Xe(t)==="object"||typeof t=="function")?t:je(e)}function ee(e,t,n){return t=X(t),He(e,me()?Reflect.construct(t,n||[],X(e).constructor):t.apply(e,n))}function te(e,t){return te=Object.setPrototypeOf||function(i,a){return i.__proto__=a,i},te(e,t)}function ne(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&&te(e,t)}let re=(function(){function e(){M(this,e)}return z(e,[{key:"log",value:function(...t){console.log(`[${this.getServiceName()}]`,...t)}},{key:"logError",value:function(...t){console.error(`[${this.getServiceName()}] Error:`,...t)}},{key:"logVerbose",value:function(...t){console.log(`[${this.getServiceName()}] Verbose:`,...t)}},{key:"handleError",value:async function(t){this.lastError=t.error||0;const n=this.getErrorMessage(t);throw this.logError(n),j(this.getServiceName().toLowerCase(),n),new Error(`${this.getServiceName()} API Error: ${n}`)}},{key:"getErrorMessage",value:function(t){return t.error&&l.API_ERROR_CODES[t.error]?l.API_ERROR_CODES[t.error]:t.message||t.toString()||"Unknown error"}},{key:"makeRequest",value:async function(t,n={}){try{this.logVerbose(`Making request to: ${t}`),ye();const i=await fetch(t,{...n,headers:{"User-Agent":"Vendetta Multi-Service Scrobbler/3.0.0",...n.headers}});if(!i.ok){const s=new Error(`HTTP ${i.status}: ${i.statusText}`);throw j(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>l.MAX_RETRY_ATTEMPTS)throw this.retryCount=0,j(this.getServiceName().toLowerCase(),`Max retries exceeded: ${i.message}`),i;return this.logVerbose(`Request failed, retrying (${this.retryCount}/${l.MAX_RETRY_ATTEMPTS})`),await new Promise(function(a){return setTimeout(a,l.RETRY_DELAY)}),this.makeRequest(t,n)}}},{key:"isDefaultCover",value:function(t){return t?l.DEFAULT_COVER_HASHES.some(function(n){return t.includes(n)}):!0}},{key:"processAlbumArt",value:function(t){return!t||this.isDefaultCover(t)?null:t}},{key:"getLastError",value:function(){return this.lastError}},{key:"resetRetryCount",value:function(){this.retryCount=0}}]),e})(),We=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"Last.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{if(!o.username||!o.apiKey)throw new Error("Username or API key not set");const n=new URLSearchParams({method:"user.getinfo",user:o.username,api_key:o.apiKey,format:"json"}),i=`${l.SERVICES.lastfm.baseUrl}?${n}`;return await this.makeRequest(i),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{if(!o.username||!o.apiKey)throw new Error("Username or API key not set");this.logVerbose("Fetching latest scrobble for user:",o.username);const n=new URLSearchParams({method:"user.getrecenttracks",user:o.username,api_key:o.apiKey,limit:"1",extended:"1",format:"json"}),i=`${l.SERVICES.lastfm.baseUrl}?${n}`,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,c=a.date?.uts?parseInt(a.date.uts):Math.floor(Date.now()/1e3);let g=null,d=null;if(s)try{const k=new URLSearchParams({method:"track.getInfo",track:a.name,artist:a.artist.name,api_key:o.apiKey,format:"json"}),L=`${l.SERVICES.lastfm.baseUrl}?${k}`,R=await this.makeRequest(L);R?.track?.duration&&(g=parseInt(R.track.duration),g>0&&(g=Math.floor(g/1e3),d=c+g))}catch(k){this.logVerbose("Failed to fetch track duration:",k)}const v=this.processAlbumArt(a.image?.find(function(k){return k.size==="large"})?.["#text"]),A={name:a.name,artist:a.artist.name,album:a.album["#text"],albumArt:v,url:a.url,date:a.date?.["#text"]??"now",nowPlaying:s,loved:a.loved==="1",from:c,to:d,duration:g};return this.logVerbose("Processed track:",A),this.log(`${s?"Now playing":"Last played"}:`,`${A.artist} - ${A.name}`),A}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(re),Je=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"Libre.fm"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.librefmUsername,i=o.librefmApiKey;if(!n||!i)throw new Error("Username or API key not set for Libre.fm");const a=new URLSearchParams({method:"user.getinfo",user:n,api_key:i,format:"json"}),s=`${l.SERVICES.librefm.baseUrl}?${a}`;return await this.makeRequest(s),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=o.librefmUsername,i=o.librefmApiKey;if(!n||!i)throw new Error("Username or API key not set for Libre.fm");this.logVerbose("Fetching latest scrobble for user:",n);const a=new URLSearchParams({method:"user.getrecenttracks",user:n,api_key:i,limit:"1",extended:"1",format:"json"}),s=`${l.SERVICES.librefm.baseUrl}?${a}`,c=(await this.makeRequest(s))?.recenttracks?.track?.[0];if(!c)throw new Error("No tracks found");this.logVerbose("Raw track data:",c);const g=!!c["@attr"]?.nowplaying,d=c.date?.uts?parseInt(c.date.uts):Math.floor(Date.now()/1e3);let v=null,A=null;if(g)try{const R=new URLSearchParams({method:"track.getInfo",track:c.name,artist:c.artist.name,api_key:i,format:"json"}),N=`${l.SERVICES.librefm.baseUrl}?${R}`,xe=await this.makeRequest(N);xe?.track?.duration&&(v=parseInt(xe.track.duration),v>0&&(v=Math.floor(v/1e3),A=d+v))}catch(R){this.logVerbose("Failed to fetch track duration:",R)}const k=this.processAlbumArt(c.image?.find(function(R){return R.size==="large"})?.["#text"]),L={name:c.name,artist:c.artist.name,album:c.album["#text"],albumArt:k,url:c.url,date:c.date?.["#text"]??"now",nowPlaying:g,loved:c.loved==="1",from:d,to:A,duration:v};return this.logVerbose("Processed track:",L),this.log(`${g?"Now playing":"Last played"}:`,`${L.artist} - ${L.name}`),L}catch(n){throw this.logError("Failed to fetch latest scrobble:",n),n}}}]),t})(re),Qe=(function(e){ne(t,e);function t(){return M(this,t),ee(this,t,arguments)}return z(t,[{key:"getServiceName",value:function(){return"ListenBrainz"}},{key:"logVerbose",value:function(...n){o.verboseLogging&&console.log(`[${this.getServiceName()}] Verbose:`,...n)}},{key:"validateCredentials",value:async function(){try{const n=o.listenbrainzUsername,i=o.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");const a=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,s={};return i&&(s.Authorization=`Token ${i}`),await this.makeRequest(a,{headers:s}),this.log("Credentials validation successful"),!0}catch(n){return this.logError("Credentials validation failed:",n),!1}}},{key:"fetchLatestScrobble",value:async function(){try{const n=o.listenbrainzUsername,i=o.listenbrainzToken;if(!n)throw new Error("Username not set for ListenBrainz");this.logVerbose("Fetching latest scrobble for user:",n);let a=null;try{const L=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/playing-now`,R={};i&&(R.Authorization=`Token ${i}`);const N=await this.makeRequest(L,{headers:R});N.listens&&N.listens.length>0&&(a=N.listens[0],a.playing_now=!0)}catch(L){this.logVerbose("No currently playing track or failed to fetch:",L)}let s;if(a)s=a,this.logVerbose("Using currently playing track");else{const L=`${l.SERVICES.listenbrainz.baseUrl}/user/${encodeURIComponent(n)}/listens?count=1`,R={};i&&(R.Authorization=`Token ${i}`);const N=await this.makeRequest(L,{headers:R});if(!N.listens||N.listens.length===0)throw new Error("No listens found");s=N.listens[0],this.logVerbose("Using latest completed listen")}this.logVerbose("Raw listen data:",s);const c=!!s.playing_now,g=s.listened_at||Math.floor(Date.now()/1e3);let d=null,v=null;s.track_metadata.additional_info?.duration_ms&&(d=Math.floor(s.track_metadata.additional_info.duration_ms/1e3),c&&d>0&&(v=g+d));const A=null;s.track_metadata.additional_info?.release_mbid;const k={name:s.track_metadata.track_name,artist:s.track_metadata.artist_name,album:s.track_metadata.release_name||"",albumArt:A,url:s.track_metadata.additional_info?.origin_url||`https://listenbrainz.org/user/${n}`,date:c?"now":new Date(g*1e3).toISOString(),nowPlaying:c,loved:!1,from:g,to:v,duration:d};return this.logVerbose("Processed track:",k),this.log(`${c?"Now playing":"Last played"}:`,`${k.artist} - ${k.name}`),k}catch(n){throw this.logError("Failed to fetch latest listen:",n),n}}},{key:"getErrorMessage",value:function(n){return n.error?n.error:n.message?n.message:n.toString()||"Unknown error"}}]),t})(re);const T=(function(){function e(){M(this,e),this.serviceInstances=new Map}return z(e,[{key:"getService",value:function(t){this.serviceInstances||(this.serviceInstances=new Map);const n=t||o.service;if(!n)throw new Error("[ServiceFactory] No service type specified and no default service configured");return this.serviceInstances.has(n)||this.serviceInstances.set(n,this.createService(n)),this.serviceInstances.get(n)}},{key:"getCurrentService",value:function(){return this.getService(o.service)}},{key:"createService",value:function(t){switch(t){case"lastfm":return new We;case"librefm":return new Je;case"listenbrainz":return new Qe;default:throw new Error(`[ServiceFactory] Unknown service type: ${t}`)}}},{key:"clearCache",value:function(){this.serviceInstances?this.serviceInstances.clear():this.serviceInstances=new Map}},{key:"validateCurrentService",value:function(){return this.getCurrentService().validateCredentials()}},{key:"testService",value:async function(t){try{return await this.getService(t).validateCredentials()}catch(n){return console.error(`[ServiceFactory] Failed to test ${t}:`,n),!1}}},{key:"getSupportedServices",value:function(){return["lastfm","librefm","listenbrainz"]}},{key:"getServiceDisplayName",value:function(t){return this.getService(t).getServiceName()}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance();function Se(e){if(!e||e<0)return"0:00";const t=Math.floor(e/3600),n=Math.floor(e%3600/60),i=Math.floor(e%60);return t>0?`${t}:${n.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${n}:${i.toString().padStart(2,"0")}`}function Ee(){return Math.floor(Date.now()/1e3)}var Ze=(function(e){return e[e.PLAYING=0]="PLAYING",e[e.STREAMING=1]="STREAMING",e[e.LISTENING=2]="LISTENING",e[e.COMPETING=5]="COMPETING",e})(Ze||{});const I=function(...e){return console.log("[Scrobble Plugin]",...e)},O=function(...e){return console.error("[Scrobble Plugin] Error:",...e)},C=function(...e){return o.verboseLogging&&console.log("[Scrobble Plugin] Verbose:",...e)},ie=(function(){function e(){M(this,e)}return z(e,[{key:"updateActivity",value:async function(){if(y.pluginStopped){C("Plugin is stopped, skipping update"),this.stopUpdates();return}const t=T.getCurrentService().getServiceName();C(`Fetching latest track from ${t}...`);let n=!1;try{if(o.ignoreSpotify){if(fe.findActivity(function(c){return c.sync_id})){C("Spotify is currently playing, clearing activity"),D("isSpotifyIgnored",!0),Y();return}D("isSpotifyIgnored",!1)}if(o.ignoreYouTubeMusic){if(fe.findActivity(function(c){return c.name==="YouTube Music"||c.application_id==="463097721130188830"||c.name&&c.name.toLowerCase().includes("youtube music")})){C("YouTube Music is currently playing, clearing activity"),D("isYouTubeMusicIgnored",!0),Y();return}D("isYouTubeMusicIgnored",!1)}ye();const i=await T.getCurrentService().fetchLatestScrobble();if(D("lastTrack",i),!i.nowPlaying){C("Track is not currently playing"),Y();return}if(y.lastTrackUrl===i.url){C("Track hasn't changed"),ve(),this.consecutiveFailures=0;return}n=!0,I(`\u{1F3B5} Track changed: ${i.artist} - ${i.name}`);let a;if(i.nowPlaying&&o.showTimestamp&&i.from){const c=Ee();let g=i.from;if(g0){const d=Math.min(i.duration*.1,30);g=c-d}else g=c;C("had to estimate start time")}a={start:g*1e3},i.to&&(a.end=i.to*1e3)}C(`\u{1F3AF} Preparing RPC update for: ${i.artist} - ${i.name}`);const s={name:o.appName||l.DEFAULT_APP_NAME,flags:0,type:o.listeningTo?2:0,details:i.name,state:`by ${i.artist}`,status_display_type:1,application_id:l.APPLICATION_ID};if(s.name.includes("{{")){const c={artist:i.artist,name:i.name,album:i.album,service:t};for(const[g,d]of Object.entries(c))s.name=s.name.replace(new RegExp(`{{${g}}}`,"g"),d||"")}if(a&&(s.timestamps=a,C("Timestamps set:",{start:new Date(a.start).toISOString(),end:a.end?new Date(a.end).toISOString():"none",duration:i.duration?Se(i.duration):"unknown"})),i.album||i.albumArt){const c=i.albumArt?[i.albumArt]:[],g=await qe(c);if(g[0]){const d=i.album?`on ${i.album}`:`${i.artist} - ${i.name}`,v=o.showTimestamp&&i.duration?` \u2022 ${Se(i.duration)}`:"";s.assets={large_image:g[0],large_text:d+v},C("Album art set:",g[0])}else i.album&&(s.assets={large_text:`on ${i.album}`})}C("Setting Discord activity:",s),D("lastActivity",s),await be(s),y.lastTrackUrl=i.url,this.currentActivity=s,y.lastActivity=s,this.consecutiveFailures=0,this.lastUpdateTime=Ee(),ve(),I(`\u2705 RPC updated successfully: ${i.artist} - ${i.name}`)}catch(i){O("Update failed:",i),j(o.service,i.message),this.handleError(i)}}},{key:"handleError",value:function(t){this.consecutiveFailures++,D("lastError",t),O(`Failure ${this.consecutiveFailures}/${l.MAX_RETRY_ATTEMPTS}:`,t.message),this.consecutiveFailures>=l.MAX_RETRY_ATTEMPTS&&(O("Max retry attempts reached, initiating reconnection..."),this.startReconnection())}},{key:"startReconnection",value:function(){var t=this;this.isReconnecting||(this.isReconnecting=!0,this.stopUpdates(),I("Starting reconnection process..."),this.reconnectTimer=setInterval(function(){I("Attempting to reconnect..."),t.initialize().then(function(){I("Reconnection successful!"),t.stopReconnection()}).catch(function(n){O("Reconnection attempt failed:",n.message)})},l.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(y.pluginStopped)throw new Error("Plugin is stopped");const n=T.getCurrentService().getServiceName();I(`Initializing with ${n}...`);try{if(!await T.validateCurrentService())throw new Error(`Invalid credentials for ${n}`);I(`${n} credentials validated successfully`)}catch(a){throw O(`Failed to validate ${n} credentials:`,a),a}this.stopUpdates(),I("\u{1F3B5} checking for already playing songs..."),await this.updateActivity();const i=Math.max((Number(o.timeInterval)||l.DEFAULT_SETTINGS.timeInterval)*1e3,l.MIN_UPDATE_INTERVAL*1e3);this.updateTimer=setInterval(function(){return t.updateActivity()},i),I(`Update timer started with interval: ${i}ms (${i/1e3}s)`)}},{key:"stop",value:function(){if(!y.pluginStopped){I("Stopping plugin..."),y.pluginStopped=!0;try{this.stopUpdates(),this.stopReconnection(),Y(),I("Plugin stopped successfully")}catch(t){console.error("[Scrobble Plugin] Stop error:",t)}}}},{key:"switchService",value:async function(t){if(y.pluginStopped)return;I(`Switching to ${t}...`);const n=!y.pluginStopped;this.stop();try{T.clearCache(),y.lastTrackUrl=void 0,this.currentActivity=void 0,this.lastUpdateTime=0,n&&(y.pluginStopped=!1,await this.initialize())}catch(i){O("Failed to switch service:",i)}}},{key:"getStatus",value:function(){const t=T.getCurrentService().getServiceName();return{running:!y.pluginStopped,service:t,consecutiveFailures:this.consecutiveFailures,isReconnecting:this.isReconnecting,lastTrackUrl:y.lastTrackUrl,updateInterval:this.updateTimer?"Active":"Inactive"}}}],[{key:"getInstance",value:function(){return e.instance||(e.instance=new e),e.instance}}]),e})().getInstance(),et=function(){return ie.initialize()},ae=function(){return ie.stop()},tt=function(e){return ie.switchService(e)};var we,Re,Te,Ie,Ae,ke,Le,Ce,Pe,Ne,$e,Ue,_e,De;const{ScrollView:B}=$.findByProps("ScrollView"),{TableRowGroup:E,TableSwitchRow:V,Stack:P,TableRow:u}=$.findByProps("TableSwitchRow","TableRowGroup","Stack","TableRow"),{TextInput:U}=$.findByProps("TextInput");(we=f.plugin.storage).username??(we.username=""),(Re=f.plugin.storage).apiKey??(Re.apiKey=""),(Te=f.plugin.storage).appName??(Te.appName="Music"),(Ie=f.plugin.storage).timeInterval??(Ie.timeInterval=5),(Ae=f.plugin.storage).showTimestamp??(Ae.showTimestamp=!0),(ke=f.plugin.storage).listeningTo??(ke.listeningTo=!0),(Le=f.plugin.storage).ignoreSpotify??(Le.ignoreSpotify=!0),(Ce=f.plugin.storage).verboseLogging??(Ce.verboseLogging=!1),(Pe=f.plugin.storage).service??(Pe.service="lastfm"),(Ne=f.plugin.storage).librefmUsername??(Ne.librefmUsername=""),($e=f.plugin.storage).librefmApiKey??($e.librefmApiKey=""),(Ue=f.plugin.storage).listenbrainzUsername??(Ue.listenbrainzUsername=""),(_e=f.plugin.storage).listenbrainzToken??(_e.listenbrainzToken=""),(De=f.plugin.storage).addToSidebar??(De.addToSidebar=!0);const h=function(e,t){return f.plugin.storage[e]??t},w=function(e,t){return f.plugin.storage[e]=t};function nt(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing Last.fm connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("lastfm")?S.showToast("\u2705 Last.fm connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C Last.fm connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"Last.fm Username",value:h("username"),onChange:function(n){w("username",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"Last.fm API Key",value:h("apiKey"),onChange:function(n){w("apiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Last.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key at last.fm/api/account/create",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),S.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function rt(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing Libre.fm connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("librefm")?S.showToast("\u2705 Libre.fm connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C Libre.fm connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"Libre.fm Username",value:h("librefmUsername"),onChange:function(n){w("librefmUsername",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"Libre.fm API Key",value:h("librefmApiKey"),onChange:function(n){w("librefmApiKey",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your Libre.fm credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get API Key",subLabel:"Create a Last.fm API key (compatible with Libre.fm)",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://www.last.fm/api/account/create")}catch(n){console.error("Failed to open Last.fm API URL:",n),S.showToast("Failed to open web browser. Please visit: https://www.last.fm/api/account/create",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function it(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(n){return n+1},0),t=async function(){S.showToast("Testing ListenBrainz connection...",m.getAssetIDByName("ClockIcon"));try{await T.testService("listenbrainz")?S.showToast("\u2705 ListenBrainz connection successful!",m.getAssetIDByName("CheckIcon")):S.showToast("\u274C ListenBrainz connection failed",m.getAssetIDByName("XIcon"))}catch{S.showToast("\u274C Connection error",m.getAssetIDByName("XIcon"))}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Credentials"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:"ListenBrainz Username (optional, uses Last.fm if empty)",value:h("listenbrainzUsername"),onChange:function(n){w("listenbrainzUsername",n),e()},isClearable:!0}),r.React.createElement(U,{placeholder:"ListenBrainz Token (optional, for private profiles)",value:h("listenbrainzToken"),onChange:function(n){w("listenbrainzToken",n),e()},secureTextEntry:!0,isClearable:!0}))),r.React.createElement(E,{title:"Actions"},r.React.createElement(u,{label:"Test Connection",subLabel:"Verify your ListenBrainz credentials",trailing:r.React.createElement(u.Arrow,null),onPress:t}),r.React.createElement(u,{label:"Get User Token",subLabel:"Get your ListenBrainz user token at listenbrainz.org/settings/",trailing:r.React.createElement(u.Arrow,null),onPress:async function(){try{await J.Linking.openURL("https://listenbrainz.org/settings/")}catch(n){console.error("Failed to open ListenBrainz settings URL:",n),S.showToast("Failed to open web browser. Please visit: https://listenbrainz.org/settings/",m.getAssetIDByName("XIcon"))}}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function at(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Activity Display"},r.React.createElement(P,{spacing:4},r.React.createElement(U,{placeholder:`App Name (Default: ${l.DEFAULT_SETTINGS.appName})`,value:h("appName",l.DEFAULT_SETTINGS.appName),onChange:function(t){w("appName",t),e()},isClearable:!0}),r.React.createElement(U,{placeholder:`Update Interval (Default: ${l.DEFAULT_SETTINGS.timeInterval}s)`,value:String(h("timeInterval",l.DEFAULT_SETTINGS.timeInterval)),onChange:function(t){const n=Number(t);n>=l.MIN_UPDATE_INTERVAL&&(w("timeInterval",n),e())},keyboardType:"numeric",isClearable:!0}))),r.React.createElement(E,{title:"Activity Options"},r.React.createElement(V,{label:"Show as Listening",subLabel:"Display as 'Listening to' instead of 'Playing'",value:h("listeningTo",l.DEFAULT_SETTINGS.listeningTo),onValueChange:function(t){w("listeningTo",t),e()}}),r.React.createElement(V,{label:"Show Timestamp",subLabel:"Display track progress and duration",value:h("showTimestamp",l.DEFAULT_SETTINGS.showTimestamp),onValueChange:function(t){w("showTimestamp",t),e()}}),r.React.createElement(V,{label:"Ignore Spotify",subLabel:"Don't show activity when Spotify is playing",value:h("ignoreSpotify",l.DEFAULT_SETTINGS.ignoreSpotify),onValueChange:function(t){w("ignoreSpotify",t),e()}}),r.React.createElement(V,{label:"Ignore YouTube Music",subLabel:"Don't show activity when YouTube Music is playing (PC client)",value:h("ignoreYouTubeMusic",l.DEFAULT_SETTINGS.ignoreYouTubeMusic),onValueChange:function(t){w("ignoreYouTubeMusic",t),e()}}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function st(){F.useProxy(f.plugin.storage);const[,e]=r.React.useReducer(function(t){return t+1},0);return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Logging Options"},r.React.createElement(V,{label:"Verbose Logging",subLabel:"Enable detailed console logging for debugging",value:h("verboseLogging",l.DEFAULT_SETTINGS.verboseLogging),onValueChange:function(t){w("verboseLogging",t),e()}})),r.React.createElement(E,{title:"Debug Information"},r.React.createElement(u,{label:"Console Logging",subLabel:"Logs are written to the browser/app console when verbose is enabled"}),r.React.createElement(u,{label:"Error Tracking",subLabel:"Connection errors and API failures are automatically logged"})),r.React.createElement(E,{title:"Log Information"},r.React.createElement(u,{label:"API Calls",subLabel:"All API requests are logged when verbose is enabled"}),r.React.createElement(u,{label:"Track Updates",subLabel:"Song changes and RPC updates are logged"}),r.React.createElement(u,{label:"Error Details",subLabel:"Connection errors and retries are logged"}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}function Ve(){F.useProxy(f.plugin.storage);const e=r.NavigationNative.useNavigation(),[,t]=r.React.useReducer(function(a){return a+1},0),n=h("service"),i=function(a){switch(a){case"lastfm":return h("username")&&h("apiKey")?"\u2705 Configured":"\u274C Missing credentials";case"librefm":return h("librefmUsername")&&h("librefmApiKey")?"\u2705 Configured":"\u274C Missing credentials";case"listenbrainz":return h("listenbrainzUsername")?"\u2705 Configured":"\u274C Missing username";default:return"\u2753 Unknown"}};return r.React.createElement(B,{style:{flex:1},contentContainerStyle:{padding:10}},r.React.createElement(P,{spacing:8},r.React.createElement(E,{title:"Active Service"},r.React.createElement(u,{label:"Current Service",subLabel:n?`Using: ${T.getServiceDisplayName(n)}`:"No service selected"}),["lastfm","librefm","listenbrainz"].map(function(a){return r.React.createElement(V,{key:a,label:T.getServiceDisplayName(a),subLabel:i(a),value:n===a,onValueChange:function(s){s&&a!==n&&(w("service",a),t())}})})),r.React.createElement(E,{title:"Service Configuration"},r.React.createElement(u,{label:"Last.fm Settings",subLabel:"Configure Last.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Last.fm Settings",render:nt})}}),r.React.createElement(u,{label:"Libre.fm Settings",subLabel:"Configure Libre.fm credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Libre.fm Settings",render:rt})}}),r.React.createElement(u,{label:"ListenBrainz Settings",subLabel:"Configure ListenBrainz credentials and options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"ListenBrainz Settings",render:it})}})),r.React.createElement(E,{title:"Plugin Configuration"},r.React.createElement(u,{label:"Display Settings",subLabel:"Customize how music activity appears",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Display Settings",render:at})}}),r.React.createElement(u,{label:"Logging Settings",subLabel:"Configure logging and debugging options",trailing:r.React.createElement(u.Arrow,null),onPress:function(){return e.push("VendettaCustomPage",{title:"Logging Settings",render:st})}}),r.React.createElement(V,{label:"Add to Sidebar",subLabel:"Show plugin in Discord settings",value:h("addToSidebar",!1),onValueChange:function(a){w("addToSidebar",a),t()}})),r.React.createElement(E,{title:"About"},r.React.createElement(u,{label:"Multi Scrobbler",subLabel:"Show off your music status from multiple services"}),r.React.createElement(u,{label:"Author",subLabel:"kmmiio99o"}),r.React.createElement(u,{label:"Version",subLabel:"1.1.0"}))),r.React.createElement(r.ReactNative.View,{style:{height:64}}))}const{FormSection:ot,FormRow:se}=Ge.Forms,{TableRowIcon:ct}=$.findByProps("TableRowIcon"),H=window.bunny,Fe=H?.metro?.findByPropsLazy("getRootNavigationRef"),oe=H?.metro?.findByPropsLazy("SETTING_RENDERER_CONFIG"),Me=H?.metro?.findByNameLazy("SettingsOverviewScreen",!1);function lt({tabs:e}){const t=r.NavigationNative.useNavigation();return r.React.createElement(se,{label:e.title(),leading:r.React.createElement(se.Icon,{source:e.icon}),trailing:r.React.createElement(r.React.Fragment,{},[e.trailing?e.trailing():null,r.React.createElement(se.Arrow,{key:"arrow"})]),onPress:function(){const n=e.page;t.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(n)}})}})}function ut(e,t){try{t.push(Q.after("default",H?.metro?.findByNameLazy("UserSettingsOverviewWrapper",!1),function(n,i){const a=Z.findInReactTree(i.props.children,function(s){return s.type?.name==="UserSettingsOverview"});a&&t.push(Q.after("render",a.type.prototype,function(s,c){const g=Z.findInReactTree(c.props.children,function(d){return d?.children?.[1]?.type===ot})?.children;if(g){const d=g.findIndex(function(v){return["BILLING_SETTINGS","PREMIUM_SETTINGS"].includes(v?.props?.label)});g.splice(-~d||4,0,r.React.createElement(lt,{key:e.key,tabs:e}))}}))},!0))}catch(n){f.logger.info("Panel UI patch failed graciously \u{1F494}",n)}}function gt(e,t){if(!oe||!Me||!Fe){console.warn("[LastFm] Missing required constants for tabs UI patch");return}const n={};n[e.key]={type:"pressable",title:e.title,icon:e.icon,IconComponent:e.icon&&function(){const s=typeof e.icon=="object"&&e.icon.uri!==void 0?e.icon.uri:e.icon;return r.React.createElement(ct,{source:s})},usePredicate:e.predicate,useTrailing:e.trailing,onPress:function(){const s=Fe.getRootNavigationRef(),c=e.page;s.navigate("VendettaCustomPage",{title:e.title(),render:function(){return r.React.createElement(c)}})},withArrow:!0};let i=oe.SETTING_RENDERER_CONFIG;Object.defineProperty(oe,"SETTING_RENDERER_CONFIG",{enumerable:!0,configurable:!0,get:function(){return{...i,...n}},set:function(s){return i=s}});const a=Symbol("LastFm-pinToSettings");t.push(Q.after("default",Me,function(s,c){if(!s[0][a]){s[0][a]=!0;const{sections:g}=Z.findInReactTree(c,function(v){return v.props?.sections}).props,d=g?.find(function(v){return["Bunny","Revenge","Kettu","Vencore"].some(function(A){return v.label===A&&v.title===A})});d?.settings&&(d.settings=[...d.settings,e.key])}}))}function ft(e){const t=[];let n=!1;const i=e.predicate||function(){return!0};return e.predicate=function(){return n?!1:i()},ut(e,t),gt(e,t),t.push(function(){return n=!0}),function(){for(const a of t)a()}}function ze(){if(!f.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=ft({key:"LastFmScrobbler",icon:m.getAssetIDByName("MusicIcon"),title:function(){return"Multi Scrobbler"},predicate:function(){return f.plugin.storage.addToSidebar===!0},page:Ve});return console.log("[Multi Scrobbler] Successfully patched sidebar"),e}catch(e){return console.error("[Multi Scrobbler] Failed to patch sidebar:",e),function(){}}}var Oe;const y={pluginStopped:!1,lastActivity:void 0,updateInterval:void 0,lastTrackUrl:void 0},ce=[];let _;const Be=l.DEFAULT_SETTINGS;for(const e of Object.keys(Be))f.plugin.storage[e]=f.plugin.storage[e]??Be[e];(Oe=f.plugin.storage).addToSidebar??(Oe.addToSidebar=!1);const o=new Proxy(f.plugin.storage,{get(e,t){return e[t]},set(e,t,n){return e[t]=n,!0}});let W=0;const le=3,dt=5e3,b=function(...e){return console.log("[Scrobble Plugin]",...e)},ue=function(...e){return console.error("[Scrobble Plugin] Error:",...e)};async function K(){try{await et(),W=0,b("Plugin initialized successfully")}catch(e){ue("Initialization failed:",e),W++,W