From da9084d8737b392d254977e67322fa44f8d4bd3f Mon Sep 17 00:00:00 2001 From: Daniel Messer Date: Thu, 23 Oct 2025 13:12:13 -0400 Subject: [PATCH] Initial commit. I think this works. --- .gitignore | 27 ++++++++ INSTALL.md | 11 ++++ README.md | 18 ++++++ background.js | 29 +++++++++ content.js | 89 +++++++++++++++++++++++++++ icon-128.png | Bin 0 -> 7561 bytes icon-16.png | Bin 0 -> 577 bytes icon-32.png | Bin 0 -> 1103 bytes icon-48.png | Bin 0 -> 990 bytes icon.svg | 11 ++++ manifest.json | 42 +++++++++++++ popup.html | 149 ++++++++++++++++++++++++++++++++++++++++++++ popup.js | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 543 insertions(+) create mode 100644 .gitignore create mode 100644 INSTALL.md create mode 100644 README.md create mode 100644 background.js create mode 100644 content.js create mode 100644 icon-128.png create mode 100644 icon-16.png create mode 100644 icon-32.png create mode 100644 icon-48.png create mode 100644 icon.svg create mode 100644 manifest.json create mode 100644 popup.html create mode 100644 popup.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..34ebcc7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# ---> macOS +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..9d03a66 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,11 @@ +# Installing SimplyReports SQL Extractor (SiReS Ex) + +1. Download ```sires-ex.xpi``` +2. Drag that file into Firefox +3. Click "Add" when prompted + +## Troubleshooting + +- If the icon doesn't appear, check the Extensions page (`about:addons`) +- Make sure the target page has an input with id or name "SQLStatementHide" +- Check browser console for any error messages diff --git a/README.md b/README.md new file mode 100644 index 0000000..0243a2b --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# SimplyReports SQL Extractor (SiReS Ex) + +The SimplyReports SQL Extractor is a Firefox add-on that will pull the SQL query from a SimplyReports results page. After extracting the query, it allows you to copy that query to the clipboard or save it as a file. + +## Features + +- Visual indicator when a SimplyReports SQL query is found on a page +- Copy extracted SQL to clipboard +- Save SQL as .sql file with custom filename +- Basic SQL formatting for better readability +- Auto-extraction when opening the extension popup + +## Usage + +1. Build a report in SimplyReports. +2. Run the report. +3. On the results page, you should see a green indicator on the top right of the page that indicates that SiReS Ex found a SimplyReports query in your results page. +4. Click the add-on's icon and you'll be prompted to copy the query to the clipboard or save the query to a file. diff --git a/background.js b/background.js new file mode 100644 index 0000000..49f86bb --- /dev/null +++ b/background.js @@ -0,0 +1,29 @@ +// background.js - Background script for handling downloads + +browser.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 + browser.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/content.js b/content.js new file mode 100644 index 0000000..3054834 --- /dev/null +++ b/content.js @@ -0,0 +1,89 @@ +// content.js - Content script that runs on web pages + +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 +browser.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/icon-128.png b/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..56f5fce210f6cad9215a6e36e2ebd6539abc926e GIT binary patch literal 7561 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 literal 0 HcmV?d00001 diff --git a/icon-16.png b/icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..0508a1bfbf0d5cfed7dcf713604f8205c0ef29dd GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbL!Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZKdx@Ck8MocdV-f~I{|oc37(NP@VZK;*PfAQ2!N zB=rfzoeEL|VuNHq%TN6zHx08UuA6YxTYt8zsJpHrWluw#7KgVzWW<2kU?37PlE5G)h z{9!Qnv;LgV-YdTr?Eb#y`mfd3f2mCWoUr+u`b>~t{{R2q&+O<3^shikkYBLT&R@TF z8t&Zr^T*E}hC6>5axPhP3@FE#21szvL3AY`ixT{C^pw5`fXYJMgCoT|0P&R`7bkmWDu_t{`zd$ zcKZT{S((2+mbjg@e9ky&k%6=2zka1x;-9OG-|ymIVmojBL+cq1N}*2=Uf_H-EisbK ypFh+8#I~OfT#G*CpRBfHO4*e3Av*9xE#sXANw%9C4=MoN&fw|l=d#Wzp$P!xdJhW# literal 0 HcmV?d00001 diff --git a/icon-32.png b/icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..3f7c952d54ff410e91e668c08916f1c1927d0bb4 GIT binary patch literal 1103 zcmXw13s6%>6n&9s_{wye7ORK}1WGINX%%Wk1t9|gLXna|ESB)GD6NH7EMt|A6b1#` zN=XPLI7%o3g#RZ5160HUL9qfYg+%0&A`%NI@4dWyUIN`Xbm#2dd(WPGc4v04MT7_0 zSnjlhAjpOh92f~!ifNjgff)E};h?EV431<%(DyD7l#>TRdO+nYLC^(H2ztSWppVNS z$S$oydfXR+2+I*+QFICyp_U?45IG11fV&AmQUG5HgT%2EapGuz1~33LK4#LtR&FLh z#)T=R0RQu*UXYvGUneG8S_MYsnFKCe%+cg#YN3{Fo?H;?<=MxA&_{(!b;7Ecso<&SmZX?>wg z+dGT@G^{;bg-U1eHiiD?sP6th_}5ZRM1y+vm9a>s-77#mZz8@zG`nBBh#2Tazro>^ZP=8FFn%+x`T%FM)TJz|K76z%BH;OzT^P#Q zYUD|Qur}sWJ#&RsU%TQl(RH52gtKJNRx?){iPU7fXrGLOomO|c3u<^0GkA^5u9V%- z>Z)@5-0aK!@43?6EXpV67yjT$cIOYhv81svr}`Ovuk_v0ZoR>6lnv+iT&5G;-6FTx z`F+DU8*`CYQU^HIu=(rPhtVrERvO0xC)^XX`VYHYkiC}}<#L?V-{ ztwjYL`uXCq)-{WOXm5u;a?QZPU+Bp4w4q-)4nGieCOzw6WjwObCvk|8Z&2lsauSKa+RdYe|_|g!Q*Osr#B!UpDXg_6jvS<#N`^t8@G? zoAz=s&5o@|sczjtiHplKbCe(EEC(dMQ<$)URam6$FRW}i5nLSIaN>x&gEMX07v|4P oeko22NlW2h_tyfZ4;oNX;s5{u literal 0 HcmV?d00001 diff --git a/icon-48.png b/icon-48.png new file mode 100644 index 0000000000000000000000000000000000000000..f3fc2b318519ef36b0c399c7542a47c6709382ad GIT binary patch literal 990 zcmV<410np0P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw0004iP)t-s0000Ylk_2#^B|V< zA(r$am-8c+^CXi0AeQqXmh&N&^C6b=E2{q@mh&K%^CFh>A(ital=C2$@*HM+U`(Ly6&FB0?ru8YB^J}^H_4@wh^ZqEA^O4K>md*LB*!x1J^_b52Xt?(@ zpY+n|{r>;|*X;ex==}8h{%E)NiO2aOmh(!f^@PRv!Q%Yi@ckv2^Ty=-`u+bTm-BGD z_j|zjVYK$8)%roD^_P*zEmUvG!1`_WAt(CzL^43qOvnHL00(qQO+^Rk3KtItHE2(Qv;Y7Bq)9|UR7l6wmiaeAK@`Ww zQz4MrWfhHe~n`He5=nWR$V<07lwBV?VjBOhn+}0m?}QpI{~yKEX=( zc!1v)>gpRB?NZUy+|pVH(AM7ZqtNMac6B>@09?I@=;(ufbpI#}pa%wrhG7Ip$GkrO zctA~57y6ocK zJ|58G5RcS^;}h5E+4)7_(jUEwT!(MyDaCv~Ur157jX4qHcL4Vr4;Y<(q=J-QL;b$+yO@GS^ZoYB}a36Z(6BP0?||n z40&F5XbBzqkmu!3i8fD@7J*gB81lT*J&1_%yyC?n!e0{-2~I&V#LCB4@;_NxKB0=} z^gO)YMo2M_Akd$Hp_-kqsg*t(1|wrvdZ9M=pD~r2RiA5%xymN7pSfx#fVSD^+W-In M07*qoM6N<$g0zDjGynhq literal 0 HcmV?d00001 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..8145d60 --- /dev/null +++ b/icon.svg @@ -0,0 +1,11 @@ + + + + + + SQL + + + + + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f7c314d --- /dev/null +++ b/manifest.json @@ -0,0 +1,42 @@ +{ + "manifest_version": 2, + "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", + "clipboardWrite", + "downloads" + ], + + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], + + "background": { + "scripts": ["background.js"], + "persistent": false + }, + + "browser_action": { + "default_popup": "popup.html", + "default_title": "Extract SQL Query", + "default_icon": { + "16": "icon-16.png", + "32": "icon-32.png", + "48": "icon-48.png", + "128": "icon-128.png" + } + }, + + "icons": { + "16": "icon-16.png", + "32": "icon-32.png", + "48": "icon-48.png", + "128": "icon-128.png" + } +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..cd7ccb2 --- /dev/null +++ b/popup.html @@ -0,0 +1,149 @@ + + + + + + + +
+

