Added Chrome extension
This commit is contained in:
29
Chrome/Code/background.js
Normal file
29
Chrome/Code/background.js
Normal file
@@ -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
|
||||
}
|
||||
});
|
||||
89
Chrome/Code/content.js
Normal file
89
Chrome/Code/content.js
Normal file
@@ -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();
|
||||
}
|
||||
BIN
Chrome/Code/icon128.png
Normal file
BIN
Chrome/Code/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
Chrome/Code/icon16.png
Normal file
BIN
Chrome/Code/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 719 B |
BIN
Chrome/Code/icon32.png
Normal file
BIN
Chrome/Code/icon32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 958 B |
BIN
Chrome/Code/icon48.png
Normal file
BIN
Chrome/Code/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
40
Chrome/Code/manifest.json
Normal file
40
Chrome/Code/manifest.json
Normal file
@@ -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": ["<all_urls>"],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
149
Chrome/Code/popup.html
Normal file
149
Chrome/Code/popup.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
width: 350px;
|
||||
padding: 15px;
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.header h3 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.sql-preview {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.filename-input {
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h3>SiReS Ex - SimplyReports SQL Extractor</h3>
|
||||
</div>
|
||||
|
||||
<div id="status" class="status" style="display: none;"></div>
|
||||
|
||||
<div id="sqlPreview" class="sql-preview" style="display: none;"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="extractBtn" class="btn-primary">Extract SQL Query</button>
|
||||
|
||||
<div id="actionButtons" style="display: none;">
|
||||
<button id="copyBtn" class="btn-success">Copy to Clipboard</button>
|
||||
<input type="text" id="filenameInput" class="filename-input" placeholder="Enter filename (optional)">
|
||||
<button id="downloadBtn" class="btn-secondary">Save as File</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Click "Extract SQL Query" to search for SQL in the current page
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
182
Chrome/Code/popup.js
Normal file
182
Chrome/Code/popup.js
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
45
Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md
Normal file
45
Chrome/Privacy Policy/Privacy Policy for SiReS Ex.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user