Initial Commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -0,0 +1,328 @@
|
||||
# Build Instructions
|
||||
|
||||
This document contains detailed instructions for building and distributing the Greasemonkey browser extension.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Web browser (Chrome or Firefox)
|
||||
- Text editor (for development)
|
||||
- Node.js and npm (optional, for Firefox automated builds)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Testing Locally
|
||||
|
||||
#### Chrome
|
||||
```bash
|
||||
1. Open Chrome
|
||||
2. Navigate to chrome://extensions/
|
||||
3. Enable "Developer mode" (top-right toggle)
|
||||
4. Click "Load unpacked"
|
||||
5. Select the greasemonkey/chrome directory
|
||||
```
|
||||
|
||||
#### Firefox
|
||||
```bash
|
||||
1. Open Firefox
|
||||
2. Navigate to about:debugging#/runtime/this-firefox
|
||||
3. Click "Load Temporary Add-on"
|
||||
4. Select greasemonkey/firefox/manifest.json
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Making Changes
|
||||
|
||||
Edit files in the appropriate directory (`chrome/` or `firefox/`).
|
||||
|
||||
**Key files:**
|
||||
- `manifest.json` - Extension configuration
|
||||
- `popup.html` - UI structure
|
||||
- `popup.css` - UI styling
|
||||
- `popup.js` - UI functionality
|
||||
- `content.js` - Code that runs on web pages
|
||||
- `background.js` - Background service/script
|
||||
|
||||
### 2. Testing Changes
|
||||
|
||||
After making changes:
|
||||
|
||||
**Chrome:**
|
||||
1. Go to `chrome://extensions/`
|
||||
2. Click the reload icon on the Greasemonkey extension card
|
||||
3. Test your changes
|
||||
|
||||
**Firefox:**
|
||||
1. Go to `about:debugging#/runtime/this-firefox`
|
||||
2. Click "Reload" on the Greasemonkey extension
|
||||
3. Test your changes
|
||||
|
||||
### 3. Debugging
|
||||
|
||||
**Chrome:**
|
||||
- Background script logs: `chrome://extensions/` → "service worker" link
|
||||
- Content script logs: Browser console on any page (F12)
|
||||
- Popup logs: Right-click popup → "Inspect"
|
||||
|
||||
**Firefox:**
|
||||
- Background script logs: `about:debugging` → "Inspect" on extension
|
||||
- Content script logs: Browser console on any page (F12)
|
||||
- Popup logs: Right-click popup → "Inspect Element"
|
||||
|
||||
## Building for Distribution
|
||||
|
||||
### Chrome/Edge Build
|
||||
|
||||
#### Method 1: Manual Packaging (Recommended for Testing)
|
||||
|
||||
1. Open Chrome
|
||||
2. Go to `chrome://extensions/`
|
||||
3. Ensure "Developer mode" is enabled
|
||||
4. Click "Pack extension"
|
||||
5. For "Extension root directory", select the `chrome` folder
|
||||
6. Leave "Private key file" empty for first build
|
||||
7. Click "Pack Extension"
|
||||
8. Chrome generates two files:
|
||||
- `chrome.crx` - The packaged extension
|
||||
- `chrome.pem` - Private key (keep this secure for updates!)
|
||||
|
||||
#### Method 2: Chrome Web Store Distribution
|
||||
|
||||
1. Create a developer account at [Chrome Web Store](https://chrome.google.com/webstore/developer/dashboard)
|
||||
2. Pay one-time $5 registration fee
|
||||
3. Create a ZIP file of the `chrome` directory:
|
||||
```bash
|
||||
cd chrome
|
||||
zip -r ../greasemonkey-chrome.zip *
|
||||
```
|
||||
4. Upload to Chrome Web Store dashboard
|
||||
5. Fill in store listing details
|
||||
6. Submit for review
|
||||
|
||||
**Note:** Keep your `.pem` private key file secure. You'll need it for future updates.
|
||||
|
||||
### Firefox Build
|
||||
|
||||
#### Method 1: Temporary Installation (Development)
|
||||
|
||||
Already covered in Quick Start - good for testing only. Temporary add-ons are removed when Firefox restarts.
|
||||
|
||||
#### Method 2: Self-Distribution (Unverified)
|
||||
|
||||
1. Create a ZIP file:
|
||||
```bash
|
||||
cd firefox
|
||||
zip -r ../greasemonkey-firefox.zip *
|
||||
```
|
||||
2. Rename to `.xpi`:
|
||||
```bash
|
||||
mv greasemonkey-firefox.zip greasemonkey-firefox.xpi
|
||||
```
|
||||
3. Users can install by dragging `.xpi` file to Firefox
|
||||
4. Firefox will show "unverified" warning
|
||||
|
||||
#### Method 3: Official Distribution (Recommended)
|
||||
|
||||
1. Install web-ext tool:
|
||||
```bash
|
||||
npm install -g web-ext
|
||||
```
|
||||
|
||||
2. Build the extension:
|
||||
```bash
|
||||
cd firefox
|
||||
web-ext build
|
||||
```
|
||||
This creates a ZIP file in `web-ext-artifacts/` directory
|
||||
|
||||
3. Sign with Mozilla:
|
||||
- Create account at [addons.mozilla.org](https://addons.mozilla.org/developers/)
|
||||
- Get API keys from [Developer Hub](https://addons.mozilla.org/developers/addon/api/key/)
|
||||
- Sign the extension:
|
||||
```bash
|
||||
web-ext sign \
|
||||
--api-key=YOUR_API_KEY \
|
||||
--api-secret=YOUR_API_SECRET
|
||||
```
|
||||
- Or upload manually to addons.mozilla.org
|
||||
|
||||
4. Distribution options:
|
||||
- **Listed**: Public on addons.mozilla.org (requires review)
|
||||
- **Unlisted**: Self-distribute signed XPI (automatic signing)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
greasemonkey/
|
||||
├── chrome/ # Chrome extension
|
||||
│ ├── manifest.json
|
||||
│ ├── background.js
|
||||
│ ├── content.js
|
||||
│ ├── popup.html
|
||||
│ ├── popup.css
|
||||
│ ├── popup.js
|
||||
│ ├── icon16.png
|
||||
│ ├── icon48.png
|
||||
│ └── icon128.png
|
||||
├── firefox/ # Firefox extension
|
||||
│ ├── manifest.json
|
||||
│ ├── background.js
|
||||
│ ├── content.js
|
||||
│ ├── popup.html
|
||||
│ ├── popup.css
|
||||
│ ├── popup.js
|
||||
│ ├── icon16.png
|
||||
│ ├── icon48.png
|
||||
│ └── icon128.png
|
||||
├── README.md
|
||||
└── BUILD.md # This file
|
||||
```
|
||||
|
||||
## Version Updates
|
||||
|
||||
When releasing a new version:
|
||||
|
||||
1. Update version number in both `manifest.json` files:
|
||||
```json
|
||||
"version": "1.1.0"
|
||||
```
|
||||
|
||||
2. Update README.md version history
|
||||
|
||||
3. Test thoroughly in both browsers
|
||||
|
||||
4. Build new packages:
|
||||
- Chrome: Re-pack with same `.pem` key
|
||||
- Firefox: Build and sign new version
|
||||
|
||||
5. Upload to respective stores/distribution channels
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before building for distribution:
|
||||
|
||||
- [ ] Test all features in Chrome
|
||||
- [ ] Test all features in Firefox
|
||||
- [ ] Verify icons display correctly
|
||||
- [ ] Check manifest.json versions match
|
||||
- [ ] Test storage sync functionality
|
||||
- [ ] Verify permissions are appropriate
|
||||
- [ ] Test on various websites
|
||||
- [ ] Check browser console for errors
|
||||
- [ ] Test add/edit/delete rule functionality
|
||||
- [ ] Verify enable/disable toggles work
|
||||
- [ ] Test all match types (URL, Element, Both)
|
||||
- [ ] Review code for security issues
|
||||
- [ ] Update version numbers
|
||||
- [ ] Update documentation
|
||||
|
||||
## File Size Optimization
|
||||
|
||||
To reduce extension size:
|
||||
|
||||
1. Minify JavaScript (optional):
|
||||
```bash
|
||||
npm install -g terser
|
||||
terser popup.js -c -m -o popup.min.js
|
||||
```
|
||||
|
||||
2. Optimize PNG icons:
|
||||
```bash
|
||||
# If you have optipng installed
|
||||
optipng icon*.png
|
||||
```
|
||||
|
||||
3. Remove development files:
|
||||
- Source maps
|
||||
- Test files
|
||||
- Documentation (keep only in repo)
|
||||
|
||||
## Automated Build Script
|
||||
|
||||
You can create a build script for automation:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# build.sh
|
||||
|
||||
VERSION="1.0.0"
|
||||
|
||||
echo "Building Greasemonkey v${VERSION}..."
|
||||
|
||||
# Chrome build
|
||||
echo "Building Chrome extension..."
|
||||
cd chrome
|
||||
zip -r "../dist/greasemonkey-chrome-${VERSION}.zip" * -x "*.DS_Store"
|
||||
cd ..
|
||||
|
||||
# Firefox build
|
||||
echo "Building Firefox extension..."
|
||||
cd firefox
|
||||
zip -r "../dist/greasemonkey-firefox-${VERSION}.zip" * -x "*.DS_Store"
|
||||
cd ..
|
||||
|
||||
echo "Build complete! Files in dist/ directory"
|
||||
```
|
||||
|
||||
Make it executable:
|
||||
```bash
|
||||
chmod +x build.sh
|
||||
mkdir -p dist
|
||||
./build.sh
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Chrome Extension Doesn't Load
|
||||
- Check manifest.json syntax (use JSON validator)
|
||||
- Ensure all referenced files exist
|
||||
- Check browser console for errors
|
||||
|
||||
### Firefox Temporary Add-on Removed
|
||||
- This is expected behavior
|
||||
- Use web-ext for persistent testing:
|
||||
```bash
|
||||
cd firefox
|
||||
web-ext run
|
||||
```
|
||||
|
||||
### Icons Not Showing
|
||||
- Verify icon files exist
|
||||
- Check file paths in manifest.json
|
||||
- Ensure icon sizes are correct (16x16, 48x48, 128x128)
|
||||
|
||||
### Storage Not Persisting
|
||||
- Chrome: Check sync is enabled in browser
|
||||
- Firefox: Temporary add-ons don't persist storage
|
||||
- Use proper signed/installed extension for testing
|
||||
|
||||
## Distribution Platforms
|
||||
|
||||
### Official Stores
|
||||
- [Chrome Web Store](https://chrome.google.com/webstore/developer/dashboard)
|
||||
- [Firefox Add-ons](https://addons.mozilla.org/developers/)
|
||||
- [Microsoft Edge Add-ons](https://partner.microsoft.com/dashboard/microsoftedge/overview)
|
||||
|
||||
### Self-Distribution
|
||||
- GitHub Releases
|
||||
- Personal website
|
||||
- Direct XPI/CRX files
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never commit your Chrome `.pem` private key
|
||||
- Keep Firefox API keys secure
|
||||
- Don't bundle sensitive data in extension
|
||||
- Review all code before distribution
|
||||
- Test on malicious sites carefully
|
||||
|
||||
## Resources
|
||||
|
||||
- [Chrome Extension Documentation](https://developer.chrome.com/docs/extensions/)
|
||||
- [Firefox Extension Documentation](https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions)
|
||||
- [web-ext Documentation](https://extensionworkshop.com/documentation/develop/getting-started-with-web-ext/)
|
||||
- [Bootstrap Documentation](https://getbootstrap.com/docs/5.3/)
|
||||
|
||||
## Support
|
||||
|
||||
For build issues or questions, contact Andy Dixon.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to GreasyBoii will be documented in this file.
|
||||
|
||||
## [1.0.2] - 2024-10-16
|
||||
|
||||
### Fixed
|
||||
- **CRITICAL: CSP Violation** - Fixed Content Security Policy blocking external Bootstrap CDN
|
||||
- **Modal initialization** - Now works correctly with local Bootstrap files
|
||||
- Extension now fully functional in Chrome Manifest V3
|
||||
|
||||
### Changed
|
||||
- **Renamed to GreasyBoii** - Extension renamed from "Greasemonkey" to "GreasyBoii"
|
||||
- **Local Bootstrap** - Downloaded Bootstrap 5.3.2 files locally (227KB CSS + 79KB JS)
|
||||
- Updated all console messages to show "GreasyBoii" branding
|
||||
- Updated all UI text to reflect new name
|
||||
|
||||
### Added
|
||||
- bootstrap.min.css (227KB) - Local Bootstrap CSS
|
||||
- bootstrap.bundle.min.js (79KB) - Local Bootstrap JS with Popper
|
||||
|
||||
## [1.0.1] - 2024-10-16
|
||||
|
||||
### Fixed
|
||||
- **Add Rule button not working** - Fixed Bootstrap modal initialization timing issue
|
||||
- Added fallback initialization for Bootstrap Modal to handle CDN load timing
|
||||
- Improved error handling and logging in popup.js
|
||||
- Added null checks for DOM elements to prevent errors
|
||||
- Added console logging for debugging button clicks
|
||||
|
||||
### Changed
|
||||
- Enhanced `setupEventListeners()` with better error detection
|
||||
- Improved `openRuleModal()` with fallback modal initialization
|
||||
- Added `initializeExtension()` wrapper function for proper initialization order
|
||||
|
||||
### Added
|
||||
- TROUBLESHOOTING.md - Comprehensive troubleshooting guide
|
||||
- Console logging for debugging initialization issues
|
||||
- Better error messages when elements are not found
|
||||
|
||||
## [1.0.0] - 2024-10-16
|
||||
|
||||
### Fixed
|
||||
- **Add Rule button not working** - Fixed Bootstrap modal initialization timing issue
|
||||
- Added fallback initialization for Bootstrap Modal to handle CDN load timing
|
||||
- Improved error handling and logging in popup.js
|
||||
- Added null checks for DOM elements to prevent errors
|
||||
- Added console logging for debugging button clicks
|
||||
|
||||
### Changed
|
||||
- Enhanced `setupEventListeners()` with better error detection
|
||||
- Improved `openRuleModal()` with fallback modal initialization
|
||||
- Added `initializeExtension()` wrapper function for proper initialization order
|
||||
|
||||
### Added
|
||||
- TROUBLESHOOTING.md - Comprehensive troubleshooting guide
|
||||
- Console logging for debugging initialization issues
|
||||
- Better error messages when elements are not found
|
||||
|
||||
## [1.0.0] - 2024-10-16
|
||||
|
||||
### Added
|
||||
- Initial release
|
||||
- Chrome extension (Manifest V3)
|
||||
- Firefox extension (Manifest V2)
|
||||
- URL pattern matching
|
||||
- CSS selector (element) matching
|
||||
- Combined URL + element matching
|
||||
- JavaScript execution
|
||||
- CSS injection
|
||||
- Bootstrap 5 dark theme UI
|
||||
- Add/edit/delete rules
|
||||
- Enable/disable toggles
|
||||
- Persistent synced storage
|
||||
- Complete documentation suite
|
||||
- Example rules library
|
||||
- Interactive test page
|
||||
- Build instructions
|
||||
|
||||
### Features
|
||||
- Flexible matching system (URL, Element, Both)
|
||||
- Modern Bootstrap dark UI
|
||||
- Full CRUD operations for rules
|
||||
- Form validation
|
||||
- Error handling
|
||||
- Cross-browser support (Chrome, Firefox, Edge, Opera)
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
# Greasemonkey Example Rules
|
||||
|
||||
This document contains useful example rules you can use with Greasemonkey. Copy and paste these into your extension to get started quickly.
|
||||
|
||||
## Table of Contents
|
||||
1. [Page Styling](#page-styling)
|
||||
2. [Element Manipulation](#element-manipulation)
|
||||
3. [Content Blocking](#content-blocking)
|
||||
4. [Automation](#automation)
|
||||
5. [Custom Features](#custom-features)
|
||||
|
||||
---
|
||||
|
||||
## Page Styling
|
||||
|
||||
### Dark Mode for Any Site
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `example.com` (replace with your target site)
|
||||
**CSS Code:**
|
||||
```css
|
||||
body {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4a9eff !important;
|
||||
}
|
||||
|
||||
div, p, span, article {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Increase Font Size
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `news-site.com`
|
||||
**CSS Code:**
|
||||
```css
|
||||
body {
|
||||
font-size: 18px !important;
|
||||
line-height: 1.6 !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Animations
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (any site with annoying animations)
|
||||
**CSS Code:**
|
||||
```css
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Color Scheme
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `docs.` (targets documentation sites)
|
||||
**CSS Code:**
|
||||
```css
|
||||
:root {
|
||||
--bg-color: #282c34;
|
||||
--text-color: #abb2bf;
|
||||
--accent-color: #61afef;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color) !important;
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color) !important;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Element Manipulation
|
||||
|
||||
### Highlight Search Results
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `.search-result`
|
||||
**CSS Code:**
|
||||
```css
|
||||
.search-result:hover {
|
||||
background-color: #fff3cd !important;
|
||||
border-left: 4px solid #ffc107 !important;
|
||||
transition: all 0.2s ease !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Expand Collapsed Content
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `.collapsed`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
document.querySelectorAll('.collapsed').forEach(elem => {
|
||||
elem.classList.remove('collapsed');
|
||||
elem.style.display = 'block';
|
||||
});
|
||||
```
|
||||
|
||||
### Add Copy Buttons to Code Blocks
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `pre code`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
document.querySelectorAll('pre code').forEach(block => {
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Copy';
|
||||
button.style.cssText = 'position:absolute;top:5px;right:5px;padding:5px 10px;cursor:pointer;';
|
||||
button.onclick = () => {
|
||||
navigator.clipboard.writeText(block.textContent);
|
||||
button.textContent = 'Copied!';
|
||||
setTimeout(() => button.textContent = 'Copy', 2000);
|
||||
};
|
||||
block.parentElement.style.position = 'relative';
|
||||
block.parentElement.appendChild(button);
|
||||
});
|
||||
```
|
||||
|
||||
### Enhance Tables
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `table`
|
||||
**CSS Code:**
|
||||
```css
|
||||
table {
|
||||
border-collapse: collapse !important;
|
||||
width: 100% !important;
|
||||
margin: 20px 0 !important;
|
||||
}
|
||||
|
||||
table th {
|
||||
background-color: #4CAF50 !important;
|
||||
color: white !important;
|
||||
padding: 12px !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 10px !important;
|
||||
border-bottom: 1px solid #ddd !important;
|
||||
}
|
||||
|
||||
table tr:hover {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Content Blocking
|
||||
|
||||
### Hide Advertisements
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (target site)
|
||||
**CSS Code:**
|
||||
```css
|
||||
.ad,
|
||||
.ads,
|
||||
.advertisement,
|
||||
[class*="ad-"],
|
||||
[id*="ad-"],
|
||||
.sponsored,
|
||||
.promotion {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Cookie Notices
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (any site)
|
||||
**CSS Code:**
|
||||
```css
|
||||
[class*="cookie"],
|
||||
[id*="cookie"],
|
||||
[class*="consent"],
|
||||
[id*="gdpr"],
|
||||
.privacy-banner {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Hide Social Media Widgets
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (target site)
|
||||
**CSS Code:**
|
||||
```css
|
||||
.social-share,
|
||||
.share-buttons,
|
||||
[class*="social-"],
|
||||
iframe[src*="facebook"],
|
||||
iframe[src*="twitter"] {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Remove Popups
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (target site)
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
// Remove overlay popups
|
||||
document.querySelectorAll('.popup, .modal, .overlay').forEach(el => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
// Re-enable scrolling
|
||||
document.body.style.overflow = 'auto';
|
||||
document.documentElement.style.overflow = 'auto';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation
|
||||
|
||||
### Auto-Close Annoying Popups
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `.modal`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
setTimeout(() => {
|
||||
const closeButton = document.querySelector('.modal .close, .modal [aria-label="Close"]');
|
||||
if (closeButton) {
|
||||
closeButton.click();
|
||||
console.log('Popup auto-closed');
|
||||
}
|
||||
}, 2000);
|
||||
```
|
||||
|
||||
### Auto-Accept Cookies
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `[class*="cookie"]`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
const acceptButton = document.querySelector(
|
||||
'button[id*="accept"], button[class*="accept"], .cookie-accept, #acceptCookies'
|
||||
);
|
||||
if (acceptButton) {
|
||||
acceptButton.click();
|
||||
console.log('Cookies auto-accepted');
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Fill Test Form
|
||||
**Match Type:** Both URL and element
|
||||
**URL Pattern:** `test-form.html`
|
||||
**Element Selector:** `#myForm`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
document.getElementById('username').value = 'testuser';
|
||||
document.getElementById('email').value = 'test@example.com';
|
||||
console.log('Form auto-filled');
|
||||
```
|
||||
|
||||
### Skip Video Ads
|
||||
**Match Type:** Page contains element
|
||||
**Element Selector:** `.video-player`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
setInterval(() => {
|
||||
const skipButton = document.querySelector('.skip-ad, .ytp-ad-skip-button');
|
||||
if (skipButton && skipButton.offsetParent !== null) {
|
||||
skipButton.click();
|
||||
console.log('Ad skipped');
|
||||
}
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Features
|
||||
|
||||
### Add Floating Note Pad
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (any site where you want notes)
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
const notepad = document.createElement('div');
|
||||
notepad.innerHTML = `
|
||||
<div style="position:fixed;top:10px;right:10px;width:300px;z-index:10000;background:white;border:2px solid #333;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
|
||||
<div style="background:#333;color:white;padding:10px;cursor:move;border-radius:6px 6px 0 0;">
|
||||
Quick Notes
|
||||
</div>
|
||||
<textarea id="greasemonkey-notes" style="width:100%;height:200px;padding:10px;border:none;font-family:monospace;"></textarea>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(notepad);
|
||||
|
||||
// Load saved notes
|
||||
const notes = localStorage.getItem('greasemonkey-notes') || '';
|
||||
document.getElementById('greasemonkey-notes').value = notes;
|
||||
|
||||
// Save notes on change
|
||||
document.getElementById('greasemonkey-notes').addEventListener('input', (e) => {
|
||||
localStorage.setItem('greasemonkey-notes', e.target.value);
|
||||
});
|
||||
```
|
||||
|
||||
### Add Page Stats Widget
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (any site)
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
const stats = document.createElement('div');
|
||||
stats.style.cssText = 'position:fixed;bottom:10px;right:10px;background:#333;color:white;padding:10px;border-radius:8px;font-size:12px;z-index:10000;';
|
||||
|
||||
const updateStats = () => {
|
||||
const wordCount = document.body.innerText.split(/\s+/).length;
|
||||
const linkCount = document.querySelectorAll('a').length;
|
||||
const imageCount = document.querySelectorAll('img').length;
|
||||
|
||||
stats.innerHTML = `
|
||||
<strong>Page Stats</strong><br>
|
||||
Words: ${wordCount}<br>
|
||||
Links: ${linkCount}<br>
|
||||
Images: ${imageCount}
|
||||
`;
|
||||
};
|
||||
|
||||
document.body.appendChild(stats);
|
||||
updateStats();
|
||||
```
|
||||
|
||||
### Reading Mode
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `article` or `blog`
|
||||
**CSS Code:**
|
||||
```css
|
||||
body * {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
article,
|
||||
article *,
|
||||
.article-content,
|
||||
.article-content *,
|
||||
.post-content,
|
||||
.post-content * {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 800px !important;
|
||||
margin: 0 auto !important;
|
||||
padding: 40px 20px !important;
|
||||
background: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
article {
|
||||
background: white !important;
|
||||
padding: 40px !important;
|
||||
line-height: 1.8 !important;
|
||||
font-size: 18px !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Add Custom Keyboard Shortcuts
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (target site)
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Ctrl+Shift+D - Toggle dark mode
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
|
||||
document.body.style.filter =
|
||||
document.body.style.filter === 'invert(1)' ? '' : 'invert(1)';
|
||||
}
|
||||
|
||||
// Ctrl+Shift+H - Hide images
|
||||
if (e.ctrlKey && e.shiftKey && e.key === 'H') {
|
||||
document.querySelectorAll('img').forEach(img => {
|
||||
img.style.display = img.style.display === 'none' ? '' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Scroll Progress Bar
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** (any site with long content)
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
const progressBar = document.createElement('div');
|
||||
progressBar.style.cssText = 'position:fixed;top:0;left:0;width:0%;height:4px;background:linear-gradient(90deg,#667eea,#764ba2);z-index:10000;transition:width 0.3s;';
|
||||
document.body.appendChild(progressBar);
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const winScroll = document.documentElement.scrollTop;
|
||||
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
const scrolled = (winScroll / height) * 100;
|
||||
progressBar.style.width = scrolled + '%';
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tips for Creating Your Own Rules
|
||||
|
||||
1. **Test in Console First**: Try your JavaScript in the browser console (F12) before adding to a rule
|
||||
2. **Use !important**: CSS often needs `!important` to override existing styles
|
||||
3. **Check Element Selectors**: Right-click → Inspect to find correct selectors
|
||||
4. **Start Simple**: Begin with basic rules and add complexity
|
||||
5. **Use Console Logging**: Add `console.log()` to debug your JavaScript
|
||||
6. **Test Match Conditions**: Verify URL patterns and selectors match correctly
|
||||
|
||||
## Combining Rules
|
||||
|
||||
You can combine multiple techniques in one rule:
|
||||
|
||||
**Match Type:** Both URL and element
|
||||
**URL Pattern:** `github.com`
|
||||
**Element Selector:** `.repository-content`
|
||||
**CSS Code:**
|
||||
```css
|
||||
.repository-content {
|
||||
max-width: 1400px !important;
|
||||
margin: 0 auto !important;
|
||||
}
|
||||
```
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
console.log('GitHub enhancer loaded');
|
||||
document.querySelectorAll('.markdown-body').forEach(md => {
|
||||
md.style.fontSize = '16px';
|
||||
md.style.lineHeight = '1.8';
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Waiting for Elements (Mutation Observer)
|
||||
```javascript
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
const targetElement = document.querySelector('.dynamic-element');
|
||||
if (targetElement) {
|
||||
targetElement.style.background = 'yellow';
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
```
|
||||
|
||||
### Periodic Checks
|
||||
```javascript
|
||||
setInterval(() => {
|
||||
const ads = document.querySelectorAll('.ad');
|
||||
ads.forEach(ad => ad.remove());
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
### Event Delegation
|
||||
```javascript
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.matches('.my-button')) {
|
||||
console.log('Button clicked!');
|
||||
e.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Happy scripting! Create your own custom rules and enhance your browsing experience. 🚀
|
||||
@@ -0,0 +1,267 @@
|
||||
# Installing GreasyBoii (Packaged Version)
|
||||
|
||||
Your extensions are now packaged and ready to install!
|
||||
|
||||
## Files Created
|
||||
|
||||
✅ `dist/greasyboii-chrome-v1.0.4.zip` (62 KB)
|
||||
✅ `dist/greasyboii-firefox-v1.0.4.zip` (62 KB)
|
||||
|
||||
---
|
||||
|
||||
## Chrome Installation
|
||||
|
||||
### Method 1: Load Unpacked (Development)
|
||||
1. Open `chrome://extensions/`
|
||||
2. Enable "Developer mode" (top-right toggle)
|
||||
3. Click "Load unpacked"
|
||||
4. Select the `chrome` folder
|
||||
5. ✅ Done!
|
||||
|
||||
### Method 2: Install ZIP (Distribution)
|
||||
1. Unzip `greasyboii-chrome-v1.0.4.zip` to a folder
|
||||
2. Open `chrome://extensions/`
|
||||
3. Enable "Developer mode"
|
||||
4. Click "Load unpacked"
|
||||
5. Select the unzipped folder
|
||||
6. ✅ Done!
|
||||
|
||||
### Method 3: Pack to CRX (Best for Sharing)
|
||||
1. Open `chrome://extensions/`
|
||||
2. Enable "Developer mode"
|
||||
3. Click "Pack extension"
|
||||
4. For "Extension root directory" → Select the `chrome` folder
|
||||
5. Leave "Private key file" empty
|
||||
6. Click "Pack Extension"
|
||||
7. You get `chrome.crx` file (the actual extension)
|
||||
8. Share this `.crx` file with others
|
||||
9. Users drag it to `chrome://extensions/` to install
|
||||
|
||||
**Note:** Chrome will show "This extension is not from the Chrome Web Store" warning for unpublished extensions.
|
||||
|
||||
---
|
||||
|
||||
## Firefox Installation
|
||||
|
||||
### Method 1: Load Temporary (Development)
|
||||
1. Open `about:debugging#/runtime/this-firefox`
|
||||
2. Click "Load Temporary Add-on"
|
||||
3. Select any file from the `firefox` folder (e.g., `manifest.json`)
|
||||
4. ✅ Done! (Removed when Firefox restarts)
|
||||
|
||||
### Method 2: Install ZIP (Distribution)
|
||||
1. Rename `greasyboii-firefox-v1.0.4.zip` to `greasyboii-firefox-v1.0.4.xpi`
|
||||
2. Drag the `.xpi` file into Firefox
|
||||
3. Click "Add" when prompted
|
||||
4. ✅ Done!
|
||||
|
||||
**Note:** Unsigned extensions will show a warning. For production use, sign it through addons.mozilla.org.
|
||||
|
||||
### Method 3: Permanent Install (Signed)
|
||||
For permanent installation without warnings:
|
||||
1. Create account at [addons.mozilla.org](https://addons.mozilla.org/developers/)
|
||||
2. Submit the ZIP file
|
||||
3. Choose "Unlisted" for self-distribution
|
||||
4. Mozilla automatically signs it
|
||||
5. Download the signed XPI
|
||||
6. Install the signed version
|
||||
|
||||
---
|
||||
|
||||
## Edge Installation
|
||||
|
||||
Edge uses Chrome extensions:
|
||||
1. Open `edge://extensions/`
|
||||
2. Enable "Developer mode"
|
||||
3. Click "Load unpacked"
|
||||
4. Select the `chrome` folder
|
||||
5. ✅ Done!
|
||||
|
||||
---
|
||||
|
||||
## Opera Installation
|
||||
|
||||
Opera also uses Chrome extensions:
|
||||
1. Open `opera://extensions/`
|
||||
2. Enable "Developer mode"
|
||||
3. Click "Load unpacked"
|
||||
4. Select the `chrome` folder
|
||||
5. ✅ Done!
|
||||
|
||||
---
|
||||
|
||||
## Verifying Installation
|
||||
|
||||
After installing, you should see:
|
||||
- ✅ GreasyBoii icon in the browser toolbar
|
||||
- ✅ Extension name: "GreasyBoii"
|
||||
- ✅ Version: 1.0.4
|
||||
- ✅ Author: Andy Dixon
|
||||
|
||||
Click the icon to open the popup and test:
|
||||
1. Click "+ Add Rule"
|
||||
2. Modal should open
|
||||
3. Create a test rule
|
||||
4. Visit a matching page
|
||||
5. Script should execute!
|
||||
|
||||
---
|
||||
|
||||
## Testing the Package
|
||||
|
||||
Before sharing, test the packaged version:
|
||||
|
||||
1. **Uninstall the development version** (if loaded)
|
||||
2. **Install from the package** (follow steps above)
|
||||
3. **Test all features:**
|
||||
- ✅ Add rule
|
||||
- ✅ Edit rule
|
||||
- ✅ Delete rule
|
||||
- ✅ Toggle rule
|
||||
- ✅ Visit target page
|
||||
- ✅ Verify script executes
|
||||
- ✅ Check console logs
|
||||
|
||||
---
|
||||
|
||||
## Sharing Your Extension
|
||||
|
||||
### For Chrome Users:
|
||||
Send them:
|
||||
1. The `.crx` file (after packing)
|
||||
2. Installation instructions:
|
||||
- Open `chrome://extensions/`
|
||||
- Enable "Developer mode"
|
||||
- Drag the `.crx` file into the window
|
||||
- Click "Add extension"
|
||||
|
||||
### For Firefox Users:
|
||||
Send them:
|
||||
1. The `.xpi` file (renamed `.zip`)
|
||||
2. Installation instructions:
|
||||
- Drag the `.xpi` file into Firefox
|
||||
- Click "Add"
|
||||
|
||||
### For All Users (Best):
|
||||
1. Publish on Chrome Web Store ($5 one-time fee)
|
||||
2. Publish on Firefox Add-ons (free)
|
||||
3. Users can install with one click
|
||||
|
||||
---
|
||||
|
||||
## Updating the Extension
|
||||
|
||||
When you release a new version:
|
||||
|
||||
### Chrome:
|
||||
1. Update version in `chrome/manifest.json`
|
||||
2. Re-package the extension
|
||||
3. If you packed to `.crx`, use the SAME `.pem` file
|
||||
4. Users get automatic updates if installed from Chrome Web Store
|
||||
|
||||
### Firefox:
|
||||
1. Update version in `firefox/manifest.json`
|
||||
2. Re-package the extension
|
||||
3. Re-sign with Mozilla (if signed)
|
||||
4. Users get automatic updates if installed from Firefox Add-ons
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Chrome: "CRX_REQUIRED_PROOF_MISSING"
|
||||
- **Issue:** Chrome blocks unsigned extensions by default
|
||||
- **Solution:** Users must enable "Developer mode" to install
|
||||
- **Better:** Publish on Chrome Web Store
|
||||
|
||||
### Firefox: "Add-on appears corrupt"
|
||||
- **Issue:** ZIP structure is incorrect
|
||||
- **Solution:** Files must be at ZIP root, not in a subfolder
|
||||
- **Fix:** Re-create ZIP from inside the firefox folder
|
||||
|
||||
### Extension doesn't work after packaging
|
||||
- **Check:** All files are included
|
||||
- **Check:** manifest.json is valid (use JSON validator)
|
||||
- **Check:** Version number is correct
|
||||
- **Test:** Install on a fresh browser profile
|
||||
|
||||
### Chrome: Updates not working
|
||||
- **Issue:** Different `.pem` file used
|
||||
- **Solution:** Always use the same `.pem` file for updates
|
||||
- **Lost .pem?** You'll need to create a new extension (new ID)
|
||||
|
||||
---
|
||||
|
||||
## File Sizes
|
||||
|
||||
Your packaged extensions are:
|
||||
- **Chrome:** 62 KB (zipped)
|
||||
- **Firefox:** 62 KB (zipped)
|
||||
|
||||
Both include:
|
||||
- Extension code
|
||||
- Bootstrap 5.3.2 (CSS + JS)
|
||||
- Icons
|
||||
- Manifest files
|
||||
|
||||
---
|
||||
|
||||
## Publishing to Official Stores
|
||||
|
||||
### Chrome Web Store:
|
||||
1. Cost: $5 one-time developer fee
|
||||
2. URL: https://chrome.google.com/webstore/developer/dashboard
|
||||
3. Upload: The ZIP file (not CRX)
|
||||
4. Review: Usually 1-3 days
|
||||
5. Benefits: Automatic updates, no warnings, discoverable
|
||||
|
||||
### Firefox Add-ons:
|
||||
1. Cost: Free
|
||||
2. URL: https://addons.mozilla.org/developers/
|
||||
3. Upload: The ZIP file
|
||||
4. Options:
|
||||
- **Listed:** Public on addons.mozilla.org (reviewed)
|
||||
- **Unlisted:** Self-distribute signed version (instant)
|
||||
5. Benefits: Automatic signing, automatic updates
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands
|
||||
|
||||
```bash
|
||||
# Package both browsers
|
||||
./package-all.sh
|
||||
|
||||
# Package Chrome only
|
||||
cd chrome && zip -r ../dist/greasyboii-chrome.zip *
|
||||
|
||||
# Package Firefox only
|
||||
cd firefox && zip -r ../dist/greasyboii-firefox.zip *
|
||||
|
||||
# Rename Firefox ZIP to XPI
|
||||
mv dist/greasyboii-firefox-v1.0.4.zip dist/greasyboii-firefox-v1.0.4.xpi
|
||||
|
||||
# Check package contents
|
||||
unzip -l dist/greasyboii-chrome-v1.0.4.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Distribution Checklist
|
||||
|
||||
Before distributing:
|
||||
- ✅ Test packaged version thoroughly
|
||||
- ✅ Verify all features work
|
||||
- ✅ Check version numbers are correct
|
||||
- ✅ Include installation instructions
|
||||
- ✅ Mention browser compatibility
|
||||
- ✅ Provide support contact
|
||||
- ✅ Consider publishing to official stores
|
||||
|
||||
---
|
||||
|
||||
**Current Version:** 1.0.4
|
||||
**Package Size:** 62 KB
|
||||
**Ready to Distribute:** ✅ Yes
|
||||
|
||||
Enjoy sharing GreasyBoii! 🐒✨
|
||||
@@ -0,0 +1,94 @@
|
||||
Greasemonkey Browser Extension
|
||||
Author: Andy Dixon
|
||||
Version: 1.0.0
|
||||
|
||||
=== FILE STRUCTURE ===
|
||||
|
||||
greasemonkey/
|
||||
├── chrome/ Chrome Extension (Manifest V3)
|
||||
│ ├── manifest.json Extension configuration
|
||||
│ ├── background.js Background service worker
|
||||
│ ├── content.js Content script (runs on pages)
|
||||
│ ├── popup.html Extension popup UI
|
||||
│ ├── popup.css Popup styling (Bootstrap Dark)
|
||||
│ ├── popup.js Popup functionality
|
||||
│ ├── icon16.png 16x16 icon
|
||||
│ ├── icon48.png 48x48 icon
|
||||
│ └── icon128.png 128x128 icon
|
||||
│
|
||||
├── firefox/ Firefox Extension (Manifest V2)
|
||||
│ ├── manifest.json Extension configuration
|
||||
│ ├── background.js Background script
|
||||
│ ├── content.js Content script (runs on pages)
|
||||
│ ├── popup.html Extension popup UI
|
||||
│ ├── popup.css Popup styling (Bootstrap Dark)
|
||||
│ ├── popup.js Popup functionality
|
||||
│ ├── icon16.png 16x16 icon
|
||||
│ ├── icon48.png 48x48 icon
|
||||
│ └── icon128.png 128x128 icon
|
||||
│
|
||||
├── README.md Full documentation
|
||||
├── BUILD.md Build and distribution instructions
|
||||
├── QUICKSTART.md Quick start guide
|
||||
├── test-page.html Test page for trying rules
|
||||
└── MANIFEST.txt This file
|
||||
|
||||
=== FEATURES ===
|
||||
|
||||
✓ Execute custom JavaScript on any webpage
|
||||
✓ Inject custom CSS styles on any webpage
|
||||
✓ Three matching modes:
|
||||
- URL contains string
|
||||
- Page contains element (CSS selector)
|
||||
- Both URL and element match
|
||||
✓ Bootstrap 5 Dark theme UI
|
||||
✓ Add, edit, delete, and toggle rules
|
||||
✓ Persistent storage (synced across browser)
|
||||
✓ Multiple rules support
|
||||
✓ Visual rule indicators (type badges, JS/CSS badges)
|
||||
✓ Chrome and Firefox compatible
|
||||
|
||||
=== QUICK INSTALL ===
|
||||
|
||||
Chrome:
|
||||
1. chrome://extensions/
|
||||
2. Enable "Developer mode"
|
||||
3. Click "Load unpacked"
|
||||
4. Select the chrome/ folder
|
||||
|
||||
Firefox:
|
||||
1. about:debugging#/runtime/this-firefox
|
||||
2. Click "Load Temporary Add-on"
|
||||
3. Select firefox/manifest.json
|
||||
|
||||
=== USAGE ===
|
||||
|
||||
1. Click Greasemonkey icon in toolbar
|
||||
2. Click "+ Add Rule"
|
||||
3. Configure:
|
||||
- Name your rule
|
||||
- Choose match type
|
||||
- Enter URL pattern and/or element selector
|
||||
- Add JavaScript and/or CSS code
|
||||
4. Save and reload target page
|
||||
|
||||
=== DOCUMENTATION ===
|
||||
|
||||
- README.md - Complete feature documentation and examples
|
||||
- BUILD.md - Building and distribution instructions
|
||||
- QUICKSTART.md - 5-minute getting started guide
|
||||
- test-page.html - Interactive test page
|
||||
|
||||
=== NEXT STEPS ===
|
||||
|
||||
1. Read QUICKSTART.md to get started
|
||||
2. Try the test-page.html for examples
|
||||
3. Create your first rule
|
||||
4. Read README.md for advanced features
|
||||
5. See BUILD.md for distribution
|
||||
|
||||
=== SUPPORT ===
|
||||
|
||||
For questions or issues, contact Andy Dixon.
|
||||
|
||||
Enjoy! 🐒✨
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
# How to Package GreasyBoii Extension
|
||||
|
||||
This guide shows you how to package GreasyBoii for distribution.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Chrome/Edge (Easy Method)
|
||||
1. Open `chrome://extensions/`
|
||||
2. Enable "Developer mode" (top-right toggle)
|
||||
3. Click "Pack extension"
|
||||
4. For "Extension root directory" → Select the `chrome` folder
|
||||
5. Leave "Private key file" empty (first time)
|
||||
6. Click "Pack Extension"
|
||||
7. ✅ Done! You get 2 files:
|
||||
- `chrome.crx` - The packaged extension
|
||||
- `chrome.pem` - Private key (KEEP THIS SECURE!)
|
||||
|
||||
### Firefox (Command Line)
|
||||
```bash
|
||||
cd firefox
|
||||
zip -r ../greasyboii-firefox-v1.0.4.zip *
|
||||
```
|
||||
Done! You get `greasyboii-firefox-v1.0.4.zip`
|
||||
|
||||
---
|
||||
|
||||
## Detailed Instructions
|
||||
|
||||
## Chrome Extension Packaging
|
||||
|
||||
### Method 1: Chrome Built-in Packer (Recommended)
|
||||
|
||||
**Step 1: Open Extensions Page**
|
||||
```
|
||||
chrome://extensions/
|
||||
```
|
||||
|
||||
**Step 2: Enable Developer Mode**
|
||||
- Toggle the "Developer mode" switch in the top-right corner
|
||||
|
||||
**Step 3: Pack Extension**
|
||||
1. Click "Pack extension" button
|
||||
2. In the dialog:
|
||||
- **Extension root directory**: Browse and select `/path/to/greasemonkey/chrome`
|
||||
- **Private key file**: Leave empty (first time) or select your `.pem` file
|
||||
3. Click "Pack Extension"
|
||||
|
||||
**Step 4: Files Created**
|
||||
You'll get two files in the parent directory:
|
||||
- `chrome.crx` - The packed extension (ready to distribute)
|
||||
- `chrome.pem` - Your private key (KEEP THIS SECRET!)
|
||||
|
||||
**Important Notes:**
|
||||
- ⚠️ **Keep the `.pem` file secure** - You need it to update your extension
|
||||
- ⚠️ **Never share your `.pem` file** - Anyone with it can impersonate your extension
|
||||
- ✅ The `.crx` file is what you distribute to users
|
||||
|
||||
### Method 2: Command Line (Using Chrome)
|
||||
|
||||
```bash
|
||||
# Navigate to Chrome binary location
|
||||
# macOS
|
||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --pack-extension=/path/to/greasemonkey/chrome
|
||||
|
||||
# Windows
|
||||
"C:\Program Files\Google\Chrome\Application\chrome.exe" --pack-extension=C:\path\to\greasemonkey\chrome
|
||||
|
||||
# Linux
|
||||
google-chrome --pack-extension=/path/to/greasemonkey/chrome
|
||||
```
|
||||
|
||||
### Method 3: Create ZIP for Chrome Web Store
|
||||
|
||||
If you want to publish on Chrome Web Store:
|
||||
|
||||
```bash
|
||||
cd chrome
|
||||
zip -r ../greasyboii-chrome-v1.0.4.zip *
|
||||
```
|
||||
|
||||
Then upload the ZIP to [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)
|
||||
|
||||
---
|
||||
|
||||
## Firefox Extension Packaging
|
||||
|
||||
### Method 1: Simple ZIP (Recommended for Testing)
|
||||
|
||||
**Create ZIP:**
|
||||
```bash
|
||||
cd firefox
|
||||
zip -r ../greasyboii-firefox-v1.0.4.zip *
|
||||
```
|
||||
|
||||
**Rename to XPI (optional):**
|
||||
```bash
|
||||
mv greasyboii-firefox-v1.0.4.zip greasyboii-firefox-v1.0.4.xpi
|
||||
```
|
||||
|
||||
**Install:**
|
||||
- Drag the `.xpi` file into Firefox
|
||||
- Or: `about:addons` → gear icon → "Install Add-on From File"
|
||||
|
||||
**Note:** Unsigned extensions show a warning. For permanent use, you need to sign it.
|
||||
|
||||
### Method 2: Using web-ext (Recommended for Distribution)
|
||||
|
||||
**Install web-ext:**
|
||||
```bash
|
||||
npm install -g web-ext
|
||||
```
|
||||
|
||||
**Build:**
|
||||
```bash
|
||||
cd firefox
|
||||
web-ext build
|
||||
```
|
||||
|
||||
This creates a signed ZIP in `web-ext-artifacts/` folder.
|
||||
|
||||
**Sign (requires Mozilla account):**
|
||||
```bash
|
||||
# Get API keys from https://addons.mozilla.org/developers/addon/api/key/
|
||||
web-ext sign \
|
||||
--api-key=YOUR_API_KEY \
|
||||
--api-secret=YOUR_API_SECRET
|
||||
```
|
||||
|
||||
**Run for testing:**
|
||||
```bash
|
||||
web-ext run
|
||||
```
|
||||
|
||||
This opens Firefox with your extension loaded.
|
||||
|
||||
---
|
||||
|
||||
## Distribution Options
|
||||
|
||||
### Option 1: Direct Distribution (Self-Hosted)
|
||||
|
||||
**Chrome (.crx file):**
|
||||
1. Pack the extension (see above)
|
||||
2. Host the `.crx` file on your website
|
||||
3. Users download and drag it to `chrome://extensions/`
|
||||
4. ⚠️ Chrome will show "unverified" warning
|
||||
|
||||
**Firefox (.xpi file):**
|
||||
1. Create and sign the extension
|
||||
2. Host the `.xpi` file on your website
|
||||
3. Users click to install
|
||||
4. ⚠️ Unsigned extensions need special Firefox config
|
||||
|
||||
### Option 2: Official Store Distribution
|
||||
|
||||
**Chrome Web Store:**
|
||||
1. Create developer account ($5 one-time fee)
|
||||
2. Go to [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard)
|
||||
3. Click "New Item"
|
||||
4. Upload your ZIP file
|
||||
5. Fill in listing details
|
||||
6. Submit for review
|
||||
7. ✅ Published after approval (usually 1-3 days)
|
||||
|
||||
**Firefox Add-ons:**
|
||||
1. Create account at [addons.mozilla.org](https://addons.mozilla.org/developers/)
|
||||
2. Submit new add-on
|
||||
3. Upload your ZIP/XPI
|
||||
4. Choose:
|
||||
- **Listed**: Public on addons.mozilla.org (requires review)
|
||||
- **Unlisted**: Self-distribute signed version (automatic)
|
||||
5. ✅ Get signed version immediately (unlisted) or after review (listed)
|
||||
|
||||
---
|
||||
|
||||
## Automation Scripts
|
||||
|
||||
I've created helper scripts for you:
|
||||
|
||||
### package-chrome.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
cd chrome
|
||||
zip -r ../dist/greasyboii-chrome-v1.0.4.zip * -x "*.DS_Store"
|
||||
echo "✅ Chrome package created: dist/greasyboii-chrome-v1.0.4.zip"
|
||||
```
|
||||
|
||||
### package-firefox.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
cd firefox
|
||||
zip -r ../dist/greasyboii-firefox-v1.0.4.zip * -x "*.DS_Store"
|
||||
echo "✅ Firefox package created: dist/greasyboii-firefox-v1.0.4.zip"
|
||||
```
|
||||
|
||||
### package-all.sh
|
||||
```bash
|
||||
#!/bin/bash
|
||||
mkdir -p dist
|
||||
|
||||
echo "📦 Packaging Chrome extension..."
|
||||
cd chrome
|
||||
zip -r ../dist/greasyboii-chrome-v1.0.4.zip * -x "*.DS_Store"
|
||||
cd ..
|
||||
|
||||
echo "📦 Packaging Firefox extension..."
|
||||
cd firefox
|
||||
zip -r ../dist/greasyboii-firefox-v1.0.4.zip * -x "*.DS_Store"
|
||||
cd ..
|
||||
|
||||
echo "✅ All packages created in dist/ folder!"
|
||||
ls -lh dist/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Checklist
|
||||
|
||||
Before packaging, ensure these files exist:
|
||||
|
||||
### Chrome folder must have:
|
||||
- ✅ manifest.json
|
||||
- ✅ background.js
|
||||
- ✅ content.js
|
||||
- ✅ popup.html
|
||||
- ✅ popup.css
|
||||
- ✅ popup.js
|
||||
- ✅ bootstrap.min.css
|
||||
- ✅ bootstrap.bundle.min.js
|
||||
- ✅ icon16.png
|
||||
- ✅ icon48.png
|
||||
- ✅ icon128.png
|
||||
|
||||
### Firefox folder must have:
|
||||
- ✅ manifest.json
|
||||
- ✅ background.js
|
||||
- ✅ content.js
|
||||
- ✅ popup.html
|
||||
- ✅ popup.css
|
||||
- ✅ popup.js
|
||||
- ✅ bootstrap.min.css
|
||||
- ✅ bootstrap.bundle.min.js
|
||||
- ✅ icon16.png
|
||||
- ✅ icon48.png
|
||||
- ✅ icon128.png
|
||||
|
||||
---
|
||||
|
||||
## Testing Packaged Extension
|
||||
|
||||
### Chrome:
|
||||
1. Go to `chrome://extensions/`
|
||||
2. Drag the `.crx` file into the window
|
||||
3. Click "Add extension"
|
||||
4. Test all features
|
||||
|
||||
### Firefox:
|
||||
1. Go to `about:addons`
|
||||
2. Click gear icon → "Install Add-on From File"
|
||||
3. Select your `.xpi` or `.zip` file
|
||||
4. Click "Add"
|
||||
5. Test all features
|
||||
|
||||
---
|
||||
|
||||
## Version Updates
|
||||
|
||||
When releasing a new version:
|
||||
|
||||
1. **Update version in manifests:**
|
||||
```json
|
||||
"version": "1.0.5"
|
||||
```
|
||||
|
||||
2. **For Chrome:**
|
||||
- Use the SAME `.pem` file when packing
|
||||
- This ensures it's recognized as an update
|
||||
|
||||
3. **For Firefox:**
|
||||
- Increment version number
|
||||
- Re-sign with same API keys
|
||||
- Upload to addons.mozilla.org
|
||||
|
||||
4. **Update documentation:**
|
||||
- README.md
|
||||
- CHANGELOG.md
|
||||
- Any version references
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Chrome: "Package is invalid: CRX_REQUIRED_PROOF_MISSING"
|
||||
- **Solution**: Users must enable "Developer mode" to install unsigned extensions
|
||||
- **Better solution**: Publish on Chrome Web Store
|
||||
|
||||
### Firefox: "This add-on could not be installed because it appears to be corrupt"
|
||||
- **Solution**: Check ZIP file structure - files should be at root, not in a subfolder
|
||||
- **Fix**: Re-create ZIP from inside the firefox folder
|
||||
|
||||
### Chrome: "Extension update failed"
|
||||
- **Solution**: Use the same `.pem` file for updates
|
||||
- **Lost .pem?**: You'll need to create a new extension (new ID)
|
||||
|
||||
### Firefox: "This add-on is not signed"
|
||||
- **Solution**: Sign it with Mozilla or enable unsigned extensions
|
||||
- **Enable unsigned**: `about:config` → `xpinstall.signatures.required` → false (Firefox Developer Edition only)
|
||||
|
||||
---
|
||||
|
||||
## Security Notes
|
||||
|
||||
- ⚠️ **Never commit `.pem` files to version control**
|
||||
- ⚠️ **Keep your API keys secret**
|
||||
- ⚠️ **Don't bundle unnecessary files** (documentation, source maps, etc.)
|
||||
- ✅ **Only include required files** in the package
|
||||
- ✅ **Test packaged version** before distributing
|
||||
|
||||
---
|
||||
|
||||
## Quick Commands Reference
|
||||
|
||||
```bash
|
||||
# Create distribution folder
|
||||
mkdir -p dist
|
||||
|
||||
# Package Chrome (ZIP for Web Store)
|
||||
cd chrome && zip -r ../dist/greasyboii-chrome.zip * && cd ..
|
||||
|
||||
# Package Firefox (ZIP)
|
||||
cd firefox && zip -r ../dist/greasyboii-firefox.zip * && cd ..
|
||||
|
||||
# Package Firefox (XPI)
|
||||
cd firefox && zip -r ../dist/greasyboii-firefox.xpi * && cd ..
|
||||
|
||||
# Install web-ext (Firefox tool)
|
||||
npm install -g web-ext
|
||||
|
||||
# Build with web-ext
|
||||
cd firefox && web-ext build
|
||||
|
||||
# Run Firefox with extension
|
||||
cd firefox && web-ext run
|
||||
|
||||
# Sign Firefox extension
|
||||
cd firefox && web-ext sign --api-key=KEY --api-secret=SECRET
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Test locally first** - Make sure everything works
|
||||
2. **Create packages** - Use scripts above
|
||||
3. **Test packages** - Install and verify
|
||||
4. **Decide distribution method**:
|
||||
- Self-host for personal use
|
||||
- Chrome Web Store for public Chrome users
|
||||
- Firefox Add-ons for public Firefox users
|
||||
5. **Update version numbers** for future releases
|
||||
|
||||
---
|
||||
|
||||
**Current Version:** 1.0.4
|
||||
**Ready to Package:** ✅ Yes
|
||||
**All Files Present:** ✅ Yes
|
||||
|
||||
Happy packaging! 🐒✨
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# Greasemonkey Quick Start Guide
|
||||
|
||||
Get up and running with Greasemonkey in 5 minutes!
|
||||
|
||||
## Installation
|
||||
|
||||
### Chrome
|
||||
1. Open Chrome
|
||||
2. Go to `chrome://extensions/`
|
||||
3. Enable "Developer mode" (top-right toggle)
|
||||
4. Click "Load unpacked"
|
||||
5. Select the `chrome` folder
|
||||
6. Done! 🎉
|
||||
|
||||
### Firefox
|
||||
1. Open Firefox
|
||||
2. Go to `about:debugging#/runtime/this-firefox`
|
||||
3. Click "Load Temporary Add-on"
|
||||
4. Select `firefox/manifest.json`
|
||||
5. Done! 🎉
|
||||
|
||||
## Your First Rule
|
||||
|
||||
Let's create a simple rule that changes Google's background color:
|
||||
|
||||
1. **Click the Greasemonkey icon** in your browser toolbar
|
||||
2. **Click "+ Add Rule"**
|
||||
3. **Fill in the form:**
|
||||
- Rule Name: `Google Dark Background`
|
||||
- Match Type: `URL contains string`
|
||||
- URL Pattern: `google.com`
|
||||
- CSS Code:
|
||||
```css
|
||||
body {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
```
|
||||
- Leave JavaScript blank
|
||||
- Keep Enabled checked
|
||||
4. **Click "Save Rule"**
|
||||
5. **Visit google.com** and see the dark background!
|
||||
|
||||
## Testing with Test Page
|
||||
|
||||
A test page is included: `test-page.html`
|
||||
|
||||
1. Open `test-page.html` in your browser
|
||||
2. Create a rule:
|
||||
- Rule Name: `Test Page Alert`
|
||||
- Match Type: `Page contains element`
|
||||
- Element Selector: `#test-element-1`
|
||||
- JavaScript Code:
|
||||
```javascript
|
||||
alert('Greasemonkey is working!');
|
||||
```
|
||||
3. Reload the test page
|
||||
4. You should see an alert!
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Hide Annoying Elements
|
||||
```css
|
||||
.advertisement,
|
||||
.popup-banner,
|
||||
#cookie-notice {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Change Colors
|
||||
```css
|
||||
body {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Auto-Click Buttons
|
||||
```javascript
|
||||
const button = document.querySelector('.accept-button');
|
||||
if (button) button.click();
|
||||
```
|
||||
|
||||
### Add Custom Content
|
||||
```javascript
|
||||
const banner = document.createElement('div');
|
||||
banner.textContent = 'Custom banner added by Greasemonkey!';
|
||||
banner.style.cssText = 'position:fixed;top:0;width:100%;background:red;color:white;padding:10px;z-index:9999;';
|
||||
document.body.insertBefore(banner, document.body.firstChild);
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- **Use `!important`** in CSS to override existing styles
|
||||
- **Check the console** (F12) for error messages
|
||||
- **Start simple** and test before adding complex rules
|
||||
- **Toggle rules off** if they cause problems
|
||||
- **Element selectors** can be found by right-clicking → Inspect Element
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Rule not working?**
|
||||
- ✓ Is the rule enabled? (check the toggle)
|
||||
- ✓ Does the URL pattern match? (try a shorter string)
|
||||
- ✓ Does the element exist? (test in console: `document.querySelector('selector')`)
|
||||
- ✓ Did you reload the page?
|
||||
|
||||
**CSS not applying?**
|
||||
- Add `!important` to CSS rules
|
||||
- Check for syntax errors
|
||||
|
||||
**JavaScript not running?**
|
||||
- Check browser console (F12) for errors
|
||||
- Test code in console first
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the full [README.md](README.md) for detailed documentation
|
||||
- See [BUILD.md](BUILD.md) for distribution instructions
|
||||
- Experiment with different selectors and code
|
||||
- Share your rules with others!
|
||||
|
||||
## Example: Complete Rule
|
||||
|
||||
Here's a complete rule that works on GitHub to highlight pull requests:
|
||||
|
||||
- **Rule Name:** `GitHub PR Highlight`
|
||||
- **Match Type:** `Both URL and element`
|
||||
- **URL Pattern:** `github.com`
|
||||
- **Element Selector:** `.js-issue-row`
|
||||
- **CSS Code:**
|
||||
```css
|
||||
.js-issue-row {
|
||||
border-left: 4px solid #28a745 !important;
|
||||
background-color: #f0fff4 !important;
|
||||
}
|
||||
```
|
||||
- **JavaScript Code:**
|
||||
```javascript
|
||||
console.log('GitHub PR highlighting active');
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
Need help? Check:
|
||||
- Browser console (F12) for errors
|
||||
- README.md for full documentation
|
||||
- Element inspector to find correct selectors
|
||||
|
||||
Happy scripting! 🐒✨
|
||||
@@ -0,0 +1,100 @@
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
GREASYBOII PACKAGING GUIDE
|
||||
Quick Reference
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ ALREADY PACKAGED!
|
||||
|
||||
Files ready in dist/ folder:
|
||||
• greasyboii-chrome-v1.0.4.zip (62 KB)
|
||||
• greasyboii-firefox-v1.0.4.zip (62 KB)
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 QUICK INSTALL
|
||||
|
||||
Chrome:
|
||||
1. Extract ZIP
|
||||
2. chrome://extensions/
|
||||
3. Enable "Developer mode"
|
||||
4. "Load unpacked" → select folder
|
||||
5. Done!
|
||||
|
||||
Firefox:
|
||||
1. Rename .zip to .xpi
|
||||
2. Drag .xpi into Firefox
|
||||
3. Click "Add"
|
||||
4. Done!
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
📦 RE-PACKAGE (if needed)
|
||||
|
||||
Run the script:
|
||||
./package-all.sh
|
||||
|
||||
Or manually:
|
||||
cd chrome && zip -r ../dist/greasyboii-chrome.zip *
|
||||
cd firefox && zip -r ../dist/greasyboii-firefox.zip *
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎁 CREATE .CRX FOR CHROME (Shareable)
|
||||
|
||||
1. chrome://extensions/
|
||||
2. Developer mode ON
|
||||
3. "Pack extension"
|
||||
4. Select chrome/ folder
|
||||
5. Leave private key empty
|
||||
6. Get chrome.crx file
|
||||
7. Share this file!
|
||||
|
||||
Users install: Drag chrome.crx to chrome://extensions/
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
🦊 CREATE .XPI FOR FIREFOX (Shareable)
|
||||
|
||||
Simply rename:
|
||||
mv dist/greasyboii-firefox-v1.0.4.zip dist/greasyboii-firefox-v1.0.4.xpi
|
||||
|
||||
Share the .xpi file!
|
||||
|
||||
Users install: Drag .xpi into Firefox
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
📚 DOCUMENTATION
|
||||
|
||||
• PACKAGING.md - Full packaging guide
|
||||
• INSTALL_PACKAGED.md - Installation instructions
|
||||
• BUILD.md - Build instructions
|
||||
• README.md - Complete documentation
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
🏪 PUBLISH TO STORES
|
||||
|
||||
Chrome Web Store:
|
||||
• $5 one-time fee
|
||||
• https://chrome.google.com/webstore/developer/dashboard
|
||||
• Upload ZIP file
|
||||
• Review takes 1-3 days
|
||||
|
||||
Firefox Add-ons:
|
||||
• Free
|
||||
• https://addons.mozilla.org/developers/
|
||||
• Upload ZIP file
|
||||
• Choose Listed or Unlisted
|
||||
• Instant signing for unlisted
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ YOUR PACKAGES ARE READY!
|
||||
|
||||
Files: dist/greasyboii-chrome-v1.0.4.zip
|
||||
dist/greasyboii-firefox-v1.0.4.zip
|
||||
|
||||
Ready to: Install, Share, or Publish!
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════
|
||||
@@ -0,0 +1,314 @@
|
||||
# Greasemonkey Browser Extension
|
||||
|
||||
**Author:** Andy Dixon
|
||||
**Version:** 1.0.0
|
||||
|
||||
A powerful browser extension that allows you to execute custom JavaScript and CSS on web pages based on flexible matching conditions.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎯 **Flexible Matching**: Execute scripts when:
|
||||
- A URL contains a specific string
|
||||
- A page contains a specific element (CSS selector)
|
||||
- Both conditions are met simultaneously
|
||||
|
||||
- 🎨 **Bootstrap Dark UI**: Intuitive configuration interface with Bootstrap 5 dark theme
|
||||
- 💾 **Persistent Storage**: All rules are saved and synced across your browser sessions
|
||||
- ⚡ **Multiple Rules**: Add, edit, and manage unlimited custom rules
|
||||
- 🔄 **Toggle Controls**: Easily enable/disable individual rules
|
||||
- 📝 **JavaScript & CSS**: Support for both JavaScript execution and CSS injection
|
||||
- 🔍 **Visual Indicators**: Clear badges showing rule type (URL/Element/Both) and code types (JS/CSS)
|
||||
|
||||
## Installation
|
||||
|
||||
### Chrome Installation
|
||||
|
||||
1. Navigate to the `chrome` directory
|
||||
2. Open Chrome and go to `chrome://extensions/`
|
||||
3. Enable "Developer mode" (toggle in top-right corner)
|
||||
4. Click "Load unpacked"
|
||||
5. Select the `chrome` directory
|
||||
6. The Greasemonkey extension is now installed!
|
||||
|
||||
### Firefox Installation
|
||||
|
||||
1. Navigate to the `firefox` directory
|
||||
2. Open Firefox and go to `about:debugging#/runtime/this-firefox`
|
||||
3. Click "Load Temporary Add-on"
|
||||
4. Select the `manifest.json` file from the `firefox` directory
|
||||
5. The Greasemonkey extension is now installed!
|
||||
|
||||
**Note:** For permanent Firefox installation, you'll need to sign the extension through [addons.mozilla.org](https://addons.mozilla.org).
|
||||
|
||||
## Usage
|
||||
|
||||
### Creating a Rule
|
||||
|
||||
1. Click the Greasemonkey icon in your browser toolbar
|
||||
2. Click the "+ Add Rule" button
|
||||
3. Fill in the rule details:
|
||||
- **Rule Name**: A descriptive name for your rule
|
||||
- **Match Type**: Choose how the rule should be triggered
|
||||
- *URL contains string*: Matches if URL contains the pattern
|
||||
- *Page contains element*: Matches if element selector exists
|
||||
- *Both URL and element*: Requires both conditions
|
||||
- **URL Pattern**: The string to match in the URL (e.g., `github.com`, `/search?`)
|
||||
- **Element Selector**: CSS selector to match (e.g., `#my-element`, `.my-class`, `div[data-id="123"]`)
|
||||
- **JavaScript Code**: Custom JavaScript to execute
|
||||
- **CSS Code**: Custom CSS to inject
|
||||
- **Enabled**: Toggle to enable/disable the rule
|
||||
4. Click "Save Rule"
|
||||
|
||||
### Editing a Rule
|
||||
|
||||
1. Open the Greasemonkey popup
|
||||
2. Click the edit button (pencil icon) on any rule card
|
||||
3. Modify the rule details
|
||||
4. Click "Save Rule"
|
||||
|
||||
### Deleting a Rule
|
||||
|
||||
1. Open the Greasemonkey popup
|
||||
2. Click the delete button (trash icon) on any rule card
|
||||
3. Confirm the deletion
|
||||
|
||||
### Toggling a Rule
|
||||
|
||||
Use the toggle switch on each rule card to quickly enable or disable rules without editing them.
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Hide Ads on Specific Sites
|
||||
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `example.com`
|
||||
**CSS Code:**
|
||||
```css
|
||||
.advertisement,
|
||||
.ad-banner,
|
||||
#sidebar-ads {
|
||||
display: none !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Auto-fill Forms
|
||||
|
||||
**Match Type:** Both URL and element
|
||||
**URL Pattern:** `form-site.com`
|
||||
**Element Selector:** `#login-form`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
document.getElementById('username').value = 'myusername';
|
||||
console.log('Auto-filled username field');
|
||||
```
|
||||
|
||||
### Example 3: Dark Mode Override
|
||||
|
||||
**Match Type:** URL contains string
|
||||
**URL Pattern:** `news-site.com`
|
||||
**CSS Code:**
|
||||
```css
|
||||
body {
|
||||
background-color: #1a1a1a !important;
|
||||
color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4a9eff !important;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Add Custom Features
|
||||
|
||||
**Match Type:** Element exists
|
||||
**Element Selector:** `.video-player`
|
||||
**JavaScript Code:**
|
||||
```javascript
|
||||
console.log('Video player detected!');
|
||||
|
||||
// Add a custom button
|
||||
const button = document.createElement('button');
|
||||
button.textContent = 'Custom Action';
|
||||
button.style.cssText = 'position:fixed;top:10px;right:10px;z-index:9999;';
|
||||
button.onclick = () => alert('Custom button clicked!');
|
||||
document.body.appendChild(button);
|
||||
```
|
||||
|
||||
### Example 5: Conditional Styling
|
||||
|
||||
**Match Type:** Both URL and element
|
||||
**URL Pattern:** `dashboard`
|
||||
**Element Selector:** `.data-table`
|
||||
**CSS Code:**
|
||||
```css
|
||||
.data-table {
|
||||
border: 2px solid #4CAF50;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
greasemonkey/
|
||||
├── chrome/ # Chrome extension files
|
||||
│ ├── manifest.json # Chrome extension manifest (v3)
|
||||
│ ├── background.js # Background service worker
|
||||
│ ├── content.js # Content script (runs on pages)
|
||||
│ ├── popup.html # Extension popup UI
|
||||
│ ├── popup.css # Popup styling
|
||||
│ ├── popup.js # Popup functionality
|
||||
│ └── icon*.png # Extension icons
|
||||
├── firefox/ # Firefox extension files
|
||||
│ ├── manifest.json # Firefox extension manifest (v2)
|
||||
│ ├── background.js # Background script
|
||||
│ ├── content.js # Content script (runs on pages)
|
||||
│ ├── popup.html # Extension popup UI
|
||||
│ ├── popup.css # Popup styling
|
||||
│ ├── popup.js # Popup functionality
|
||||
│ └── icon*.png # Extension icons
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Content Script**: Runs on every page load (`document_idle`)
|
||||
2. **Rule Matching**: Checks all enabled rules against current page
|
||||
3. **Condition Evaluation**: Tests URL patterns and element selectors
|
||||
4. **Code Execution**:
|
||||
- JavaScript is injected as a script element
|
||||
- CSS is injected as a style element
|
||||
5. **Storage**: Rules are stored in browser's sync storage
|
||||
|
||||
### Storage Format
|
||||
|
||||
Rules are stored as JSON in browser sync storage:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"name": "Example Rule",
|
||||
"matchType": "url",
|
||||
"urlPattern": "example.com",
|
||||
"elementSelector": "",
|
||||
"javascript": "console.log('Hello');",
|
||||
"css": "body { background: red; }",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Browser Compatibility
|
||||
|
||||
- **Chrome**: Requires Chrome 88+ (Manifest V3)
|
||||
- **Firefox**: Requires Firefox 48+ (Manifest V2)
|
||||
- **Edge**: Should work (Chromium-based, same as Chrome)
|
||||
- **Opera**: Should work (Chromium-based, same as Chrome)
|
||||
|
||||
## Building for Distribution
|
||||
|
||||
### Chrome
|
||||
|
||||
1. Ensure all files are in the `chrome` directory
|
||||
2. Go to `chrome://extensions/`
|
||||
3. Click "Pack extension"
|
||||
4. Select the `chrome` directory
|
||||
5. This creates a `.crx` file for distribution
|
||||
|
||||
### Firefox
|
||||
|
||||
1. Install `web-ext` tool: `npm install -g web-ext`
|
||||
2. Navigate to the `firefox` directory
|
||||
3. Run: `web-ext build`
|
||||
4. This creates a `.zip` file in `web-ext-artifacts/`
|
||||
5. Submit to [addons.mozilla.org](https://addons.mozilla.org) for signing
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **User Responsibility**: Be cautious with JavaScript code execution
|
||||
- **HTTPS**: Script injection works on both HTTP and HTTPS sites
|
||||
- **CSP**: Some sites with strict Content Security Policy may block injected scripts
|
||||
- **Permissions**: Extension requires broad permissions (`<all_urls>`) to work on all sites
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Rules Not Executing
|
||||
|
||||
1. **Check the browser console** for error messages (F12)
|
||||
2. **Verify the rule is enabled** (toggle switch)
|
||||
3. **Test the URL pattern** - ensure it matches the current page
|
||||
4. **Test the element selector** - open console and run `document.querySelector('your-selector')`
|
||||
5. **Reload the page** after creating/editing rules
|
||||
|
||||
### CSS Not Applying
|
||||
|
||||
- Use `!important` flag to override existing styles
|
||||
- Check for syntax errors in CSS
|
||||
- Verify the rule conditions are met
|
||||
|
||||
### JavaScript Not Running
|
||||
|
||||
- Check browser console for errors
|
||||
- Ensure JavaScript syntax is correct
|
||||
- Some sites may have strict Content Security Policies
|
||||
|
||||
### Storage Not Syncing
|
||||
|
||||
- Ensure browser sync is enabled
|
||||
- Check available storage quota
|
||||
- For Firefox temporary add-ons, sync storage may not persist
|
||||
|
||||
## Development
|
||||
|
||||
### Local Development
|
||||
|
||||
1. Make changes to files in `chrome/` or `firefox/` directories
|
||||
2. Reload the extension:
|
||||
- **Chrome**: Go to `chrome://extensions/` and click reload icon
|
||||
- **Firefox**: Go to `about:debugging` and click reload
|
||||
3. Test your changes
|
||||
|
||||
### Debugging
|
||||
|
||||
- **Background Script**: Check browser console in extension management page
|
||||
- **Content Script**: Check page console (F12)
|
||||
- **Popup**: Right-click popup and select "Inspect"
|
||||
|
||||
## Privacy
|
||||
|
||||
- No data is sent to external servers
|
||||
- All rules are stored locally in browser storage
|
||||
- No analytics or tracking
|
||||
- No network requests made by the extension
|
||||
|
||||
## License
|
||||
|
||||
This extension is provided as-is for personal and educational use.
|
||||
|
||||
## Support
|
||||
|
||||
For issues, questions, or feature requests, please contact Andy Dixon.
|
||||
|
||||
## Version History
|
||||
|
||||
### 1.0.0 (Initial Release)
|
||||
- URL pattern matching
|
||||
- Element selector matching
|
||||
- Combined URL + Element matching
|
||||
- JavaScript execution
|
||||
- CSS injection
|
||||
- Bootstrap dark theme UI
|
||||
- Add/Edit/Delete rules
|
||||
- Enable/Disable toggle
|
||||
- Persistent storage
|
||||
- Chrome and Firefox support
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
# 🐒 Greasemonkey - START HERE
|
||||
|
||||
**Welcome to Greasemonkey!** This browser extension lets you execute custom JavaScript and CSS on any webpage.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start (Choose Your Path)
|
||||
|
||||
### Path 1: I Want To Use It NOW (5 minutes)
|
||||
→ **Read [QUICKSTART.md](QUICKSTART.md)** - Get up and running immediately
|
||||
|
||||
### Path 2: I Want Example Rules
|
||||
→ **Read [EXAMPLES.md](EXAMPLES.md)** - 20+ ready-to-use rules to copy/paste
|
||||
|
||||
### Path 3: I Want Complete Documentation
|
||||
→ **Read [README.md](README.md)** - Full feature documentation and usage guide
|
||||
|
||||
### Path 4: I Want To Test It First
|
||||
→ **Open [test-page.html](test-page.html)** in your browser - Interactive test environment
|
||||
|
||||
### Path 5: I Want To Distribute It
|
||||
→ **Read [BUILD.md](BUILD.md)** - Building and distribution instructions
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation (Pick Your Browser)
|
||||
|
||||
### Google Chrome / Microsoft Edge
|
||||
```
|
||||
1. Open chrome://extensions/
|
||||
2. Enable "Developer mode" (top-right toggle)
|
||||
3. Click "Load unpacked"
|
||||
4. Select the chrome/ folder
|
||||
```
|
||||
|
||||
### Mozilla Firefox
|
||||
```
|
||||
1. Open about:debugging#/runtime/this-firefox
|
||||
2. Click "Load Temporary Add-on"
|
||||
3. Select firefox/manifest.json
|
||||
```
|
||||
|
||||
**That's it!** The Greasemonkey icon appears in your toolbar.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Map
|
||||
|
||||
| File | What It Contains | When To Read |
|
||||
|------|------------------|--------------|
|
||||
| **QUICKSTART.md** | 5-minute tutorial | First time setup |
|
||||
| **EXAMPLES.md** | 20+ example rules | When you need ideas |
|
||||
| **README.md** | Complete documentation | Learning all features |
|
||||
| **BUILD.md** | Build instructions | Distributing the extension |
|
||||
| **test-page.html** | Test environment | Testing your rules |
|
||||
| **PROJECT_SUMMARY.md** | Project overview | Understanding the codebase |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What Can I Do With This?
|
||||
|
||||
### Popular Use Cases
|
||||
- 🌙 **Add Dark Mode** to any website
|
||||
- 🚫 **Block Ads** and unwanted content
|
||||
- ⚡ **Automate Tasks** like form filling
|
||||
- 🎨 **Customize Design** of your favorite sites
|
||||
- 🔧 **Add Features** that websites are missing
|
||||
- 🔐 **Enhance Privacy** by removing trackers
|
||||
- ♿ **Improve Accessibility** with custom styles
|
||||
- 📝 **Inject Tools** like notepads or calculators
|
||||
|
||||
### Example: Make Any Site Dark
|
||||
1. Click Greasemonkey icon
|
||||
2. Add rule: URL contains `example.com`
|
||||
3. Add CSS: `body { background: #1a1a1a !important; color: #e0e0e0 !important; }`
|
||||
4. Visit example.com → Dark theme!
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Learning Path
|
||||
|
||||
**Total Time: ~30 minutes**
|
||||
|
||||
1. **Install** (5 min) → Follow installation steps above
|
||||
2. **Quick Start** (5 min) → Read QUICKSTART.md
|
||||
3. **First Rule** (5 min) → Create your first rule
|
||||
4. **Test** (5 min) → Open test-page.html and experiment
|
||||
5. **Examples** (10 min) → Browse EXAMPLES.md for ideas
|
||||
6. **Customize** (∞) → Start customizing your favorite sites!
|
||||
|
||||
---
|
||||
|
||||
## 📋 Features Checklist
|
||||
|
||||
✅ URL pattern matching
|
||||
✅ CSS selector matching
|
||||
✅ Combined URL + element matching
|
||||
✅ JavaScript execution
|
||||
✅ CSS injection
|
||||
✅ Bootstrap dark theme UI
|
||||
✅ Add/edit/delete rules
|
||||
✅ Enable/disable toggles
|
||||
✅ Persistent storage
|
||||
✅ Chrome support (Manifest V3)
|
||||
✅ Firefox support (Manifest V2)
|
||||
✅ Complete documentation
|
||||
✅ Example library
|
||||
✅ Test page
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technical Details
|
||||
|
||||
**Architecture:**
|
||||
- Content scripts inject at page load
|
||||
- Bootstrap 5 dark theme UI
|
||||
- Sync storage for persistence
|
||||
- No external dependencies
|
||||
|
||||
**Browser Support:**
|
||||
- Chrome 88+ (Manifest V3)
|
||||
- Firefox 48+ (Manifest V2)
|
||||
- Edge, Opera (Chromium-based)
|
||||
|
||||
**File Count:**
|
||||
- 9 files per browser
|
||||
- 7 documentation files
|
||||
- 1 test page
|
||||
- Total: 26 files
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Common Questions
|
||||
|
||||
**Q: Is it safe?**
|
||||
A: Yes! No external servers, no tracking. All code runs locally. Be cautious with JavaScript you execute.
|
||||
|
||||
**Q: Will it slow down my browser?**
|
||||
A: No. Scripts only run when conditions match, and inject efficiently at page load.
|
||||
|
||||
**Q: Can I share my rules?**
|
||||
A: Yes! Copy the rule configuration and share. Rules are just text in your browser storage.
|
||||
|
||||
**Q: Do I need programming knowledge?**
|
||||
A: Basic knowledge helps, but you can start with examples from EXAMPLES.md and modify them.
|
||||
|
||||
**Q: Can I break websites?**
|
||||
A: Unlikely to cause permanent issues. Just disable the rule if something goes wrong. Refresh to reset.
|
||||
|
||||
**Q: Does it work on all websites?**
|
||||
A: Yes, except some with strict Content Security Policy. Works on 99% of sites.
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
**Rule not working?**
|
||||
1. Check rule is enabled (toggle switch)
|
||||
2. Verify URL pattern matches current page
|
||||
3. Test element selector in console: `document.querySelector('selector')`
|
||||
4. Check browser console (F12) for errors
|
||||
5. Reload the page after creating/editing rules
|
||||
|
||||
**More help:** See troubleshooting section in [README.md](README.md)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
- **Questions?** Check README.md troubleshooting section
|
||||
- **Bugs?** Check browser console for errors
|
||||
- **Ideas?** Browse EXAMPLES.md for inspiration
|
||||
- **Contact:** Andy Dixon (extension author)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Ready To Start!
|
||||
|
||||
**Recommended first steps:**
|
||||
|
||||
1. ✅ Install extension in your browser (see above)
|
||||
2. ✅ Read [QUICKSTART.md](QUICKSTART.md) (5 minutes)
|
||||
3. ✅ Open [test-page.html](test-page.html) in browser
|
||||
4. ✅ Create your first rule
|
||||
5. ✅ Browse [EXAMPLES.md](EXAMPLES.md) for more ideas
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Reference
|
||||
|
||||
```
|
||||
greasemonkey/
|
||||
├── START_HERE.md ← You are here!
|
||||
├── QUICKSTART.md ← Start here for installation
|
||||
├── EXAMPLES.md ← 20+ ready-to-use rules
|
||||
├── README.md ← Complete documentation
|
||||
├── BUILD.md ← Distribution guide
|
||||
├── test-page.html ← Test environment
|
||||
├── chrome/ ← Chrome extension files
|
||||
└── firefox/ ← Firefox extension files
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Author:** Andy Dixon
|
||||
**Version:** 1.0.0
|
||||
**License:** Personal and educational use
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Let's Go!
|
||||
|
||||
Pick your path above and start customizing the web! 🐒✨
|
||||
@@ -0,0 +1,231 @@
|
||||
# Greasemonkey Troubleshooting Guide
|
||||
|
||||
## "Add Rule" Button Not Working
|
||||
|
||||
### Quick Fix
|
||||
1. **Reload the extension**:
|
||||
- Chrome: Go to `chrome://extensions/` and click the reload icon on Greasemonkey
|
||||
- Firefox: Go to `about:debugging` and click reload
|
||||
2. **Close and reopen the popup** by clicking the extension icon again
|
||||
3. **Check the browser console**:
|
||||
- Right-click on the popup window
|
||||
- Select "Inspect" or "Inspect Element"
|
||||
- Look at the Console tab for any error messages
|
||||
|
||||
### Common Causes
|
||||
|
||||
#### 1. Bootstrap Not Loaded
|
||||
**Symptom:** Modal doesn't open, or console shows "bootstrap is not defined"
|
||||
**Solution:**
|
||||
- Ensure you have an internet connection (Bootstrap loads from CDN)
|
||||
- If offline, the extension won't work properly without Bootstrap
|
||||
- Check browser console for network errors
|
||||
|
||||
#### 2. Content Security Policy
|
||||
**Symptom:** Console shows CSP violations
|
||||
**Solution:** Extension should work in popup, but if issues persist:
|
||||
- Try reloading the extension completely
|
||||
- Close all popup windows and reopen
|
||||
|
||||
#### 3. Extension Not Properly Loaded
|
||||
**Symptom:** Nothing happens when clicking buttons
|
||||
**Solution:**
|
||||
- Chrome:
|
||||
1. Go to `chrome://extensions/`
|
||||
2. Find Greasemonkey
|
||||
3. Click the reload icon (circular arrow)
|
||||
4. Try again
|
||||
- Firefox:
|
||||
1. Go to `about:debugging#/runtime/this-firefox`
|
||||
2. Find Greasemonkey
|
||||
3. Click "Reload"
|
||||
4. Try again
|
||||
|
||||
### Debug Steps
|
||||
|
||||
1. **Open the popup inspector**:
|
||||
- Right-click anywhere in the popup window
|
||||
- Select "Inspect" or "Inspect Element"
|
||||
- This opens DevTools for the popup
|
||||
|
||||
2. **Check the Console tab** for errors like:
|
||||
- "bootstrap is not defined" → Bootstrap didn't load
|
||||
- "Cannot read property 'show' of undefined" → Modal not initialized
|
||||
- "Element not found" → HTML structure issue
|
||||
|
||||
3. **Try in Console**:
|
||||
```javascript
|
||||
// Check if Bootstrap loaded
|
||||
console.log(typeof bootstrap); // Should show "object"
|
||||
|
||||
// Check if elements exist
|
||||
console.log(document.getElementById('addRuleBtn')); // Should show button element
|
||||
console.log(document.getElementById('ruleModal')); // Should show modal element
|
||||
```
|
||||
|
||||
4. **Manual test**:
|
||||
```javascript
|
||||
// Try to open modal manually
|
||||
const modal = new bootstrap.Modal(document.getElementById('ruleModal'));
|
||||
modal.show();
|
||||
```
|
||||
|
||||
### Testing After Fix
|
||||
|
||||
1. Reload the extension
|
||||
2. Click the Greasemonkey icon
|
||||
3. Click "+ Add Rule"
|
||||
4. Modal should open with the rule form
|
||||
5. If successful, you should see the modal with:
|
||||
- Rule Name field
|
||||
- Match Type dropdown
|
||||
- URL Pattern field (visible by default)
|
||||
- JavaScript code area
|
||||
- CSS code area
|
||||
- Enabled checkbox
|
||||
- Cancel and Save buttons
|
||||
|
||||
## Rules Not Executing
|
||||
|
||||
### Check List
|
||||
1. ✓ Is the rule enabled? (toggle switch should be on)
|
||||
2. ✓ Does the URL pattern match the current page URL?
|
||||
3. ✓ If using element selector, does the element exist on the page?
|
||||
4. ✓ Did you reload the page after creating/editing the rule?
|
||||
|
||||
### Debug Process
|
||||
1. Open the page where the rule should run
|
||||
2. Open browser console (F12)
|
||||
3. Look for messages like: `[Greasemonkey] Executing rule: YourRuleName`
|
||||
4. Check for error messages
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Rule Never Executes
|
||||
- **URL pattern too specific**: Try a shorter, more general pattern
|
||||
- ❌ `https://www.example.com/page.html?id=123`
|
||||
- ✅ `example.com`
|
||||
- **Element doesn't exist yet**: Page might load the element dynamically
|
||||
- **Rule is disabled**: Check the toggle switch
|
||||
|
||||
#### JavaScript Not Running
|
||||
- Check console for syntax errors
|
||||
- Test your code in the page console first:
|
||||
```javascript
|
||||
// Test in console before adding to rule
|
||||
console.log('Test');
|
||||
```
|
||||
|
||||
#### CSS Not Applying
|
||||
- Use `!important` to override existing styles:
|
||||
```css
|
||||
body {
|
||||
background: #000 !important;
|
||||
}
|
||||
```
|
||||
- Check CSS syntax is valid
|
||||
- Some sites have very specific CSS that overrides yours
|
||||
|
||||
## Storage Issues
|
||||
|
||||
### Rules Not Persisting
|
||||
**Chrome:**
|
||||
- Ensure sync is enabled: `chrome://settings/syncSetup`
|
||||
- Storage might be full (rare)
|
||||
|
||||
**Firefox:**
|
||||
- Temporary add-ons don't persist after restart
|
||||
- Use signed version for persistence
|
||||
|
||||
### Can't Edit/Delete Rules
|
||||
1. Check browser console for errors
|
||||
2. Try exporting rules (copy from console):
|
||||
```javascript
|
||||
chrome.storage.sync.get(['rules'], (result) => {
|
||||
console.log(JSON.stringify(result.rules, null, 2));
|
||||
});
|
||||
```
|
||||
3. Reload extension
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### Popup Slow to Open
|
||||
- Too many rules? Try disabling some
|
||||
- Check if Bootstrap CDN is slow (network issue)
|
||||
|
||||
### Page Loads Slowly
|
||||
- Complex JavaScript in rules can slow down pages
|
||||
- Try simplifying your rules
|
||||
- Use CSS instead of JavaScript when possible
|
||||
|
||||
## Browser-Specific Issues
|
||||
|
||||
### Chrome/Edge
|
||||
- **Service worker inactive**: Reload extension
|
||||
- **CSP errors**: Some sites block injected scripts (rare)
|
||||
|
||||
### Firefox
|
||||
- **Temporary add-on removed**: Expected after restart, reload it
|
||||
- **Storage not syncing**: Sign the extension for production use
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. **Check console** (F12) for error messages
|
||||
2. **Test with simple rule** first:
|
||||
- Name: Test
|
||||
- Match Type: URL
|
||||
- URL Pattern: (current page URL)
|
||||
- JavaScript: `alert('It works!');`
|
||||
3. **Reload extension** after any changes
|
||||
4. **Try test-page.html** to verify extension works
|
||||
|
||||
## Manual Reset
|
||||
|
||||
If all else fails, reset the extension:
|
||||
|
||||
### Chrome
|
||||
```javascript
|
||||
// In popup inspector console
|
||||
chrome.storage.sync.clear(() => {
|
||||
console.log('Storage cleared');
|
||||
location.reload();
|
||||
});
|
||||
```
|
||||
|
||||
### Firefox
|
||||
```javascript
|
||||
// In popup inspector console
|
||||
browser.storage.sync.clear().then(() => {
|
||||
console.log('Storage cleared');
|
||||
location.reload();
|
||||
});
|
||||
```
|
||||
|
||||
## Still Not Working?
|
||||
|
||||
1. Check if you have the latest version
|
||||
2. Try in a different browser
|
||||
3. Verify extension permissions are granted
|
||||
4. Look for browser console errors
|
||||
5. Try with a simple test rule on test-page.html
|
||||
|
||||
## Version Check
|
||||
|
||||
Current version: 1.0.0
|
||||
|
||||
Make sure you have all these files:
|
||||
- manifest.json
|
||||
- background.js
|
||||
- content.js
|
||||
- popup.html
|
||||
- popup.css
|
||||
- popup.js
|
||||
- icon files (16, 48, 128)
|
||||
|
||||
## Contact
|
||||
|
||||
If issues persist after following this guide, contact Andy Dixon with:
|
||||
- Browser version
|
||||
- Error messages from console
|
||||
- Steps to reproduce
|
||||
- Screenshot if possible
|
||||
@@ -0,0 +1,51 @@
|
||||
// Background service worker for Chrome extension
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
console.log('GreasyBoii extension installed');
|
||||
});
|
||||
|
||||
// Listen for messages from content script
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'getRules') {
|
||||
chrome.storage.sync.get(['rules'], (result) => {
|
||||
sendResponse({ rules: result.rules || [] });
|
||||
});
|
||||
return true; // Keep channel open for async response
|
||||
}
|
||||
|
||||
// Execute JavaScript via scripting API to bypass CSP
|
||||
if (request.action === 'executeScript') {
|
||||
if (!sender.tab || !sender.tab.id) {
|
||||
sendResponse({ success: false, error: 'No tab ID available' });
|
||||
return;
|
||||
}
|
||||
|
||||
const tabId = sender.tab.id;
|
||||
const code = request.code;
|
||||
const ruleName = request.ruleName;
|
||||
|
||||
console.log(`[GreasyBoii Background] Executing script for rule "${ruleName}" on tab ${tabId}`);
|
||||
|
||||
// Use chrome.scripting.executeScript which bypasses CSP
|
||||
chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
world: 'MAIN', // Execute in main world (page context), not isolated world
|
||||
func: (jsCode) => {
|
||||
try {
|
||||
// Use indirect eval to execute in global scope
|
||||
(1, eval)(jsCode);
|
||||
} catch (e) {
|
||||
console.error('[GreasyBoii] Script execution error:', e);
|
||||
}
|
||||
},
|
||||
args: [code]
|
||||
}).then(() => {
|
||||
console.log(`[GreasyBoii Background] ✓ Script executed successfully`);
|
||||
sendResponse({ success: true });
|
||||
}).catch((error) => {
|
||||
console.error(`[GreasyBoii Background] ✗ Script execution failed:`, error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
|
||||
return true; // Keep channel open for async response
|
||||
}
|
||||
});
|
||||
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
@@ -0,0 +1,93 @@
|
||||
// Content script that runs on all pages
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.log('[GreasyBoii] Content script loaded on:', window.location.href);
|
||||
|
||||
// Wait for DOM to be ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initGreasyBoii);
|
||||
} else {
|
||||
initGreasyBoii();
|
||||
}
|
||||
|
||||
function initGreasyBoii() {
|
||||
console.log('[GreasyBoii] Initializing...');
|
||||
|
||||
// Get rules from storage and execute matching ones
|
||||
chrome.storage.sync.get(['rules'], (result) => {
|
||||
const rules = result.rules || [];
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
console.log('[GreasyBoii] Found', rules.length, 'rule(s) in storage');
|
||||
|
||||
rules.forEach((rule) => {
|
||||
if (!rule.enabled) {
|
||||
console.log('[GreasyBoii] Skipping disabled rule:', rule.name);
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldExecute = false;
|
||||
|
||||
// Check conditions
|
||||
if (rule.matchType === 'url') {
|
||||
shouldExecute = currentUrl.includes(rule.urlPattern);
|
||||
console.log(`[GreasyBoii] URL match test for "${rule.name}": ${currentUrl} includes "${rule.urlPattern}" = ${shouldExecute}`);
|
||||
} else if (rule.matchType === 'element') {
|
||||
shouldExecute = document.querySelector(rule.elementSelector) !== null;
|
||||
console.log(`[GreasyBoii] Element match test for "${rule.name}": selector "${rule.elementSelector}" found = ${shouldExecute}`);
|
||||
} else if (rule.matchType === 'both') {
|
||||
const urlMatches = currentUrl.includes(rule.urlPattern);
|
||||
const elementExists = document.querySelector(rule.elementSelector) !== null;
|
||||
shouldExecute = urlMatches && elementExists;
|
||||
console.log(`[GreasyBoii] Combined match test for "${rule.name}": URL=${urlMatches}, Element=${elementExists}, Result=${shouldExecute}`);
|
||||
}
|
||||
|
||||
if (shouldExecute) {
|
||||
console.log(`[GreasyBoii] ✓ Executing rule: ${rule.name}`);
|
||||
executeRule(rule);
|
||||
} else {
|
||||
console.log(`[GreasyBoii] ✗ Rule "${rule.name}" conditions not met`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function executeRule(rule) {
|
||||
// Execute JavaScript using chrome.scripting API to bypass CSP
|
||||
if (rule.javascript && rule.javascript.trim()) {
|
||||
console.log(`[GreasyBoii] Sending JavaScript execution request for rule: ${rule.name}`);
|
||||
|
||||
// Send message to background script to execute JavaScript
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'executeScript',
|
||||
code: rule.javascript,
|
||||
ruleName: rule.name
|
||||
}, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(`[GreasyBoii] Error communicating with background:`, chrome.runtime.lastError);
|
||||
} else if (response && response.success) {
|
||||
console.log(`[GreasyBoii] ✓ JavaScript executed successfully via background script`);
|
||||
} else {
|
||||
console.error(`[GreasyBoii] JavaScript execution failed:`, response?.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Execute CSS - CSS injection doesn't have CSP issues
|
||||
if (rule.css && rule.css.trim()) {
|
||||
try {
|
||||
console.log(`[GreasyBoii] Injecting CSS for rule: ${rule.name}`);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = rule.css;
|
||||
style.setAttribute('data-greasyboii-rule', rule.name);
|
||||
(document.head || document.documentElement).appendChild(style);
|
||||
|
||||
console.log(`[GreasyBoii] ✓ CSS injected successfully`);
|
||||
} catch (error) {
|
||||
console.error(`[GreasyBoii] Error applying CSS for rule "${rule.name}":`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 499 B |
Binary file not shown.
|
After Width: | Height: | Size: 115 B |
Binary file not shown.
|
After Width: | Height: | Size: 202 B |
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "GreasyBoii",
|
||||
"version": "1.0.4",
|
||||
"description": "Execute custom JavaScript and CSS based on URL patterns and page elements",
|
||||
"author": "Andy Dixon",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab",
|
||||
"scripting"
|
||||
],
|
||||
"host_permissions": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": false
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
body {
|
||||
width: 600px;
|
||||
min-height: 400px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.rules-list {
|
||||
max-height: 450px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.rule-card {
|
||||
border: 1px solid #495057;
|
||||
border-radius: 0.375rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background-color: #212529;
|
||||
}
|
||||
|
||||
.rule-card:hover {
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.rule-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.rule-details {
|
||||
font-size: 0.875rem;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.rule-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.font-monospace {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.form-switch .form-check-input {
|
||||
cursor: pointer;
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-lg {
|
||||
max-width: 700px;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GreasyBoii</title>
|
||||
<link rel="stylesheet" href="bootstrap.min.css">
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid p-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0">GreasyBoii</h4>
|
||||
<button class="btn btn-primary btn-sm" id="addRuleBtn">+ Add Rule</button>
|
||||
</div>
|
||||
|
||||
<div id="rulesList" class="rules-list mb-3">
|
||||
<!-- Rules will be dynamically inserted here -->
|
||||
</div>
|
||||
|
||||
<div id="emptyState" class="text-center text-muted py-4" style="display: none;">
|
||||
<p>No rules yet. Click "Add Rule" to get started.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Rule Modal -->
|
||||
<div class="modal fade" id="ruleModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Add Rule</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="ruleForm">
|
||||
<div class="mb-3">
|
||||
<label for="ruleName" class="form-label">Rule Name</label>
|
||||
<input type="text" class="form-control" id="ruleName" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="matchType" class="form-label">Match Type</label>
|
||||
<select class="form-select" id="matchType" required>
|
||||
<option value="url">URL contains string</option>
|
||||
<option value="element">Page contains element</option>
|
||||
<option value="both">Both URL and element</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="urlPatternGroup">
|
||||
<label for="urlPattern" class="form-label">URL Pattern</label>
|
||||
<input type="text" class="form-control" id="urlPattern" placeholder="e.g., example.com">
|
||||
<small class="form-text text-muted">Enter a string to match in the URL</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="elementSelectorGroup" style="display: none;">
|
||||
<label for="elementSelector" class="form-label">Element Selector</label>
|
||||
<input type="text" class="form-control" id="elementSelector" placeholder="e.g., #my-element or .my-class">
|
||||
<small class="form-text text-muted">Enter a CSS selector</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="javascript" class="form-label">JavaScript Code</label>
|
||||
<textarea class="form-control font-monospace" id="javascript" rows="6" placeholder="console.log('Hello from GreasyBoii!');"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="css" class="form-label">CSS Code</label>
|
||||
<textarea class="form-control font-monospace" id="css" rows="6" placeholder="body { background-color: #000; }"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="ruleEnabled" checked>
|
||||
<label class="form-check-label" for="ruleEnabled">
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveRuleBtn">Save Rule</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="bootstrap.bundle.min.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
// Popup script for managing rules
|
||||
let currentRules = [];
|
||||
let editingRuleIndex = -1;
|
||||
let ruleModal;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Wait for Bootstrap to be fully loaded
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
initializeExtension();
|
||||
} else {
|
||||
// Fallback: wait a bit for Bootstrap to load
|
||||
setTimeout(initializeExtension, 100);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeExtension() {
|
||||
try {
|
||||
ruleModal = new bootstrap.Modal(document.getElementById('ruleModal'));
|
||||
} catch (e) {
|
||||
console.error('Error initializing modal:', e);
|
||||
}
|
||||
|
||||
loadRules();
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
const addRuleBtn = document.getElementById('addRuleBtn');
|
||||
const saveRuleBtn = document.getElementById('saveRuleBtn');
|
||||
const matchTypeSelect = document.getElementById('matchType');
|
||||
|
||||
if (addRuleBtn) {
|
||||
addRuleBtn.addEventListener('click', () => {
|
||||
console.log('Add Rule button clicked');
|
||||
openRuleModal();
|
||||
});
|
||||
} else {
|
||||
console.error('Add Rule button not found!');
|
||||
}
|
||||
|
||||
if (saveRuleBtn) {
|
||||
saveRuleBtn.addEventListener('click', saveRule);
|
||||
} else {
|
||||
console.error('Save Rule button not found!');
|
||||
}
|
||||
|
||||
if (matchTypeSelect) {
|
||||
matchTypeSelect.addEventListener('change', (e) => {
|
||||
updateFieldVisibility(e.target.value);
|
||||
});
|
||||
} else {
|
||||
console.error('Match Type select not found!');
|
||||
}
|
||||
}
|
||||
|
||||
function updateFieldVisibility(matchType) {
|
||||
const urlGroup = document.getElementById('urlPatternGroup');
|
||||
const elementGroup = document.getElementById('elementSelectorGroup');
|
||||
const urlInput = document.getElementById('urlPattern');
|
||||
const elementInput = document.getElementById('elementSelector');
|
||||
|
||||
if (matchType === 'url') {
|
||||
urlGroup.style.display = 'block';
|
||||
elementGroup.style.display = 'none';
|
||||
urlInput.required = true;
|
||||
elementInput.required = false;
|
||||
} else if (matchType === 'element') {
|
||||
urlGroup.style.display = 'none';
|
||||
elementGroup.style.display = 'block';
|
||||
urlInput.required = false;
|
||||
elementInput.required = true;
|
||||
} else if (matchType === 'both') {
|
||||
urlGroup.style.display = 'block';
|
||||
elementGroup.style.display = 'block';
|
||||
urlInput.required = true;
|
||||
elementInput.required = true;
|
||||
}
|
||||
}
|
||||
|
||||
function loadRules() {
|
||||
chrome.storage.sync.get(['rules'], (result) => {
|
||||
currentRules = result.rules || [];
|
||||
renderRules();
|
||||
});
|
||||
}
|
||||
|
||||
function renderRules() {
|
||||
const rulesList = document.getElementById('rulesList');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
|
||||
if (currentRules.length === 0) {
|
||||
rulesList.innerHTML = '';
|
||||
emptyState.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
rulesList.innerHTML = currentRules.map((rule, index) => {
|
||||
const matchTypeLabel = {
|
||||
'url': 'URL',
|
||||
'element': 'Element',
|
||||
'both': 'URL + Element'
|
||||
}[rule.matchType] || rule.matchType;
|
||||
|
||||
const hasJS = rule.javascript && rule.javascript.trim();
|
||||
const hasCSS = rule.css && rule.css.trim();
|
||||
|
||||
return `
|
||||
<div class="rule-card" data-rule-index="${index}">
|
||||
<div class="rule-header">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="rule-name">${escapeHtml(rule.name)}</span>
|
||||
<span class="badge rule-badge bg-secondary">${matchTypeLabel}</span>
|
||||
${hasJS ? '<span class="badge rule-badge bg-warning">JS</span>' : ''}
|
||||
${hasCSS ? '<span class="badge rule-badge bg-info">CSS</span>' : ''}
|
||||
</div>
|
||||
<div class="rule-actions">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input rule-toggle" type="checkbox" ${rule.enabled ? 'checked' : ''}
|
||||
data-index="${index}" title="Enable/Disable">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-light btn-edit-rule" data-index="${index}" title="Edit">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger btn-delete-rule" data-index="${index}" title="Delete">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-details">
|
||||
${rule.matchType === 'url' || rule.matchType === 'both' ? `<div><strong>URL:</strong> ${escapeHtml(rule.urlPattern)}</div>` : ''}
|
||||
${rule.matchType === 'element' || rule.matchType === 'both' ? `<div><strong>Element:</strong> ${escapeHtml(rule.elementSelector)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Add event listeners using event delegation
|
||||
attachRuleEventListeners();
|
||||
}
|
||||
|
||||
function attachRuleEventListeners() {
|
||||
const rulesList = document.getElementById('rulesList');
|
||||
|
||||
// Remove old listeners if any
|
||||
rulesList.removeEventListener('click', handleRuleClick);
|
||||
rulesList.removeEventListener('change', handleRuleToggle);
|
||||
|
||||
// Add new listeners
|
||||
rulesList.addEventListener('click', handleRuleClick);
|
||||
rulesList.addEventListener('change', handleRuleToggle);
|
||||
}
|
||||
|
||||
function handleRuleClick(e) {
|
||||
const editBtn = e.target.closest('.btn-edit-rule');
|
||||
const deleteBtn = e.target.closest('.btn-delete-rule');
|
||||
|
||||
if (editBtn) {
|
||||
const index = parseInt(editBtn.dataset.index);
|
||||
editRule(index);
|
||||
} else if (deleteBtn) {
|
||||
const index = parseInt(deleteBtn.dataset.index);
|
||||
deleteRule(index);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRuleToggle(e) {
|
||||
if (e.target.classList.contains('rule-toggle')) {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
toggleRule(index);
|
||||
}
|
||||
}
|
||||
|
||||
function openRuleModal(ruleIndex = -1) {
|
||||
editingRuleIndex = ruleIndex;
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const form = document.getElementById('ruleForm');
|
||||
|
||||
form.reset();
|
||||
|
||||
if (ruleIndex >= 0) {
|
||||
modalTitle.textContent = 'Edit Rule';
|
||||
const rule = currentRules[ruleIndex];
|
||||
document.getElementById('ruleName').value = rule.name;
|
||||
document.getElementById('matchType').value = rule.matchType;
|
||||
document.getElementById('urlPattern').value = rule.urlPattern || '';
|
||||
document.getElementById('elementSelector').value = rule.elementSelector || '';
|
||||
document.getElementById('javascript').value = rule.javascript || '';
|
||||
document.getElementById('css').value = rule.css || '';
|
||||
document.getElementById('ruleEnabled').checked = rule.enabled;
|
||||
} else {
|
||||
modalTitle.textContent = 'Add Rule';
|
||||
document.getElementById('ruleEnabled').checked = true;
|
||||
}
|
||||
|
||||
updateFieldVisibility(document.getElementById('matchType').value);
|
||||
|
||||
// Show modal - ensure Bootstrap Modal is initialized
|
||||
if (ruleModal) {
|
||||
ruleModal.show();
|
||||
} else {
|
||||
// Fallback: try to initialize and show
|
||||
try {
|
||||
ruleModal = new bootstrap.Modal(document.getElementById('ruleModal'));
|
||||
ruleModal.show();
|
||||
} catch (e) {
|
||||
console.error('Error showing modal:', e);
|
||||
alert('Error opening rule editor. Please reload the extension.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveRule() {
|
||||
const name = document.getElementById('ruleName').value.trim();
|
||||
const matchType = document.getElementById('matchType').value;
|
||||
const urlPattern = document.getElementById('urlPattern').value.trim();
|
||||
const elementSelector = document.getElementById('elementSelector').value.trim();
|
||||
const javascript = document.getElementById('javascript').value;
|
||||
const css = document.getElementById('css').value;
|
||||
const enabled = document.getElementById('ruleEnabled').checked;
|
||||
|
||||
// Validation
|
||||
if (!name) {
|
||||
alert('Please enter a rule name');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'url' && !urlPattern) {
|
||||
alert('Please enter a URL pattern');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'element' && !elementSelector) {
|
||||
alert('Please enter an element selector');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'both' && (!urlPattern || !elementSelector)) {
|
||||
alert('Please enter both URL pattern and element selector');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!javascript && !css) {
|
||||
alert('Please enter either JavaScript or CSS code');
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = {
|
||||
name,
|
||||
matchType,
|
||||
urlPattern,
|
||||
elementSelector,
|
||||
javascript,
|
||||
css,
|
||||
enabled
|
||||
};
|
||||
|
||||
if (editingRuleIndex >= 0) {
|
||||
currentRules[editingRuleIndex] = rule;
|
||||
} else {
|
||||
currentRules.push(rule);
|
||||
}
|
||||
|
||||
chrome.storage.sync.set({ rules: currentRules }, () => {
|
||||
loadRules();
|
||||
ruleModal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function editRule(index) {
|
||||
openRuleModal(index);
|
||||
}
|
||||
|
||||
function deleteRule(index) {
|
||||
if (confirm(`Delete rule "${currentRules[index].name}"?`)) {
|
||||
currentRules.splice(index, 1);
|
||||
chrome.storage.sync.set({ rules: currentRules }, () => {
|
||||
loadRules();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRule(index) {
|
||||
currentRules[index].enabled = !currentRules[index].enabled;
|
||||
chrome.storage.sync.set({ rules: currentRules }, () => {
|
||||
loadRules();
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Background script for Firefox extension
|
||||
browser.runtime.onInstalled.addListener(() => {
|
||||
console.log('GreasyBoii extension installed');
|
||||
});
|
||||
|
||||
// Listen for messages from content script
|
||||
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'getRules') {
|
||||
browser.storage.sync.get(['rules']).then((result) => {
|
||||
sendResponse({ rules: result.rules || [] });
|
||||
});
|
||||
return true; // Keep channel open for async response
|
||||
}
|
||||
});
|
||||
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
// Content script that runs on all pages
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
console.log('[GreasyBoii] Content script loaded on:', window.location.href);
|
||||
|
||||
// Wait for DOM to be ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initGreasyBoii);
|
||||
} else {
|
||||
initGreasyBoii();
|
||||
}
|
||||
|
||||
function initGreasyBoii() {
|
||||
console.log('[GreasyBoii] Initializing...');
|
||||
|
||||
// Get rules from storage and execute matching ones
|
||||
browser.storage.sync.get(['rules']).then((result) => {
|
||||
const rules = result.rules || [];
|
||||
const currentUrl = window.location.href;
|
||||
|
||||
console.log('[GreasyBoii] Found', rules.length, 'rule(s) in storage');
|
||||
|
||||
rules.forEach((rule) => {
|
||||
if (!rule.enabled) {
|
||||
console.log('[GreasyBoii] Skipping disabled rule:', rule.name);
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldExecute = false;
|
||||
|
||||
// Check conditions
|
||||
if (rule.matchType === 'url') {
|
||||
shouldExecute = currentUrl.includes(rule.urlPattern);
|
||||
console.log(`[GreasyBoii] URL match test for "${rule.name}": ${currentUrl} includes "${rule.urlPattern}" = ${shouldExecute}`);
|
||||
} else if (rule.matchType === 'element') {
|
||||
shouldExecute = document.querySelector(rule.elementSelector) !== null;
|
||||
console.log(`[GreasyBoii] Element match test for "${rule.name}": selector "${rule.elementSelector}" found = ${shouldExecute}`);
|
||||
} else if (rule.matchType === 'both') {
|
||||
const urlMatches = currentUrl.includes(rule.urlPattern);
|
||||
const elementExists = document.querySelector(rule.elementSelector) !== null;
|
||||
shouldExecute = urlMatches && elementExists;
|
||||
console.log(`[GreasyBoii] Combined match test for "${rule.name}": URL=${urlMatches}, Element=${elementExists}, Result=${shouldExecute}`);
|
||||
}
|
||||
|
||||
if (shouldExecute) {
|
||||
console.log(`[GreasyBoii] ✓ Executing rule: ${rule.name}`);
|
||||
executeRule(rule);
|
||||
} else {
|
||||
console.log(`[GreasyBoii] ✗ Rule "${rule.name}" conditions not met`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function executeRule(rule) {
|
||||
// For Firefox, we'll use exportFunction and cloneInto to bypass CSP
|
||||
if (rule.javascript && rule.javascript.trim()) {
|
||||
console.log(`[GreasyBoii] Injecting JavaScript for rule: ${rule.name}`);
|
||||
|
||||
try {
|
||||
// Method 1: Try wrappedJSObject (Firefox specific)
|
||||
if (typeof wrappedJSObject !== 'undefined') {
|
||||
try {
|
||||
const code = `
|
||||
(function() {
|
||||
try {
|
||||
${rule.javascript}
|
||||
} catch (e) {
|
||||
console.error('[GreasyBoii] Error in injected script:', e);
|
||||
}
|
||||
})();
|
||||
`;
|
||||
wrappedJSObject.eval(code);
|
||||
console.log(`[GreasyBoii] ✓ JavaScript executed via wrappedJSObject`);
|
||||
return;
|
||||
} catch (e) {
|
||||
console.log(`[GreasyBoii] wrappedJSObject method failed, trying alternative...`);
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Script injection (works on most sites)
|
||||
const script = document.createElement('script');
|
||||
script.textContent = `
|
||||
(function() {
|
||||
try {
|
||||
${rule.javascript}
|
||||
} catch (e) {
|
||||
console.error('[GreasyBoii] Error in injected script:', e);
|
||||
}
|
||||
})();
|
||||
`;
|
||||
(document.head || document.documentElement).appendChild(script);
|
||||
script.remove();
|
||||
console.log(`[GreasyBoii] ✓ JavaScript injected successfully`);
|
||||
} catch (error) {
|
||||
console.error(`[GreasyBoii] Error executing JavaScript for rule "${rule.name}":`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute CSS - CSS injection doesn't have CSP issues
|
||||
if (rule.css && rule.css.trim()) {
|
||||
try {
|
||||
console.log(`[GreasyBoii] Injecting CSS for rule: ${rule.name}`);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = rule.css;
|
||||
style.setAttribute('data-greasyboii-rule', rule.name);
|
||||
(document.head || document.documentElement).appendChild(style);
|
||||
|
||||
console.log(`[GreasyBoii] ✓ CSS injected successfully`);
|
||||
} catch (error) {
|
||||
console.error(`[GreasyBoii] Error applying CSS for rule "${rule.name}":`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 499 B |
Binary file not shown.
|
After Width: | Height: | Size: 115 B |
Binary file not shown.
|
After Width: | Height: | Size: 202 B |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "GreasyBoii",
|
||||
"version": "1.0.4",
|
||||
"description": "Execute custom JavaScript and CSS based on URL patterns and page elements",
|
||||
"author": "Andy Dixon",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"activeTab",
|
||||
"<all_urls>"
|
||||
],
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["content.js"],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": false
|
||||
}
|
||||
],
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"default_icon": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
body {
|
||||
width: 600px;
|
||||
min-height: 400px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.rules-list {
|
||||
max-height: 450px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.rule-card {
|
||||
border: 1px solid #495057;
|
||||
border-radius: 0.375rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
background-color: #212529;
|
||||
}
|
||||
|
||||
.rule-card:hover {
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.rule-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.rule-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.rule-details {
|
||||
font-size: 0.875rem;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
.rule-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.font-monospace {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.form-switch .form-check-input {
|
||||
cursor: pointer;
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.modal-lg {
|
||||
max-width: 700px;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GreasyBoii</title>
|
||||
<link rel="stylesheet" href="bootstrap.min.css">
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid p-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0">GreasyBoii</h4>
|
||||
<button class="btn btn-primary btn-sm" id="addRuleBtn">+ Add Rule</button>
|
||||
</div>
|
||||
|
||||
<div id="rulesList" class="rules-list mb-3">
|
||||
<!-- Rules will be dynamically inserted here -->
|
||||
</div>
|
||||
|
||||
<div id="emptyState" class="text-center text-muted py-4" style="display: none;">
|
||||
<p>No rules yet. Click "Add Rule" to get started.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Rule Modal -->
|
||||
<div class="modal fade" id="ruleModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Add Rule</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="ruleForm">
|
||||
<div class="mb-3">
|
||||
<label for="ruleName" class="form-label">Rule Name</label>
|
||||
<input type="text" class="form-control" id="ruleName" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="matchType" class="form-label">Match Type</label>
|
||||
<select class="form-select" id="matchType" required>
|
||||
<option value="url">URL contains string</option>
|
||||
<option value="element">Page contains element</option>
|
||||
<option value="both">Both URL and element</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="urlPatternGroup">
|
||||
<label for="urlPattern" class="form-label">URL Pattern</label>
|
||||
<input type="text" class="form-control" id="urlPattern" placeholder="e.g., example.com">
|
||||
<small class="form-text text-muted">Enter a string to match in the URL</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="elementSelectorGroup" style="display: none;">
|
||||
<label for="elementSelector" class="form-label">Element Selector</label>
|
||||
<input type="text" class="form-control" id="elementSelector" placeholder="e.g., #my-element or .my-class">
|
||||
<small class="form-text text-muted">Enter a CSS selector</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="javascript" class="form-label">JavaScript Code</label>
|
||||
<textarea class="form-control font-monospace" id="javascript" rows="6" placeholder="console.log('Hello from GreasyBoii!');"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="css" class="form-label">CSS Code</label>
|
||||
<textarea class="form-control font-monospace" id="css" rows="6" placeholder="body { background-color: #000; }"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="ruleEnabled" checked>
|
||||
<label class="form-check-label" for="ruleEnabled">
|
||||
Enabled
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" id="saveRuleBtn">Save Rule</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="bootstrap.bundle.min.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,302 @@
|
||||
// Popup script for managing rules (Firefox version)
|
||||
let currentRules = [];
|
||||
let editingRuleIndex = -1;
|
||||
let ruleModal;
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Wait for Bootstrap to be fully loaded
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
initializeExtension();
|
||||
} else {
|
||||
// Fallback: wait a bit for Bootstrap to load
|
||||
setTimeout(initializeExtension, 100);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeExtension() {
|
||||
try {
|
||||
ruleModal = new bootstrap.Modal(document.getElementById('ruleModal'));
|
||||
} catch (e) {
|
||||
console.error('Error initializing modal:', e);
|
||||
}
|
||||
|
||||
loadRules();
|
||||
setupEventListeners();
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
const addRuleBtn = document.getElementById('addRuleBtn');
|
||||
const saveRuleBtn = document.getElementById('saveRuleBtn');
|
||||
const matchTypeSelect = document.getElementById('matchType');
|
||||
|
||||
if (addRuleBtn) {
|
||||
addRuleBtn.addEventListener('click', () => {
|
||||
console.log('Add Rule button clicked');
|
||||
openRuleModal();
|
||||
});
|
||||
} else {
|
||||
console.error('Add Rule button not found!');
|
||||
}
|
||||
|
||||
if (saveRuleBtn) {
|
||||
saveRuleBtn.addEventListener('click', saveRule);
|
||||
} else {
|
||||
console.error('Save Rule button not found!');
|
||||
}
|
||||
|
||||
if (matchTypeSelect) {
|
||||
matchTypeSelect.addEventListener('change', (e) => {
|
||||
updateFieldVisibility(e.target.value);
|
||||
});
|
||||
} else {
|
||||
console.error('Match Type select not found!');
|
||||
}
|
||||
}
|
||||
|
||||
function updateFieldVisibility(matchType) {
|
||||
const urlGroup = document.getElementById('urlPatternGroup');
|
||||
const elementGroup = document.getElementById('elementSelectorGroup');
|
||||
const urlInput = document.getElementById('urlPattern');
|
||||
const elementInput = document.getElementById('elementSelector');
|
||||
|
||||
if (matchType === 'url') {
|
||||
urlGroup.style.display = 'block';
|
||||
elementGroup.style.display = 'none';
|
||||
urlInput.required = true;
|
||||
elementInput.required = false;
|
||||
} else if (matchType === 'element') {
|
||||
urlGroup.style.display = 'none';
|
||||
elementGroup.style.display = 'block';
|
||||
urlInput.required = false;
|
||||
elementInput.required = true;
|
||||
} else if (matchType === 'both') {
|
||||
urlGroup.style.display = 'block';
|
||||
elementGroup.style.display = 'block';
|
||||
urlInput.required = true;
|
||||
elementInput.required = true;
|
||||
}
|
||||
}
|
||||
|
||||
function loadRules() {
|
||||
browser.storage.sync.get(['rules']).then((result) => {
|
||||
currentRules = result.rules || [];
|
||||
renderRules();
|
||||
});
|
||||
}
|
||||
|
||||
function renderRules() {
|
||||
const rulesList = document.getElementById('rulesList');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
|
||||
if (currentRules.length === 0) {
|
||||
rulesList.innerHTML = '';
|
||||
emptyState.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.style.display = 'none';
|
||||
|
||||
rulesList.innerHTML = currentRules.map((rule, index) => {
|
||||
const matchTypeLabel = {
|
||||
'url': 'URL',
|
||||
'element': 'Element',
|
||||
'both': 'URL + Element'
|
||||
}[rule.matchType] || rule.matchType;
|
||||
|
||||
const hasJS = rule.javascript && rule.javascript.trim();
|
||||
const hasCSS = rule.css && rule.css.trim();
|
||||
|
||||
return `
|
||||
<div class="rule-card" data-rule-index="${index}">
|
||||
<div class="rule-header">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="rule-name">${escapeHtml(rule.name)}</span>
|
||||
<span class="badge rule-badge bg-secondary">${matchTypeLabel}</span>
|
||||
${hasJS ? '<span class="badge rule-badge bg-warning">JS</span>' : ''}
|
||||
${hasCSS ? '<span class="badge rule-badge bg-info">CSS</span>' : ''}
|
||||
</div>
|
||||
<div class="rule-actions">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input rule-toggle" type="checkbox" ${rule.enabled ? 'checked' : ''}
|
||||
data-index="${index}" title="Enable/Disable">
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-light btn-edit-rule" data-index="${index}" title="Edit">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger btn-delete-rule" data-index="${index}" title="Delete">
|
||||
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-details">
|
||||
${rule.matchType === 'url' || rule.matchType === 'both' ? `<div><strong>URL:</strong> ${escapeHtml(rule.urlPattern)}</div>` : ''}
|
||||
${rule.matchType === 'element' || rule.matchType === 'both' ? `<div><strong>Element:</strong> ${escapeHtml(rule.elementSelector)}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Add event listeners using event delegation
|
||||
attachRuleEventListeners();
|
||||
}
|
||||
|
||||
function attachRuleEventListeners() {
|
||||
const rulesList = document.getElementById('rulesList');
|
||||
|
||||
// Remove old listeners if any
|
||||
rulesList.removeEventListener('click', handleRuleClick);
|
||||
rulesList.removeEventListener('change', handleRuleToggle);
|
||||
|
||||
// Add new listeners
|
||||
rulesList.addEventListener('click', handleRuleClick);
|
||||
rulesList.addEventListener('change', handleRuleToggle);
|
||||
}
|
||||
|
||||
function handleRuleClick(e) {
|
||||
const editBtn = e.target.closest('.btn-edit-rule');
|
||||
const deleteBtn = e.target.closest('.btn-delete-rule');
|
||||
|
||||
if (editBtn) {
|
||||
const index = parseInt(editBtn.dataset.index);
|
||||
editRule(index);
|
||||
} else if (deleteBtn) {
|
||||
const index = parseInt(deleteBtn.dataset.index);
|
||||
deleteRule(index);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRuleToggle(e) {
|
||||
if (e.target.classList.contains('rule-toggle')) {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
toggleRule(index);
|
||||
}
|
||||
}
|
||||
|
||||
function openRuleModal(ruleIndex = -1) {
|
||||
editingRuleIndex = ruleIndex;
|
||||
const modalTitle = document.getElementById('modalTitle');
|
||||
const form = document.getElementById('ruleForm');
|
||||
|
||||
form.reset();
|
||||
|
||||
if (ruleIndex >= 0) {
|
||||
modalTitle.textContent = 'Edit Rule';
|
||||
const rule = currentRules[ruleIndex];
|
||||
document.getElementById('ruleName').value = rule.name;
|
||||
document.getElementById('matchType').value = rule.matchType;
|
||||
document.getElementById('urlPattern').value = rule.urlPattern || '';
|
||||
document.getElementById('elementSelector').value = rule.elementSelector || '';
|
||||
document.getElementById('javascript').value = rule.javascript || '';
|
||||
document.getElementById('css').value = rule.css || '';
|
||||
document.getElementById('ruleEnabled').checked = rule.enabled;
|
||||
} else {
|
||||
modalTitle.textContent = 'Add Rule';
|
||||
document.getElementById('ruleEnabled').checked = true;
|
||||
}
|
||||
|
||||
updateFieldVisibility(document.getElementById('matchType').value);
|
||||
|
||||
// Show modal - ensure Bootstrap Modal is initialized
|
||||
if (ruleModal) {
|
||||
ruleModal.show();
|
||||
} else {
|
||||
// Fallback: try to initialize and show
|
||||
try {
|
||||
ruleModal = new bootstrap.Modal(document.getElementById('ruleModal'));
|
||||
ruleModal.show();
|
||||
} catch (e) {
|
||||
console.error('Error showing modal:', e);
|
||||
alert('Error opening rule editor. Please reload the extension.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveRule() {
|
||||
const name = document.getElementById('ruleName').value.trim();
|
||||
const matchType = document.getElementById('matchType').value;
|
||||
const urlPattern = document.getElementById('urlPattern').value.trim();
|
||||
const elementSelector = document.getElementById('elementSelector').value.trim();
|
||||
const javascript = document.getElementById('javascript').value;
|
||||
const css = document.getElementById('css').value;
|
||||
const enabled = document.getElementById('ruleEnabled').checked;
|
||||
|
||||
// Validation
|
||||
if (!name) {
|
||||
alert('Please enter a rule name');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'url' && !urlPattern) {
|
||||
alert('Please enter a URL pattern');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'element' && !elementSelector) {
|
||||
alert('Please enter an element selector');
|
||||
return;
|
||||
}
|
||||
|
||||
if (matchType === 'both' && (!urlPattern || !elementSelector)) {
|
||||
alert('Please enter both URL pattern and element selector');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!javascript && !css) {
|
||||
alert('Please enter either JavaScript or CSS code');
|
||||
return;
|
||||
}
|
||||
|
||||
const rule = {
|
||||
name,
|
||||
matchType,
|
||||
urlPattern,
|
||||
elementSelector,
|
||||
javascript,
|
||||
css,
|
||||
enabled
|
||||
};
|
||||
|
||||
if (editingRuleIndex >= 0) {
|
||||
currentRules[editingRuleIndex] = rule;
|
||||
} else {
|
||||
currentRules.push(rule);
|
||||
}
|
||||
|
||||
browser.storage.sync.set({ rules: currentRules }).then(() => {
|
||||
loadRules();
|
||||
ruleModal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function editRule(index) {
|
||||
openRuleModal(index);
|
||||
}
|
||||
|
||||
function deleteRule(index) {
|
||||
if (confirm(`Delete rule "${currentRules[index].name}"?`)) {
|
||||
currentRules.splice(index, 1);
|
||||
browser.storage.sync.set({ rules: currentRules }).then(() => {
|
||||
loadRules();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRule(index) {
|
||||
currentRules[index].enabled = !currentRules[index].enabled;
|
||||
browser.storage.sync.set({ rules: currentRules }).then(() => {
|
||||
loadRules();
|
||||
});
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
echo "📦 Packaging GreasyBoii v1.0.4..."
|
||||
mkdir -p dist
|
||||
|
||||
echo "Chrome..."
|
||||
cd chrome && zip -r ../dist/greasyboii-chrome-v1.0.4.zip * -x "*.DS_Store" && cd ..
|
||||
|
||||
echo "Firefox..."
|
||||
cd firefox && zip -r ../dist/greasyboii-firefox-v1.0.4.zip * -x "*.DS_Store" && cd ..
|
||||
|
||||
echo ""
|
||||
echo "✅ Done! Packages created:"
|
||||
ls -lh dist/
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Greasemonkey Test Page</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
h1 { color: #333; }
|
||||
.test-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.test-element {
|
||||
padding: 10px;
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #2196F3;
|
||||
margin: 10px 0;
|
||||
}
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Greasemonkey Extension Test Page</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Test Element Selector</h2>
|
||||
<p>This page contains elements you can target with your Greasemonkey rules.</p>
|
||||
|
||||
<div id="test-element-1" class="test-element">
|
||||
<strong>Element #1</strong> - ID: <code>#test-element-1</code>
|
||||
</div>
|
||||
|
||||
<div id="test-element-2" class="test-element">
|
||||
<strong>Element #2</strong> - ID: <code>#test-element-2</code>
|
||||
</div>
|
||||
|
||||
<div class="test-class-target test-element">
|
||||
<strong>Element #3</strong> - Class: <code>.test-class-target</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Example Rules to Try</h2>
|
||||
|
||||
<h3>Example 1: Change Background Color</h3>
|
||||
<ul>
|
||||
<li><strong>Match Type:</strong> URL contains string</li>
|
||||
<li><strong>URL Pattern:</strong> <code>test-page.html</code></li>
|
||||
<li><strong>CSS Code:</strong></li>
|
||||
</ul>
|
||||
<pre><code>body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
||||
color: white !important;
|
||||
}</code></pre>
|
||||
|
||||
<h3>Example 2: Add Alert on Element</h3>
|
||||
<ul>
|
||||
<li><strong>Match Type:</strong> Page contains element</li>
|
||||
<li><strong>Element Selector:</strong> <code>#test-element-1</code></li>
|
||||
<li><strong>JavaScript Code:</strong></li>
|
||||
</ul>
|
||||
<pre><code>const elem = document.getElementById('test-element-1');
|
||||
elem.style.background = 'yellow';
|
||||
elem.innerHTML += ' - Modified by Greasemonkey!';</code></pre>
|
||||
|
||||
<h3>Example 3: Combined Rule</h3>
|
||||
<ul>
|
||||
<li><strong>Match Type:</strong> Both URL and element</li>
|
||||
<li><strong>URL Pattern:</strong> <code>test-page</code></li>
|
||||
<li><strong>Element Selector:</strong> <code>.test-class-target</code></li>
|
||||
<li><strong>JavaScript Code:</strong></li>
|
||||
</ul>
|
||||
<pre><code>const elem = document.querySelector('.test-class-target');
|
||||
elem.style.border = '4px solid red';
|
||||
elem.style.animation = 'pulse 2s infinite';
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);</code></pre>
|
||||
|
||||
<h3>Example 4: Hide Elements</h3>
|
||||
<ul>
|
||||
<li><strong>Match Type:</strong> URL contains string</li>
|
||||
<li><strong>URL Pattern:</strong> <code>test-page.html</code></li>
|
||||
<li><strong>CSS Code:</strong></li>
|
||||
</ul>
|
||||
<pre><code>#test-element-2 {
|
||||
display: none !important;
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>How to Test</h2>
|
||||
<ol>
|
||||
<li>Install the Greasemonkey extension in your browser</li>
|
||||
<li>Open this test page in your browser</li>
|
||||
<li>Click the Greasemonkey icon in your toolbar</li>
|
||||
<li>Create a new rule using one of the examples above</li>
|
||||
<li>Reload this page to see the effects</li>
|
||||
<li>Check the browser console (F12) for Greasemonkey log messages</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>Verification</h2>
|
||||
<p>Open your browser's developer console (F12) to see Greasemonkey execution logs.</p>
|
||||
<p>You should see messages like: <code>[Greasemonkey] Executing rule: Your Rule Name</code></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
console.log('Test page loaded. Greasemonkey rules should execute now if conditions are met.');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user