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