SQL Query Extractor

+
+ + + + + +
+ + + +
+ +
+ Click "Extract SQL Query" to search for SQL in the current page +
+ + + + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..24ad42b --- /dev/null +++ b/popup.js @@ -0,0 +1,167 @@ +// popup.js - Popup script for user interactions + +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', function() { + extractBtn.disabled = true; + extractBtn.textContent = 'Extracting...'; + + // Send message to content script + browser.tabs.query({ active: true, currentWindow: true }, function(tabs) { + browser.tabs.sendMessage(tabs[0].id, { action: "extractSQL" }, function(response) { + 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; + } + }); + }); + }); + + // Copy to Clipboard + copyBtn.addEventListener('click', function() { + if (!currentSQLQuery) return; + + // 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')); + + navigator.clipboard.writeText(currentSQLQuery).then(function() { + const originalText = copyBtn.textContent; + copyBtn.textContent = 'Copied!'; + copyBtn.style.backgroundColor = '#28a745'; + + setTimeout(function() { + copyBtn.textContent = originalText; + copyBtn.style.backgroundColor = ''; + }, 1500); + }).catch(function(err) { + showStatus('error', 'Failed to copy to clipboard: ' + err.message); + }); + }); + + // Download as File + downloadBtn.addEventListener('click', function() { + if (!currentSQLQuery) return; + + downloadBtn.disabled = true; + downloadBtn.textContent = 'Downloading...'; + + const filename = filenameInput.value.trim() || null; + + // Send message to background script + browser.runtime.sendMessage({ + action: "downloadSQL", + sqlQuery: currentSQLQuery, + filename: filename + }, function(response) { + 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')); + } + }); + }); + + 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 + browser.tabs.query({ active: true, currentWindow: true }, function(tabs) { + const currentTab = tabs[0]; + if (currentTab.url && !currentTab.url.startsWith('chrome://') && !currentTab.url.startsWith('moz-extension://')) { + // Auto-extract after a short delay + setTimeout(function() { + extractBtn.click(); + }, 500); + } + }); +});