From b8fb1ea9cb96327d07aec379a7928c21311fa857 Mon Sep 17 00:00:00 2001 From: Daniel Messer Date: Mon, 27 Oct 2025 11:30:19 -0400 Subject: [PATCH] Added Chrome extension --- Chrome/Code/background.js | 29 +++ Chrome/Code/content.js | 89 +++++++++ Chrome/Code/icon128.png | Bin 0 -> 2174 bytes Chrome/Code/icon16.png | Bin 0 -> 719 bytes Chrome/Code/icon32.png | Bin 0 -> 958 bytes Chrome/Code/icon48.png | Bin 0 -> 1180 bytes Chrome/Code/manifest.json | 40 ++++ Chrome/Code/popup.html | 149 ++++++++++++++ Chrome/Code/popup.js | 182 ++++++++++++++++++ .../Privacy Policy for SiReS Ex.md | 45 +++++ 10 files changed, 534 insertions(+) create mode 100644 Chrome/Code/background.js create mode 100644 Chrome/Code/content.js create mode 100644 Chrome/Code/icon128.png create mode 100644 Chrome/Code/icon16.png create mode 100644 Chrome/Code/icon32.png create mode 100644 Chrome/Code/icon48.png create mode 100644 Chrome/Code/manifest.json create mode 100644 Chrome/Code/popup.html create mode 100644 Chrome/Code/popup.js create mode 100644 Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md diff --git a/Chrome/Code/background.js b/Chrome/Code/background.js new file mode 100644 index 0000000..64ad560 --- /dev/null +++ b/Chrome/Code/background.js @@ -0,0 +1,29 @@ +// background.js - Background script for handling downloads (Chrome-optimized) + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "downloadSQL") { + const { sqlQuery, filename } = request; + + // Create blob with SQL content + const blob = new Blob([sqlQuery], { type: 'text/sql' }); + const url = URL.createObjectURL(blob); + + // Generate filename if not provided + const finalFilename = filename || `sql_query_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.sql`; + + // Download the file + chrome.downloads.download({ + url: url, + filename: finalFilename, + saveAs: true + }).then(() => { + URL.revokeObjectURL(url); + sendResponse({ success: true }); + }).catch((error) => { + console.error('Download failed:', error); + sendResponse({ success: false, error: error.message }); + }); + + return true; // Keep the message channel open for async response + } +}); diff --git a/Chrome/Code/content.js b/Chrome/Code/content.js new file mode 100644 index 0000000..e23426f --- /dev/null +++ b/Chrome/Code/content.js @@ -0,0 +1,89 @@ +// content.js - Content script that runs on web pages (Chrome-optimized) + +function extractSQLQuery() { + // Look for the specific input element + const sqlInput = document.getElementById('SQLStatementHide'); + + if (sqlInput) { + const sqlQuery = sqlInput.value; + if (sqlQuery && sqlQuery.trim()) { + return cleanupSQLQuery(sqlQuery.trim()); + } else { + return null; + } + } + + // Fallback: look for any input with name="SQLStatementHide" + const sqlInputByName = document.querySelector('input[name="SQLStatementHide"]'); + if (sqlInputByName) { + const sqlQuery = sqlInputByName.value; + if (sqlQuery && sqlQuery.trim()) { + return cleanupSQLQuery(sqlQuery.trim()); + } + } + + return null; +} + +function cleanupSQLQuery(sqlQuery) { + // Fix double single quotes around dates/values (''2017-10-23'' becomes '2017-10-23') + // This handles the common issue where systems incorrectly double-escape quotes + return sqlQuery.replace(/''/g, "'"); +} + +// Listen for messages from popup +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + if (request.action === "extractSQL") { + const sqlQuery = extractSQLQuery(); + sendResponse({ + success: sqlQuery !== null, + sqlQuery: sqlQuery, + url: window.location.href, + timestamp: new Date().toISOString() + }); + } +}); + +// Optional: Add a visual indicator when SQL is found +function addVisualIndicator() { + const sqlInput = document.getElementById('SQLStatementHide') || + document.querySelector('input[name="SQLStatementHide"]'); + + if (sqlInput && sqlInput.value && sqlInput.value.trim()) { + // Add a small visual indicator that SQL was found + if (!document.getElementById('sql-extractor-indicator')) { + const indicator = document.createElement('div'); + indicator.id = 'sql-extractor-indicator'; + indicator.style.cssText = ` + position: fixed; + top: 10px; + right: 10px; + background: #4CAF50; + color: white; + padding: 5px 10px; + border-radius: 3px; + font-size: 12px; + z-index: 10000; + font-family: Arial, sans-serif; + opacity: 0.8; + pointer-events: none; + `; + indicator.textContent = 'SQL Query Found'; + document.body.appendChild(indicator); + + // Remove indicator after 3 seconds + setTimeout(() => { + if (document.getElementById('sql-extractor-indicator')) { + document.body.removeChild(indicator); + } + }, 3000); + } + } +} + +// Run indicator check when page loads +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', addVisualIndicator); +} else { + addVisualIndicator(); +} diff --git a/Chrome/Code/icon128.png b/Chrome/Code/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..fc641a63521fe65934272a86fee8b8bc2eb4b869 GIT binary patch literal 2174 zcmbV~dpOkV7RP^cALCM!8Aa|?!^m#NU@E3Idk7~Q!w@0q!n7r~BuPS!IUYM{R49zw z6p_owJsG!>kQup$$;qHl+qGTJ&)(;peV+5r-si0MS!+Gd`>y9*@8|oj_5SrSV6&~2 zjTHca0ssVDzp!=np4SUhDH?f#w8FL85ucQxoz_D z+sIm~TIB!P)|&wh@SOX0|04T0*za6}fE*0`0xT2@8~~Qpm`*Wv0>Au# ztP*mcnOA6}j<)}&+CV#ry!TwmlFqjoHR7ETUdN{u{zI-((dw{pPfAL`;VGJp8*6yP z?-s+eiPgU_)G$uRF*h^lIN)E-lyG_e9esD{TK8x_Nn{I^@frc#(Rw30v8H%8%h!FH zC?polUwOP_I;0yg;*%Hd`b&diOa?y9{20=kdf4Ke^uB=mpI_VNnh zR?zUZ(>Mm5guqjj%8#j9Z`a6rF!?$_+oMy~N9y<N#i;nDgV$$$=R`+iBhWY^~Y0|Qrv-)77%HRml?=S2Pc3gLM1#rvTug1Q6ECSrTcbuFvZPIeq7sX-&H zSUS~y`P{?GQp6E*Oja@4B}#JRN~Z$kfMzkA7!~r(xi|IcQb_P=;_@tk$C;yAa0pqO zvV3`*70ZuZnS37SOZhmIYr~S;7n5d7fK~L?Ie72BHaMM}7_v%tP$(TAF|jl+tG-=H zKY#sZ)v2rbL*m>uwL&$<4qnalS$55EO%mVm^YWoqVt)AoJ*gl-tup_qDWEb=aC8+30lVGzrVS@Nv*~Y8zWrw!nR< z)bJO6G0@mvq-OQ=+Yx0>+!DSsR%owyrN?7=AIbHO6K5&lTzqiYMCPWq2NMG8Ze#@> z{WR^VL^2VlT%bzo149P;_C`{PRt|GUskERKUS@m5*o!Y$K3>difu`0bVL=&=N5a6- zZPK`K`74-5GST3`0=qFq?=g8l3Gk1`J8C1$ojoUmVttYsj`pM=+sP(0vaAuh@(K|?++DY&B1n&gx^Y(3L?15+&6)L25ZcEUEqb&|Ey`8( zZ$={a5$=a8QAMf7Q=@A0qJ17uhQ8y5NvP(8=80?z(}*QG6BXA4xBX#e!-aZ6JyiE= z*3$&T4|B%W5vMRQNr;oIipeg-&PTy2$&3_E*kcAApADhoj-a|J4EklQzVPhaXcuuXT;FDxjn^%;-c|3h#{zn1la3vnU}N6Xf6ch5LT9TUG*pI#=7 zevRAC_V2NC>__0c($ez|z~&FjgP6?=^01>e;oA%uGP z6-JuTf?L)0O+Oo&?@}0X-?<3nAt7*#gTFgdl91c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$fZ6(Txny& ng9-(UuUu*>XnJ0^X!7&vyZTEOb!k8H-@&D^sDTi+|9=wzKs{_} literal 0 HcmV?d00001 diff --git a/Chrome/Code/icon48.png b/Chrome/Code/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..ac59f130700712cad7b6d3eac4848446a3412583 GIT binary patch literal 1180 zcmex=c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$y0v9q+!b{#jr1I%ADXpyq+&N$y42d|j6> zZdtf;??HVvh5q{KT=^4!9ur9kzj@5$!<8);l^;E~nlWp8)wZ<2=F&tGFU?ApF9)9zwcI*(nLr%zt{RX_eut4hM}oYd4demG@L?8OV7 zj{YV03hW!6$=vjxQGf z`F4K3Rz=0H*ws}pw|&3vue>#6eg4$uaOFE6(H6s=iS+jq&kCcW(Ep0oGnA2PhR z>e9uymZU{%`o!+cz4?U|=o_Bei=V%W?tEm=7yIJ+r+-g@{-3claO&{{|Ky0$?;@|< zmWVvwY^Su;GI(~&cDH%WwQBR%-tw7T>6IEfar3c5Zf|}k_169Jb&lCR^V#PRkJHl3 z#}4=zzU?}@Xz>mATdVfwCjO4BwokwGU8qaD`0zZL-PiIT{XM;Q(FUVahq?P6$UV6> jX;Ra(X`O-+JNHGry7^{HplglccSTo`07fLt{{JQb)OPS$ literal 0 HcmV?d00001 diff --git a/Chrome/Code/manifest.json b/Chrome/Code/manifest.json new file mode 100644 index 0000000..22d3079 --- /dev/null +++ b/Chrome/Code/manifest.json @@ -0,0 +1,40 @@ +{ + "manifest_version": 3, + "name": "SimplyReports SQL Extractor", + "version": "1.0", + "description": "Grab the SQL query used in a SimplyReports results page and copy it to the clipboard or save it to a file.", + + "permissions": [ + "activeTab", + "downloads" + ], + + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], + + "background": { + "service_worker": "background.js" + }, + + "action": { + "default_popup": "popup.html", + "default_title": "Extract SQL Query", + "default_icon": { + "16": "icon16.png", + "32": "icon32.png", + "48": "icon48.png", + "128": "icon128.png" + } + }, + + "icons": { + "16": "icon16.png", + "32": "icon32.png", + "48": "icon48.png", + "128": "icon128.png" + } +} diff --git a/Chrome/Code/popup.html b/Chrome/Code/popup.html new file mode 100644 index 0000000..72ba633 --- /dev/null +++ b/Chrome/Code/popup.html @@ -0,0 +1,149 @@ + + + + + + + +
+

