diff --git a/key-inspector/install/index.js b/key-inspector/install/index.js index f4c5238..9e051d4 100644 --- a/key-inspector/install/index.js +++ b/key-inspector/install/index.js @@ -1,8 +1,8 @@ (function(m,e,l,p,k,B,w,A){"use strict";const{ScrollView:F,View:j}=e.ReactNative;function O({children:t}){return e.React.createElement(F,{style:{flex:1}},t,e.React.createElement(j,{style:{height:24}}))}const{View:z,Text:D}=e.ReactNative;function I({children:t,style:r}){return e.React.createElement(z,{style:[{marginHorizontal:16,marginVertical:8,padding:10,borderRadius:8,borderWidth:1,borderColor:"rgba(128,128,128,0.25)"},r]},e.React.createElement(D,{style:{fontSize:12.5,lineHeight:18,opacity:.75}},t))}const{TouchableOpacity:x,Text:_}=e.ReactNative;function h({label:t,onPress:r,disabled:n,style:s}){return e.React.createElement(x,{disabled:n,onPress:r,style:[{backgroundColor:"#5865F2",borderRadius:8,padding:10,alignItems:"center",opacity:n?.5:1},s]},e.React.createElement(_,{style:{color:"white",fontWeight:"600"}},t))}function v(t){const r=window?.modules;if(r)for(const n in r){const s=r[n];if(!s?.isInitialized)continue;const i=s.publicModule?.exports;if(i)try{if(t(i))return i;if(i.default!=null&&t(i.default))return i.default}catch{}}}function T(t){return v(function(r){return r?.name===t||r?.displayName===t||r?.type?.name===t||r?.type?.displayName===t})}function E(t){return v(function(r){return r?.name===t||r?.displayName===t})}function P(...t){return v(function(r){return t.every(function(n){return r?.[n]!==void 0})})}function C(t){return v(function(r){return typeof r?.getName=="function"&&r.getName.length===0&&r.getName()===t})}const M=[{label:'findByProps("computePermissions", "canEveryoneRole")',run:function(){return l.findByProps("computePermissions","canEveryoneRole")}},{label:'findByProps("getBotLabel")',run:function(){return l.findByProps("getBotLabel")}},{label:'findByName("DisplayName", false)',run:function(){return l.findByName("DisplayName",!1)}},{label:'findByName("HeaderName", false)',run:function(){return l.findByName("HeaderName",!1)}},{label:'findByName("getTagProperties", false)',run:function(){return l.findByName("getTagProperties",!1)}},{label:'findByTypeNameAll("UserRow")',run:function(){return l.findByTypeNameAll("UserRow")}},{label:'findByStoreName("ThemeStore")',run:function(){return l.findByStoreName("ThemeStore")}},{label:'findByStoreName("GuildMemberStore")',run:function(){return l.findByStoreName("GuildMemberStore")}},{label:'findByStoreName("GuildStore")',run:function(){return l.findByStoreName("GuildStore")}},{label:'findByStoreName("ChannelStore")',run:function(){return l.findByStoreName("ChannelStore")}},{label:'findByStoreName("UserStore")',run:function(){return l.findByStoreName("UserStore")}},{label:'findByStoreName("RelationshipStore")',run:function(){return l.findByStoreName("RelationshipStore")}},{label:'findByName("RowManager", false)',run:function(){return l.findByName("RowManager",!1)}},{label:'findByProps("TYPING_WRAPPER_HEIGHT")',run:function(){return l.findByProps("TYPING_WRAPPER_HEIGHT")}},{label:'findByName("VoiceUserConnected", false)',run:function(){return l.findByName("VoiceUserConnected",!1)}},{label:"find(resolveSemanticColor via default.internal)",run:function(){return l.find(function(t){return t?.default?.internal?.resolveSemanticColor})}},{label:"find(resolveSemanticColor via meta)",run:function(){return l.find(function(t){return t?.meta?.resolveSemanticColor})}},{label:"find(UserProfile)",run:function(){return l.find(function(t){return t?.type?.name==="UserProfile"})}},{label:'findByName("UserProfileSection", false)',run:function(){return l.findByName("UserProfileSection",!1)}},{label:'findByProps("sendMessage", "sendBotMessage")',run:function(){return l.findByProps("sendMessage","sendBotMessage")}},{label:'findByProps("openLazy", "hideActionSheet")',run:function(){return l.findByProps("openLazy","hideActionSheet")}},{label:'findByProps("push", "pushLazy", "pop")',run:function(){return l.findByProps("push","pushLazy","pop")}},{label:'findByProps("getRenderCloseButton")',run:function(){return l.findByProps("getRenderCloseButton")}},{label:'findByProps("getHeaderCloseButton")',run:function(){return l.findByProps("getHeaderCloseButton")}},{label:'findByName("Navigator")',run:function(){return l.findByName("Navigator")}},{label:'findByName("ThemedRolePill", false)',run:function(){return l.findByName("ThemedRolePill",!1)}},{label:'findByProps("saveProfileChanges")',run:function(){return l.findByProps("saveProfileChanges")}},{label:'findByProps("useAvatarColors")',run:function(){return l.findByProps("useAvatarColors")}},{label:'findByProps("useThemeContext")',run:function(){return l.findByProps("useThemeContext")}},{label:'findByProps("getProfileTheme")',run:function(){return l.findByProps("getProfileTheme")}},{label:'findByProps("ThemeContextProvider")',run:function(){return l.findByProps("ThemeContextProvider")}},{label:"semanticColors.HEADER_SECONDARY",run:function(){return B.semanticColors?.HEADER_SECONDARY}},{label:"semanticColors.BACKGROUND_ACCENT",run:function(){return B.semanticColors?.BACKGROUND_ACCENT}},{label:"semanticColors.BACKGROUND_PRIMARY",run:function(){return B.semanticColors?.BACKGROUND_PRIMARY}},{label:"semanticColors.BACKGROUND_FLOATING",run:function(){return B.semanticColors?.BACKGROUND_FLOATING}},{label:"semanticColors.BUTTON_OUTLINE_BRAND_BORDER_ACTIVE",run:function(){return B.semanticColors?.BUTTON_OUTLINE_BRAND_BORDER_ACTIVE}},{label:"semanticColors.TEXT_NORMAL",run:function(){return B.semanticColors?.TEXT_NORMAL}}];function U(t){if(t==null)return"NOT FOUND";if(typeof t=="object"){const r=Object.keys(t);return`found (${r.length} key${r.length===1?"":"s"})`}return`found (${typeof t})`}function G(){return M.map(function({label:t,run:r}){try{const n=r();return{label:t,found:n!=null,detail:U(n)}}catch(n){return{label:t,found:!1,detail:`threw: ${n}`}}})}function H(t){const r=t.filter(function(n){return!n.found});return[`Key Inspector report - ${new Date().toISOString()}`,`${t.length} checked, ${r.length} missing`,"",...t.map(function(n){return`[${n.found?"OK":"MISSING"}] ${n.label} -> ${n.detail}`}),"",`semanticColors keys (${Object.keys(B.semanticColors??{}).length}):`,...Object.keys(B.semanticColors??{}).sort(),"",`rawColors keys (${Object.keys(B.rawColors??{}).length}):`,...Object.keys(B.rawColors??{}).sort()].join(` `)}const L=new Set(["vendetta.metro.modules"]);function V(t){return t===null?"null":t===void 0?"undefined":Array.isArray(t)?`array(${t.length})`:typeof t}function Y(t=3){const r=new WeakSet,n=[];function s(i,f,d){if(i==null||typeof i!="object"&&typeof i!="function")return;if(L.has(f)){n.push(`${f} -> (skipped - Discord's full module registry, use the search tools for this)`);return}if(typeof i=="object"){if(r.has(i)){n.push(`${f} -> [already listed elsewhere, circular]`);return}r.add(i)}let u;try{u=Object.keys(i).sort()}catch(a){n.push(`${f} -> [couldn't read keys: ${a}]`);return}const o=u.slice(0,60);for(const a of o){const c=`${f}.${a}`;let y;try{y=i[a]}catch($){n.push(`${c} -> [threw on access: ${$}]`);continue}const b=V(y);n.push(`${c} (${b})`),b==="object"&&d60&&n.push(`${f} -> (+${u.length-60} more keys not shown at this level)`)}try{s(globalThis.vendetta,"vendetta",0)}catch(i){n.push(`Error walking the vendetta object: ${i}`)}return n.length?n.join(` -`):"vendetta object not found or empty"}function K(t){if(t===void 0)return"undefined";if(t===null)return"null";if(typeof t=="function")return`[Function: ${t.name||"anonymous"}]`;if(typeof t!="object")return String(t);const r=new WeakSet;try{return JSON.stringify(t,function(n,s){if(typeof s=="function")return`[Function: ${s.name||"anonymous"}]`;if(typeof s=="symbol")return s.toString();if(typeof s=="object"&&s!==null){if(r.has(s))return"[Circular]";r.add(s)}return s},2)}catch{try{return`[unserializable object, keys: ${Object.keys(t).join(", ")}]`}catch(n){return`[unserializable, and Object.keys threw: ${n}]`}}}function W(t){return t.replace(/[‘’]/g,"'").replace(/[“”]/g,'"').replace(/[–—]/g,"-")}async function q(t){const r=W(t);try{const n=await new Function("findByProps","findByName","findByTypeName","findByStoreName","findByPropsAll","findByNameAll","find","rawFind","rawFindByTypeName","rawFindByProps","rawFindByName","rawFindByStoreName","instead","before","after","getAssetIDByName","React","ReactNative","FluxDispatcher","window",`return (async () => { +`):"vendetta object not found or empty"}function K(t){if(t===void 0)return"undefined";if(t===null)return"null";if(typeof t=="function")return`[Function: ${t.name||"anonymous"}]`;if(typeof t!="object")return String(t);const r=new WeakSet;try{return JSON.stringify(t,function(n,s){if(typeof s=="function")return`[Function: ${s.name||"anonymous"}]`;if(typeof s=="symbol")return s.toString();if(typeof s=="object"&&s!==null){if(r.has(s))return"[Circular]";r.add(s)}return s},2)}catch{try{return`[unserializable object, keys: ${Object.keys(t).join(", ")}]`}catch(n){return`[unserializable, and Object.keys threw: ${n}]`}}}function W(t){return t.replace(/[‘’]/g,"'").replace(/[“”]/g,'"').replace(/[–—]/g,"-")}async function q(t){const r=W(t);try{const n=new Function("findByProps","findByName","findByTypeName","findByStoreName","findByPropsAll","findByNameAll","find","rawFind","rawFindByTypeName","rawFindByProps","rawFindByName","rawFindByStoreName","instead","before","after","getAssetIDByName","React","ReactNative","FluxDispatcher","window",`return (function() { ${r} -})();`)(l.findByProps,l.findByName,l.findByTypeName,l.findByStoreName,l.findByPropsAll,l.findByNameAll,l.find,v,T,P,E,C,w.instead,w.before,w.after,A.getAssetIDByName,e.React,e.ReactNative,e.FluxDispatcher,window);return K(n)}catch(n){return`Error: ${n instanceof Error?`${n.message} +})();`)(l.findByProps,l.findByName,l.findByTypeName,l.findByStoreName,l.findByPropsAll,l.findByNameAll,l.find,v,T,P,E,C,w.instead,w.before,w.after,A.getAssetIDByName,e.React,e.ReactNative,e.FluxDispatcher,window),s=n&&typeof n.then=="function"?await n:n;return K(s)}catch(n){return`Error: ${n instanceof Error?`${n.message} ${n.stack??""}`:String(n)}`}}const J=500;function X(t,r){return new Promise(function(n){const s=new Map;let i=function(){},f=!1;const d=function(){if(f)return;f=!0,i();const u=r?.trim(),o=[...s.entries()].sort(function(a,c){return c[1]-a[1]}).map(function([a,c]){return`${String(c).padStart(5)} ${a}`});n(o.length?`${s.size} distinct type(s)${u?` matching "${u}"`:""} over ${t}ms: ${o.join(` @@ -12,7 +12,7 @@ ${o.join(` `):`"${i}" never rendered during the ${n}ms capture window.`))};try{o=w.before("type",f,function(c){d.push(`Call ${d.length+1} props: ${Q(c[0])}`),d.length>=r&&a()})}catch(c){s(`Failed to patch "${i}": ${c}`);return}setTimeout(a,n)})}const{View:N,Text:R}=e.ReactNative,{FormSection:S,FormInput:g}=k.Forms;function ee(){const[t,r]=e.React.useState(null);return e.React.createElement(S,{title:"Full diagnostic scan"},e.React.createElement(N,{style:{paddingHorizontal:16,paddingVertical:8}},e.React.createElement(h,{label:"Run scan & copy full report",onPress:function(){const n=G(),s=n.filter(function(i){return!i.found}).length;e.clipboard.setString(H(n)),r(`${n.length} checked, ${s} missing - full report copied`),p.showToast(`${s} missing out of ${n.length} - report copied`,void 0)}}),t&&e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.7}},t)))}function te(){const[t,r]=e.React.useState(""),[n,s]=e.React.useState(null),[i,f]=e.React.useState(!1),d=async function(){if(!(!t.trim()||i)){f(!0);try{const u=await q(t);s(u),e.clipboard.setString(u),p.showToast("Result copied",void 0)}finally{f(!1)}}};return e.React.createElement(S,{title:"Eval"},e.React.createElement(N,{style:{paddingHorizontal:16,paddingVertical:8}},e.React.createElement(g,{title:"Code",placeholder:'e.g. return findByProps("getRootNavigationRef")',value:t,onChange:r,multiline:!0,autoCorrect:!1,autoCapitalize:"none",spellCheck:!1}),e.React.createElement(h,{label:i?"Running...":"Run & copy result",style:{marginTop:8},disabled:i,onPress:d}),n!=null&&e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.85},selectable:!0},n.length>2e3?`${n.slice(0,2e3)} -... (truncated, full result copied)`:n),e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.7}},"Runs as the body of an async function - a bare expression, `return ...`, or `await ...` all work. In scope: findByProps/findByName/findByTypeName/ findByStoreName/findByPropsAll/findByNameAll/find, the raw* passive variants (rawFind/rawFindByTypeName/rawFindByProps/rawFindByName/rawFindByStoreName), instead/before/after, getAssetIDByName, React, ReactNative, FluxDispatcher, and window - enough to prototype a real patch here before it goes in a plugin. Nothing runs until you tap the button.")))}function ne(){const[t,r]=e.React.useState(""),[n,s]=e.React.useState("10"),[i,f]=e.React.useState(null),[d,u]=e.React.useState(!1),o=async function(){if(d)return;const a=Math.max(1,Number(n)||10)*1e3;u(!0),f(null),p.showToast(`Capturing Flux events for ${a/1e3}s - go do the thing...`,void 0);try{const c=await X(a,t);f(c),e.clipboard.setString(c),p.showToast("Capture done - report copied",void 0)}finally{u(!1)}};return e.React.createElement(S,{title:"Flux event logger"},e.React.createElement(N,{style:{paddingHorizontal:16,paddingVertical:8}},e.React.createElement(g,{title:"Filter (optional substring, e.g. GUILD)",placeholder:"Leave blank to capture everything",value:t,onChange:r}),e.React.createElement(g,{title:"Capture duration (seconds)",placeholder:"10",value:n,onChange:s,keyboardType:"numeric"}),e.React.createElement(h,{label:d?"Capturing...":"Start capture",style:{marginTop:8},disabled:d,onPress:o}),i!=null&&e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.85},selectable:!0},i.length>2e3?`${i.slice(0,2e3)} +... (truncated, full result copied)`:n),e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.7}},"Runs as a plain (non-async) function body - a bare expression or `return ...` works, but not the async/await keywords (Hermes doesn't support those in dynamically-eval'd code). For timed/async checks, return a Promise built with a plain function instead of an arrow/async function - it gets awaited for you outside the sandbox. In scope: findByProps/findByName/ findByTypeName/findByStoreName/findByPropsAll/findByNameAll/find, the raw* passive variants (rawFind/rawFindByTypeName/rawFindByProps/rawFindByName/ rawFindByStoreName), instead/before/after, getAssetIDByName, React, ReactNative, FluxDispatcher, and window. Nothing runs until you tap the button.")))}function ne(){const[t,r]=e.React.useState(""),[n,s]=e.React.useState("10"),[i,f]=e.React.useState(null),[d,u]=e.React.useState(!1),o=async function(){if(d)return;const a=Math.max(1,Number(n)||10)*1e3;u(!0),f(null),p.showToast(`Capturing Flux events for ${a/1e3}s - go do the thing...`,void 0);try{const c=await X(a,t);f(c),e.clipboard.setString(c),p.showToast("Capture done - report copied",void 0)}finally{u(!1)}};return e.React.createElement(S,{title:"Flux event logger"},e.React.createElement(N,{style:{paddingHorizontal:16,paddingVertical:8}},e.React.createElement(g,{title:"Filter (optional substring, e.g. GUILD)",placeholder:"Leave blank to capture everything",value:t,onChange:r}),e.React.createElement(g,{title:"Capture duration (seconds)",placeholder:"10",value:n,onChange:s,keyboardType:"numeric"}),e.React.createElement(h,{label:d?"Capturing...":"Start capture",style:{marginTop:8},disabled:d,onPress:o}),i!=null&&e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.85},selectable:!0},i.length>2e3?`${i.slice(0,2e3)} ... (truncated, full report copied)`:i),e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.7}},"Tap start, then go do the thing you're trying to understand (open a screen, send a message, tap a button) before the window closes. Reports every action type that dispatched, sorted by count - the fastest way to find which event to hook into instead of guessing from decompiled source.")))}function re(){const[t,r]=e.React.useState(""),[n,s]=e.React.useState("3"),[i,f]=e.React.useState("15"),[d,u]=e.React.useState(null),[o,a]=e.React.useState(!1),c=async function(){const y=t.trim();if(!y||o)return;const b=Math.max(1,Number(i)||15)*1e3;a(!0),u(null);try{const $=await Z(y,Math.max(1,Number(n)||3),b);u($),e.clipboard.setString($),p.showToast("Capture done - result copied",void 0)}finally{a(!1)}};return e.React.createElement(S,{title:"Component render/props logger"},e.React.createElement(N,{style:{paddingHorizontal:16,paddingVertical:8}},e.React.createElement(g,{title:"Component name (findByTypeName / findByName)",placeholder:"e.g. YouBarNotificationsButton",value:t,onChange:r}),e.React.createElement(g,{title:"Max renders to capture",placeholder:"3",value:n,onChange:s,keyboardType:"numeric"}),e.React.createElement(g,{title:"Give up after (seconds)",placeholder:"15",value:i,onChange:f,keyboardType:"numeric"}),e.React.createElement(h,{label:o?"Waiting for renders...":"Start capture",style:{marginTop:8},disabled:o,onPress:c}),d!=null&&e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.85},selectable:!0},d.length>2e3?`${d.slice(0,2e3)} ... (truncated, full result copied)`:d),e.React.createElement(R,{style:{marginTop:8,fontSize:12.5,opacity:.7}},"Patches the component to log its actual props on every render, up to the call cap or the time limit. Only works for React.memo/forwardRef-wrapped components (most of what findByTypeName/findByName return) - a bare function component has no property to intercept its own calls through.")))}function ae(){const[t,r]=e.React.useState(""),[n,s]=e.React.useState(""),[i,f]=e.React.useState(""),[d,u]=e.React.useState(""),o=function(a,c){try{const y=c();if(!y){e.clipboard.setString(`${a} -> not found`),p.showToast("Not found - copied that result",void 0);return}const b=Object.keys(y).sort();e.clipboard.setString(`${a} -> found, keys: ${b.join(` diff --git a/key-inspector/install/manifest.json b/key-inspector/install/manifest.json index bc5f5fc..8b4cf43 100644 --- a/key-inspector/install/manifest.json +++ b/key-inspector/install/manifest.json @@ -1 +1 @@ -{"name":"Key Inspector","description":"Plugin-dev toolkit: on-device module search (incl. a cache-safe raw mode), a Flux event logger, a component render/props logger, an eval console, and full API/color-token dumps - for diagnosing and building plugins without needing a computer.","authors":[{"name":"everestmcarthur"}],"main":"index.js","hash":"68a66530acff19ac18589080da355518a31d032ee806376ee90a0df6092b37ad"} \ No newline at end of file +{"name":"Key Inspector","description":"Plugin-dev toolkit: on-device module search (incl. a cache-safe raw mode), a Flux event logger, a component render/props logger, an eval console, and full API/color-token dumps - for diagnosing and building plugins without needing a computer.","authors":[{"name":"everestmcarthur"}],"main":"index.js","hash":"d1c8e56fd0fa1f32f825207667a16a2d6e0150c99898b7194ce51ffb1f2a3783"} \ No newline at end of file