mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-05 19:24:39 +00:00
feat: magic button that crashes chrome tabs (yes rly idk why)
This commit is contained in:
+24
-3
@@ -38,6 +38,7 @@ import documentLoadCSS from '@/css/documentload.scss?inline'
|
|||||||
import renderSvelte from '@/interface/main'
|
import renderSvelte from '@/interface/main'
|
||||||
import Settings from '@/interface/pages/settings.svelte'
|
import Settings from '@/interface/pages/settings.svelte'
|
||||||
import { settingsPopup } from './interface/hooks/SettingsPopup'
|
import { settingsPopup } from './interface/hooks/SettingsPopup'
|
||||||
|
import ReactFiber from './seqta/utils/ReactFiber'
|
||||||
|
|
||||||
let SettingsClicked = false
|
let SettingsClicked = false
|
||||||
export let MenuOptionsOpen = false
|
export let MenuOptionsOpen = false
|
||||||
@@ -90,6 +91,26 @@ async function init() {
|
|||||||
}
|
}
|
||||||
console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.')
|
console.info('[BetterSEQTA+] Successfully initalised BetterSEQTA+, starting to load assets.')
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
waitForElm('.Viewer__Viewer___32BH-', true).then(() => {
|
||||||
|
console.log('Element exists')
|
||||||
|
|
||||||
|
const button = document.createElement('button')
|
||||||
|
button.innerHTML = 'Click me'
|
||||||
|
button.onclick = () => {
|
||||||
|
// function call to run onclick
|
||||||
|
ReactFiber.find(".Viewer__Viewer___32BH-", { debug: true })
|
||||||
|
.setState(prev => ({ ...prev, selected: new Set([999999]) }))
|
||||||
|
}
|
||||||
|
button.style.position = 'fixed'
|
||||||
|
button.style.top = '0'
|
||||||
|
button.style.right = '0'
|
||||||
|
button.style.padding = '10px'
|
||||||
|
button.style.zIndex = '9999'
|
||||||
|
button.style.backgroundColor = 'red'
|
||||||
|
button.style.color = 'white'
|
||||||
|
document.body.appendChild(button)
|
||||||
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
@@ -624,10 +645,11 @@ export async function waitForElm(selector: string, usePolling: boolean = false,
|
|||||||
} else {
|
} else {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const registerObserver = () => {
|
const registerObserver = () => {
|
||||||
const { unregister } = eventManager.register(`${selector}`, {
|
const { unregister } = eventManager.register(`${selector}`, {
|
||||||
customCheck: (element) => element.matches(selector)
|
customCheck: (element) => element.matches(selector)
|
||||||
}, (element) => {
|
}, async (element) => {
|
||||||
resolve(element);
|
resolve(element);
|
||||||
|
await delay(1);
|
||||||
unregister(); // Remove the listener once the element is found
|
unregister(); // Remove the listener once the element is found
|
||||||
});
|
});
|
||||||
return unregister;
|
return unregister;
|
||||||
@@ -2976,7 +2998,6 @@ async function handleAssessments(node: Element): Promise<void> {
|
|||||||
}
|
}
|
||||||
const preaverage = numaverage.toFixed(0) as unknown as number
|
const preaverage = numaverage.toFixed(0) as unknown as number
|
||||||
const prepaverage = Math.ceil(preaverage / 5) * 5;
|
const prepaverage = Math.ceil(preaverage / 5) * 5;
|
||||||
console.info(prepaverage)
|
|
||||||
const NumberGradeMap: Record<number, string> = {
|
const NumberGradeMap: Record<number, string> = {
|
||||||
100: "A+",
|
100: "A+",
|
||||||
95: "A",
|
95: "A",
|
||||||
|
|||||||
+94
-101
@@ -1,62 +1,6 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
import type { SettingsState } from "@/types/storage";
|
import type { SettingsState } from "@/types/storage";
|
||||||
|
|
||||||
export const openDB = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open('MyDatabase', 1);
|
|
||||||
|
|
||||||
request.onupgradeneeded = (event: any) => {
|
|
||||||
const db = event.target.result;
|
|
||||||
db.createObjectStore('backgrounds', { keyPath: 'id' });
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onsuccess = () => {
|
|
||||||
resolve(request.result);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onerror = (event: any) => {
|
|
||||||
reject('Error opening database: ' + event.target.errorCode);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const writeData = async (type: any, data: any) => {
|
|
||||||
const db: any = await openDB();
|
|
||||||
|
|
||||||
const tx = db.transaction('backgrounds', 'readwrite');
|
|
||||||
const store = tx.objectStore('backgrounds');
|
|
||||||
const request = await store.put({ id: 'customBackground', type, data });
|
|
||||||
|
|
||||||
return request.result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const readData = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
openDB()
|
|
||||||
.then((db: any) => {
|
|
||||||
const tx = db.transaction('backgrounds', 'readonly');
|
|
||||||
const store = tx.objectStore('backgrounds');
|
|
||||||
|
|
||||||
// Retrieve the custom background
|
|
||||||
const getRequest = store.get('customBackground');
|
|
||||||
|
|
||||||
// Attach success and error event handlers
|
|
||||||
getRequest.onsuccess = function(event: any) {
|
|
||||||
resolve(event.target.result);
|
|
||||||
};
|
|
||||||
|
|
||||||
getRequest.onerror = function(event: any) {
|
|
||||||
console.error('An error occurred:', event);
|
|
||||||
reject(event);
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('An error occurred:', error);
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function reloadSeqtaPages() {
|
function reloadSeqtaPages() {
|
||||||
const result = browser.tabs.query({})
|
const result = browser.tabs.query({})
|
||||||
function open (tabs: any) {
|
function open (tabs: any) {
|
||||||
@@ -69,56 +13,104 @@ function reloadSeqtaPages() {
|
|||||||
result.then(open, console.error)
|
result.then(open, console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const injectedTabs = new Set<number>(); // Keep track of injected tabs
|
||||||
|
|
||||||
|
async function injectPageState(tabId: number) {
|
||||||
|
if (injectedTabs.has(tabId)) {
|
||||||
|
return; // Already injected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await browser.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
files: ["pageState.js"],
|
||||||
|
// @ts-ignore
|
||||||
|
world: "MAIN",
|
||||||
|
});
|
||||||
|
injectedTabs.add(tabId);
|
||||||
|
console.log(`[background] Injected pageState.js into tab ${tabId}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[background] Failed to inject pageState.js into tab ${tabId}:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the script when a tab is closed
|
||||||
|
browser.tabs.onRemoved.addListener((tabId) => {
|
||||||
|
injectedTabs.delete(tabId);
|
||||||
|
});
|
||||||
|
|
||||||
// Main message listener
|
// Main message listener
|
||||||
browser.runtime.onMessage.addListener((request: any, _sender: any, sendResponse: any) => {
|
browser.runtime.onMessage.addListener((request: any, sender: any, sendResponse: (response?: any) => void) => {
|
||||||
|
|
||||||
switch (request.type) {
|
switch (request.type) {
|
||||||
case 'reloadTabs':
|
case 'reloadTabs':
|
||||||
reloadSeqtaPages();
|
reloadSeqtaPages();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'extensionPages':
|
|
||||||
browser.tabs.query({}).then(function (tabs) {
|
|
||||||
for (let tab of tabs) {
|
|
||||||
if (tab.url?.includes('chrome-extension://')) {
|
|
||||||
browser.tabs.sendMessage(tab.id!, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'currentTab':
|
|
||||||
browser.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
|
|
||||||
browser.tabs.sendMessage(tabs[0].id!, request).then(function (response) {
|
|
||||||
sendResponse(response);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case 'githubTab':
|
|
||||||
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'setDefaultStorage':
|
case 'extensionPages':
|
||||||
SetStorageValue(DefaultValues);
|
browser.tabs.query({}).then(function (tabs) {
|
||||||
break;
|
for (let tab of tabs) {
|
||||||
|
if (tab.url?.includes('chrome-extension://')) {
|
||||||
|
browser.tabs.sendMessage(tab.id!, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'currentTab':
|
||||||
|
browser.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
|
||||||
|
browser.tabs.sendMessage(tabs[0].id!, request).then(function (response) {
|
||||||
|
sendResponse(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true; // Keep message channel open for async response
|
||||||
|
|
||||||
case 'sendNews':
|
case 'githubTab':
|
||||||
const date = new Date();
|
browser.tabs.create({ url: 'github.com/BetterSEQTA/BetterSEQTA-Plus' });
|
||||||
|
break;
|
||||||
const from =
|
|
||||||
date.getFullYear() +
|
|
||||||
'-' +
|
|
||||||
(date.getMonth() + 1) +
|
|
||||||
'-' +
|
|
||||||
(date.getDate() - 5);
|
|
||||||
|
|
||||||
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
|
|
||||||
|
|
||||||
GetNews(sendResponse, url);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
case 'setDefaultStorage':
|
||||||
console.log('Unknown request type');
|
SetStorageValue(DefaultValues);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'sendNews':
|
||||||
|
const date = new Date();
|
||||||
|
|
||||||
|
const from =
|
||||||
|
date.getFullYear() +
|
||||||
|
'-' +
|
||||||
|
(date.getMonth() + 1) +
|
||||||
|
'-' +
|
||||||
|
(date.getDate() - 5);
|
||||||
|
|
||||||
|
const url = `https://newsapi.org/v2/everything?domains=abc.net.au&from=${from}&apiKey=17c0da766ba347c89d094449504e3080`;
|
||||||
|
|
||||||
|
GetNews(sendResponse, url);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'reactFiberRequest': {
|
||||||
|
//const { tabId, /* selector, action, payload, debug */ } = request;
|
||||||
|
const tabId = sender.tab.id;
|
||||||
|
|
||||||
|
// Ensure pageState.js is injected
|
||||||
|
injectPageState(tabId).then(() => {
|
||||||
|
// Forward the request to the injected script
|
||||||
|
browser.tabs.sendMessage(tabId, { ...request, type: "reactFiberAction" }, { frameId: 0 }) // Target the main world
|
||||||
|
.then(response => {
|
||||||
|
sendResponse(response); // Send the response back to the content script
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(`[background] Error communicating with injected script in tab ${tabId}:`, err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("[background] Failed to inject", err);
|
||||||
|
});
|
||||||
|
return true; // Keep the message channel open for the async response
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log('Unknown request type');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -220,6 +212,7 @@ const DefaultValues: SettingsState = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
customshortcuts: [],
|
customshortcuts: [],
|
||||||
|
lettergrade: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
function SetStorageValue(object: any) {
|
function SetStorageValue(object: any) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"64": "resources/icons/icon-64.png"
|
"64": "resources/icons/icon-64.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": ["tabs", "notifications", "storage"],
|
"permissions": ["tabs", "notifications", "storage", "scripting", "activeTab"],
|
||||||
"host_permissions": ["https://newsapi.org/", "*://*/*"],
|
"host_permissions": ["https://newsapi.org/", "*://*/*"],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.ts"
|
"service_worker": "background.ts"
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
class ReactFiber {
|
||||||
|
constructor(selector, options = {}) {
|
||||||
|
this.selector = selector;
|
||||||
|
this.debug = options.debug || false;
|
||||||
|
this.nodes = [...document.querySelectorAll(selector)]; // Support multiple elements
|
||||||
|
this.fibers = this.nodes.map(node => this.getFiberNode(node));
|
||||||
|
this.components = this.fibers.map(fiber => this.getOwnerComponent(fiber));
|
||||||
|
|
||||||
|
if (this.debug) {
|
||||||
|
console.log("📌 Selected Nodes:", this.nodes);
|
||||||
|
console.log("🔍 Found Fibers:", this.fibers);
|
||||||
|
console.log("🛠 Found Components:", this.components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static find(selector, options = {}) {
|
||||||
|
return new ReactFiber(selector, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFiberNode(node) {
|
||||||
|
if (!node) return null;
|
||||||
|
const fiberKey = Object.getOwnPropertyNames(node).find(name =>
|
||||||
|
name.startsWith('__reactFiber') || name.startsWith('__reactInternalInstance')
|
||||||
|
);
|
||||||
|
return fiberKey ? node[fiberKey] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOwnerComponent(fiberNode) {
|
||||||
|
let current = fiberNode;
|
||||||
|
while (current) {
|
||||||
|
if (current.stateNode && (current.stateNode.setState || current.stateNode.forceUpdate)) {
|
||||||
|
return current.stateNode;
|
||||||
|
}
|
||||||
|
current = current.return;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(key = null) {
|
||||||
|
if (!this.components.length) return null;
|
||||||
|
const state = this.components[0]?.state || null;
|
||||||
|
return key ? state?.[key] : state;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(updateFn) {
|
||||||
|
this.components.forEach(component => {
|
||||||
|
if (component?.setState) {
|
||||||
|
component.setState(prevState => {
|
||||||
|
const newState = updateFn(prevState);
|
||||||
|
if (this.debug) console.log("✅ Updated State:", newState);
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this; // Enable chaining
|
||||||
|
}
|
||||||
|
|
||||||
|
getProp(propName) {
|
||||||
|
if (!this.fibers.length) return null;
|
||||||
|
return this.fibers[0]?.memoizedProps?.[propName] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProp(propName, value) {
|
||||||
|
this.fibers.forEach(fiber => {
|
||||||
|
if (fiber?.memoizedProps) {
|
||||||
|
fiber.memoizedProps[propName] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.forceUpdate(); // Apply the change and return this for chaining
|
||||||
|
}
|
||||||
|
|
||||||
|
forceUpdate() {
|
||||||
|
this.components.forEach(component => {
|
||||||
|
if (component?.forceUpdate) {
|
||||||
|
component.forceUpdate();
|
||||||
|
if (this.debug) console.log("🔄 Forced React Re-render");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this; // Enable chaining
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message listener for communication with the background script
|
||||||
|
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||||
|
if (request.type === "reactFiberAction") {
|
||||||
|
const { selector, action, payload, debug } = request;
|
||||||
|
const fiberInstance = ReactFiber.find(selector, {debug}); // Use the class
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "getState":
|
||||||
|
sendResponse(fiberInstance.getState(payload.key));
|
||||||
|
break;
|
||||||
|
case "setState":
|
||||||
|
// Very important: Eval the function string in the context of the page
|
||||||
|
const updateFn = eval(`(${payload.updateFn})`);
|
||||||
|
fiberInstance.setState(updateFn);
|
||||||
|
sendResponse({}); // Send acknowledgement
|
||||||
|
break;
|
||||||
|
case "getProp":
|
||||||
|
sendResponse(fiberInstance.getProp(payload.propName));
|
||||||
|
break;
|
||||||
|
case "setProp":
|
||||||
|
fiberInstance.setProp(payload.propName, payload.value);
|
||||||
|
sendResponse({}); // Send acknowledgement
|
||||||
|
break;
|
||||||
|
case "forceUpdate":
|
||||||
|
fiberInstance.forceUpdate();
|
||||||
|
sendResponse({}); // Send acknowledgement
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(`[pageState] Unknown action: ${action}`);
|
||||||
|
sendResponse(null);
|
||||||
|
}
|
||||||
|
return true; // Keep message channel open (for consistency, even if not always needed)
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
|
||||||
|
class ReactFiber {
|
||||||
|
private selector: string;
|
||||||
|
private debug: boolean;
|
||||||
|
|
||||||
|
constructor(selector: string, options: { debug?: boolean } = {}) {
|
||||||
|
this.selector = selector;
|
||||||
|
this.debug = options.debug || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static find(selector: string, options: { debug?: boolean } = {}) {
|
||||||
|
return new ReactFiber(selector, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendMessage(action: string, payload: any = {}): Promise<any> {
|
||||||
|
const message = {
|
||||||
|
type: "reactFiberRequest",
|
||||||
|
selector: this.selector,
|
||||||
|
action,
|
||||||
|
payload,
|
||||||
|
debug: this.debug
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await browser.runtime.sendMessage(message);
|
||||||
|
|
||||||
|
if (this.debug) {
|
||||||
|
console.log("Content Received Response:", response);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending message:", error);
|
||||||
|
return null; // or throw error, depending on desired behavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getState(key?: string): Promise<any> {
|
||||||
|
return this.sendMessage("getState", { key });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setState(updateFn: (prevState: any) => any): Promise<ReactFiber> {
|
||||||
|
// Serialize the update function to a string. This is the tricky part.
|
||||||
|
// We can only send strings/JSON-serializable data through messages.
|
||||||
|
const updateFnString = updateFn.toString();
|
||||||
|
await this.sendMessage("setState", { updateFn: updateFnString });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProp(propName: string): Promise<any> {
|
||||||
|
return this.sendMessage("getProp", { propName });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setProp(propName: string, value: any): Promise<ReactFiber> {
|
||||||
|
await this.sendMessage("setProp", { propName, value });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async forceUpdate(): Promise<ReactFiber> {
|
||||||
|
await this.sendMessage("forceUpdate");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default ReactFiber
|
||||||
+2
-1
@@ -76,7 +76,8 @@ export default defineConfig(({ command }) => ({
|
|||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
settings: join(__dirname, 'src', 'interface', 'index.html'),
|
settings: join(__dirname, 'src', 'interface', 'index.html'),
|
||||||
migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html')
|
migration: join(__dirname, 'src', 'seqta', 'utils', 'migration', 'migrate.html'),
|
||||||
|
pageState: join(__dirname, 'src', 'pageState.js'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user