SiReS Ex - SimplyReports SQL Extractor

+
+ + + + + +
+ + + +
+ +
+ Click "Extract SQL Query" to search for SQL in the current page +
+ + + + diff --git a/Chrome/Code/popup.js b/Chrome/Code/popup.js new file mode 100644 index 0000000..83bebea --- /dev/null +++ b/Chrome/Code/popup.js @@ -0,0 +1,182 @@ +// popup.js - Popup script for user interactions (Manifest V3 compatible) + +let currentSQLQuery = null; + +document.addEventListener('DOMContentLoaded', function() { + const extractBtn = document.getElementById('extractBtn'); + const copyBtn = document.getElementById('copyBtn'); + const downloadBtn = document.getElementById('downloadBtn'); + const filenameInput = document.getElementById('filenameInput'); + const statusDiv = document.getElementById('status'); + const sqlPreview = document.getElementById('sqlPreview'); + const actionButtons = document.getElementById('actionButtons'); + + // Extract SQL Query + extractBtn.addEventListener('click', async function() { + extractBtn.disabled = true; + extractBtn.textContent = 'Extracting...'; + + try { + // Get active tab + const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); + + // Send message to content script + const response = await chrome.tabs.sendMessage(tab.id, { action: "extractSQL" }); + + extractBtn.disabled = false; + extractBtn.textContent = 'Extract SQL Query'; + + if (response && response.success) { + // Debug: Log the original SQL + console.log('Original SQL:', response.sqlQuery); + + // Store the formatted version as the main query + currentSQLQuery = formatSQL(response.sqlQuery); + + // Debug: Log the formatted SQL + console.log('Formatted SQL:', currentSQLQuery); + console.log('Formatted SQL contains newlines:', currentSQLQuery.includes('\n')); + + showStatus('success', 'SQL Query found and extracted!'); + showSQLPreview(currentSQLQuery); + actionButtons.style.display = 'block'; + + // Set default filename based on current page + const url = new URL(response.url); + const defaultName = `sql_query_${url.hostname}_${new Date().toISOString().slice(0, 10)}.sql`; + filenameInput.value = defaultName; + } else { + showStatus('error', 'No SQL query found in the current page'); + actionButtons.style.display = 'none'; + sqlPreview.style.display = 'none'; + currentSQLQuery = null; + } + } catch (error) { + extractBtn.disabled = false; + extractBtn.textContent = 'Extract SQL Query'; + showStatus('error', 'Error extracting SQL: ' + error.message); + } + }); + + // Copy to Clipboard + copyBtn.addEventListener('click', async function() { + if (!currentSQLQuery) return; + + try { + // Debug: Log what we're trying to copy + console.log('Copying SQL:', currentSQLQuery); + console.log('SQL length:', currentSQLQuery.length); + console.log('Contains newlines:', currentSQLQuery.includes('\n')); + + await navigator.clipboard.writeText(currentSQLQuery); + + const originalText = copyBtn.textContent; + copyBtn.textContent = 'Copied!'; + copyBtn.style.backgroundColor = '#28a745'; + + setTimeout(function() { + copyBtn.textContent = originalText; + copyBtn.style.backgroundColor = ''; + }, 1500); + } catch (err) { + showStatus('error', 'Failed to copy to clipboard: ' + err.message); + } + }); + + // Download as File + downloadBtn.addEventListener('click', async function() { + if (!currentSQLQuery) return; + + downloadBtn.disabled = true; + downloadBtn.textContent = 'Downloading...'; + + try { + const filename = filenameInput.value.trim() || null; + + // Send message to background script + const response = await chrome.runtime.sendMessage({ + action: "downloadSQL", + sqlQuery: currentSQLQuery, + filename: filename + }); + + downloadBtn.disabled = false; + downloadBtn.textContent = 'Save as File'; + + if (response && response.success) { + const originalText = downloadBtn.textContent; + downloadBtn.textContent = 'Downloaded!'; + downloadBtn.style.backgroundColor = '#28a745'; + + setTimeout(function() { + downloadBtn.textContent = originalText; + downloadBtn.style.backgroundColor = ''; + }, 1500); + } else { + showStatus('error', 'Failed to download file: ' + (response ? response.error : 'Unknown error')); + } + } catch (error) { + downloadBtn.disabled = false; + downloadBtn.textContent = 'Save as File'; + showStatus('error', 'Failed to download file: ' + error.message); + } + }); + + function showStatus(type, message) { + statusDiv.className = `status ${type}`; + statusDiv.textContent = message; + statusDiv.style.display = 'block'; + + // Auto-hide after 5 seconds + setTimeout(function() { + statusDiv.style.display = 'none'; + }, 5000); + } + + function showSQLPreview(sqlQuery) { + sqlPreview.textContent = sqlQuery; + sqlPreview.style.display = 'block'; + } + + function formatSQL(sql) { + // Simple but reliable SQL formatting + let formatted = sql.trim(); + + // Normalize whitespace first + formatted = formatted.replace(/\s+/g, ' '); + + // Add line breaks before major keywords (simple approach) + formatted = formatted.replace(/\sFROM\s/gi, '\nFROM '); + formatted = formatted.replace(/\sWHERE\s/gi, '\nWHERE '); + formatted = formatted.replace(/\sAND\s/gi, '\n AND '); + formatted = formatted.replace(/\sOR\s/gi, '\n OR '); + formatted = formatted.replace(/\sORDER\sBY\s/gi, '\nORDER BY '); + formatted = formatted.replace(/\sGROUP\sBY\s/gi, '\nGROUP BY '); + formatted = formatted.replace(/\sHAVING\s/gi, '\nHAVING '); + formatted = formatted.replace(/\sJOIN\s/gi, '\nJOIN '); + formatted = formatted.replace(/\sINNER\sJOIN\s/gi, '\nINNER JOIN '); + formatted = formatted.replace(/\sLEFT\sJOIN\s/gi, '\nLEFT JOIN '); + formatted = formatted.replace(/\sRIGHT\sJOIN\s/gi, '\nRIGHT JOIN '); + + // Add semicolon if missing + if (!formatted.trim().endsWith(';')) { + formatted += ';'; + } + + // Clean up any leading/trailing whitespace on lines + formatted = formatted.split('\n').map(line => line.trim()).join('\n'); + + return formatted; + } + + // Auto-extract on popup open if we're on a page that might have SQL + chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { + const currentTab = tabs[0]; + if (currentTab.url && !currentTab.url.startsWith('chrome://') && !currentTab.url.startsWith('chrome-extension://')) { + // Auto-extract after a short delay + setTimeout(function() { + extractBtn.click(); + }, 500); + } + }); +}); diff --git a/Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md b/Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md new file mode 100644 index 0000000..6f0bd27 --- /dev/null +++ b/Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md @@ -0,0 +1,45 @@ +# Privacy Policy for SimplyReports SQL Extractor + +--- + +**Last updated: 2025-10-27** + +## Information Collection and Use + +SimplyReports SQL Extractor does **not collect, store, or transmit any personal information or user data**. This extension operates entirely within your local browser environment. + +## What This Extension Does + +This extension: + +- Scans the current web page for SQL queries in specific HTML elements +- Displays found SQL queries within the extension popup +- Allows you to copy SQL queries to your clipboard +- Enables you to download SQL queries as local files + +## Data Processing + +- **No External Servers**: All processing happens locally in your browser +- **No Data Storage**: No user data, browsing history, or personal information is stored by this extension +- **No Data Transmission**: No information is sent to external servers or third parties +- **Temporary Processing Only**: SQL queries are only held temporarily in memory while you use the extension + +## Permissions Used + +This extension requests the following permissions: + +- **activeTab**: To read content from the current web page to extract SQL queries +- **clipboardWrite**: To copy SQL queries to your clipboard when requested +- **downloads**: To save SQL query files to your local computer when requested + +## Third-Party Services + +This extension does not integrate with or send data to any third-party services, analytics platforms, or external APIs. + +## Changes to This Policy + +Any updates to this privacy policy will be reflected in the extension's listing on the Chrome Web Store. + +## Contact + +If you have questions about this privacy policy, please contact cyberpunklibrarian [at] protonmail [dot] com \ No newline at end of file