From fb308715ba84ca84a2e6c8c6528e8ca7a62c298c Mon Sep 17 00:00:00 2001 From: callmegodsent Date: Fri, 17 Jan 2025 18:21:45 +0100 Subject: [PATCH] push back --- README.md | 245 +++++++++++++++++++++++++++++++++ example.html | 196 +++++++++++++++++++++++++++ miragek-StopEdit.js | 321 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 762 insertions(+) create mode 100644 README.md create mode 100644 example.html create mode 100644 miragek-StopEdit.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ba6ec3 --- /dev/null +++ b/README.md @@ -0,0 +1,245 @@ +--- +>- + 1. [Features](#features) 2. [Installation](#installation) 3. [Usage](#usage) + 4. [Example](#example) 5. [Options](#options) 6. [How It Works](#how-it-works) + 7. [Changelog](#changelog) 8. [To-Do List](#to-do-list) 9. + [Contributing](#contributing) 10. [License](#license) 11. [Author](#author) +--- + +# StopEdit - A Lightweight JavaScript Library to Protect Your Page 🛡️ + +StopEdit is a simple JavaScript library designed to keep your webpage safe from unwanted changes. It actively monitors your page for edits and resets everything back to its original state. Whether it's someone tampering with "Inspect Element" or trying to edit your content directly, StopEdit has you covered. + +## ✨ Features + +- **Customizable Protection**: Guard your entire page or just specific elements (default: ``). +- **Automatic Reversion**: Detects unauthorized changes and immediately reverts them. +- **Heartbeat Monitoring**: Periodic checks to ensure content integrity. +- **Debugging Tools**: Enables detailed logging in the console for easy debugging. + +-------------------------------------------------------------------------------- + +## ⚙️ Installation + +To install StopEdit, simply download the `StopEdit.js` file and include it in your HTML file: + +```html + +``` + +-------------------------------------------------------------------------------- + +## 🛠️ Usage + +Here's how you can set it up: + +### Core Feature + +```html + +``` + +### Image protection + +```html +anything +``` + +-------------------------------------------------------------------------------- + +## 🧩 Example + +Here's a complete example to get you started: + +```html + + + + + + StopEdit Example + + +

Protected Content

+

This text is protected. Any changes made to it will be automatically undone.

+ +
+

Protected Text

+
+ This section is editable! Feel free to type here. 🤦‍♂️ +
+
+ This section is editable! Feel free to type here ✍️ +
+
+ +
+

Protected Text

+
+ This section is editable! Feel free to type here but only via inspect element 🔴 +
+
+ This section is editable! Feel free to type here but only via inspect element 😎 +
+
+ +
+

Protected Text

+
+ This section is not editable! Why? because we did not add the editable-section id😃 +
+ +
+ This section is not editable! Why? because we did not add the editable-section id ✍️ +
+
+ + +anything +
+ +anything + + + + + + + +``` + +Please refer to the example.html for more complete examples with code snippets you can edit and play with to better understand the library. Please do this before you drop any hate speech. Say no to cyber bully. This lib/plugin is made by a PHP developer 😂. But feel safe and free to take it serious. + +-------------------------------------------------------------------------------- + +## 🔧 Options + +- **`selector`** _(default: `'body'`)_: Target specific elements for protection. +- **`heartbeat`** _(default: `1000` ms)_: Set the interval (in milliseconds) for monitoring changes. +- **`debug`** _(default: `false`)_: Turn on logging to track actions in the console. + +-------------------------------------------------------------------------------- + +## 🔍 How It Works + +1. **Content Backup**: When initialized, StopEdit saves a copy of the original content. +2. **Live Monitoring**: It uses `MutationObserver` to track real-time changes. +3. **Regular Checks**: Optional heartbeat checks provide an added layer of security. + +-------------------------------------------------------------------------------- + +## 📜 Changelog + +### [Unreleased] + +- Video protection +- Print blocker +- Adblock detect +- Device Blocker +- Prevent Copy +- Block suspected dangerous users +- Lock page with password + +### [1.2.0] - Current ✌ + +- Image protection +- Readme doc updated + +### [1.1.0] - Initial Release 🎉 + +- Core functionality for monitoring and resetting content. +- Documentation added. + +-------------------------------------------------------------------------------- + +## 📝 To-Do List + +### Current Progress 🚀 + +- [x] Browser compatibility testing. +- [x] Improved protection for image URLs. +- [ ] Enhanced anti-theft measures for code. +- [ ] Prevent copying of page content. + +### Future Enhancements 🔧 + +- [ ] Server-side integration (PHP, Node.js). +- [ ] Detailed online documentation with examples. + +-------------------------------------------------------------------------------- + +## 🤝 Contributing + +We welcome contributions! Whether it's fixing a bug, adding new features, or improving the documentation, your input is valuable. Here's how to contribute: + +1. **Fork the repository**: [Visit the repo here](https://github.com/miragekdev/miragek-stop-edit-js-lib). +2. **Clone your fork**: + + ```bash + git clone https://github.com/YOUR_USERNAME/miragek-stop-edit-js-lib.git + ``` + +3. **Create a branch**: + + ```bash + git checkout -b feature/your-feature-name + ``` + +4. **Make your changes and commit**: + + ```bash + git add . + git commit -m "Description of your changes" + ``` + +5. **Push your changes**: + + ```bash + git push origin feature/your-feature-name + ``` + +6. **Submit a Pull Request**: Open a PR in the main repository. + +-------------------------------------------------------------------------------- + +### Guidelines + +- Ensure your changes are well-tested. +- Keep your commit messages clear and concise. +- Follow the repository's coding standards. + +Have questions? Feel free to open an issue or start a discussion! + +-------------------------------------------------------------------------------- + +## 📄 License + +StopEdit is licensed under the MIT License. See the `LICENSE` file for more details. + +-------------------------------------------------------------------------------- + +## 👤 Author + +**Created by Godsent for Miragek** + +-------------------------------------------------------------------------------- + +## 🚀 Final Notes + +StopEdit is a straightforward solution to protect your webpage content. It's lightweight, easy to set up, and highly effective. If you have suggestions, questions, or feedback, don't hesitate to get in touch! diff --git a/example.html b/example.html new file mode 100644 index 0000000..1620592 --- /dev/null +++ b/example.html @@ -0,0 +1,196 @@ + + + + + + StopEdit - Cool Edition 😎 + + + +
+

StopEdit Demo - Protect Your Content 🛡️

+ +

Explore the power of protected text and images. Try editing and see the magic! ✨

+
+ +
+
Protected Text
+
+

This text is protected and cannot be edited directly. 🔒

+

The editable section below is an example of a text area that users can type into, but any edits made will be monitored and protected by the StopEdit system.

+
+
+ +
+

Protected Text

+
+ This section is editable! Feel free to type here. 🤦‍♂️ +
+
+ This section is editable! Feel free to type here ✍️ +
+
+ +
+

Protected Text

+
+ This section is editable! Feel free to type here but only via inspect element 🔴 +
+
+ This section is editable! Feel free to type here but only via inspect element 😎 +
+
+ +
+

Protected Text

+
+ This section is not editable! Why? because we did not add the editable-section id😃 +
+ +
+ This section is not editable! Why? because we did not add the editable-section id ✍️ +
+
+ + +
+
Protected Images
+
+

In this section, we have two images: one protected and the other normal. The protected image has special styling to distinguish it from the regular one.

+
+
+ +
+

Protected Images

+ +
+ + +
+
Footer & JavaScript Initialization
+
+

The footer contains credits for the StopEdit Team. Below that, JavaScript is used to initialize the StopEdit library.

+

This code enables automatic protection for text and images, and stops unauthorized edits. The functionality is powered by the `miragek-StopEdit.js` library.

+
+
+ + + + + + + + + diff --git a/miragek-StopEdit.js b/miragek-StopEdit.js new file mode 100644 index 0000000..a30320f --- /dev/null +++ b/miragek-StopEdit.js @@ -0,0 +1,321 @@ +class StopEdit { + constructor(options = {}) { + this.selector = options.selector || 'body'; + this.heartbeat = options.heartbeat || 100000; // 100,000 ms (1.67 minutes) + this.debug = options.debug || false; + this.whitelist = options.whitelist || []; + this.originalContent = null; + this.observer = null; + this.whitelistMap = new Map(); + this.resetting = false; + this.mutationTimeout = null; + this.heartbeatInterval = null; + this.editableElements = new Set(); + this.protected = new Set(); // Track protected elements + } + + init() { + document.addEventListener('DOMContentLoaded', () => { + const target = document.querySelector(this.selector); + if (!target) { + console.error(`StopEdit: Selector "${this.selector}" not found.`); + return; + } + + // Initialize protection features + this.disableDirectEditing(target); + this.originalContent = this.cloneContent(target); + this.storeWhitelistElements(target); + this.startObserving(target); + + // Initialize image protection + this.initializeImageProtection(target); + + if (this.heartbeat) { + this.startHeartbeat(target); + } + + if (this.debug) { + console.log('StopEdit initialized: Protecting', this.selector); + } + + // Add global protection + this.addGlobalProtection(); + }); + } + + addGlobalProtection() { + // Prevent common keyboard shortcuts + document.addEventListener('keydown', (e) => { + if ((e.ctrlKey || e.metaKey) && + (e.key === 'c' || e.key === 'C' || + e.key === 'x' || e.key === 'X' || + e.key === 's' || e.key === 'S' || + e.key === 'p' || e.key === 'P')) { + e.preventDefault(); + return false; + } + }); + + // Prevent right-click on protected elements + document.addEventListener('contextmenu', (e) => { + if (this.isProtected(e.target)) { + e.preventDefault(); + return false; + } + }); + } + + initializeImageProtection(target) { + const protectedImages = this.getLabelledImages(target); + if (protectedImages.length > 0) { + this.protect(protectedImages); + } + + // Watch for dynamically added images + this.observeNewImages(target); + } + + observeNewImages(target) { + const imageObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeName === 'IMG' && node.hasAttribute('protected')) { + this.protect([node]); + } + }); + }); + }); + + imageObserver.observe(target, { + childList: true, + subtree: true + }); + } + + getLabelledImages(target) { + return Array.from(target.getElementsByTagName('img')) + .filter(img => img.hasAttribute('protected')); + } + + protect(images) { + images.forEach(img => { + try { + if (!img.complete) { + img.addEventListener('load', () => this.applyImageProtection(img)); + } else { + this.applyImageProtection(img); + } + } catch (error) { + if (this.debug) { + console.error('Failed to protect image:', error); + } + } + }); + } + + applyImageProtection(img) { + if (this.protected.has(img)) return; + + const canvas = document.createElement('canvas'); + + // Use specified dimensions for consistent sizing + const width = 150; // Desired width + const height = 150; // Desired height + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + + // Apply smoothing for better quality + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + + // Draw image scaled to the specified dimensions + ctx.drawImage(img, 0, 0, width, height); + + // Preserve original image attributes + canvas.className = img.className; + canvas.id = img.id; + canvas.setAttribute('protected', ''); + + // Apply the same inline styles as the original image + canvas.style.cssText = window.getComputedStyle(img).cssText; + + // Override size-specific styles to ensure proper dimensions + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + + // Add protection (e.g., disable interaction or prevent saving) + this.addProtectionEvents(canvas); + + // Replace original image + img.parentNode.insertBefore(canvas, img); + img.remove(); + + // Mark as protected + this.protected.add(canvas); +} + + + addProtectionEvents(element) { + const events = ['contextmenu', 'dragstart', 'selectstart', 'copy', 'cut']; + events.forEach(event => { + element.addEventListener(event, this.preventDefaultEvent); + }); + } + + preventDefaultEvent(e) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + + isProtected(element) { + return element.hasAttribute('protected') || this.protected.has(element); + } + + startHeartbeat(target) { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + } + + this.heartbeatInterval = setInterval(() => { + if (this.debug) { + console.log('StopEdit: Heartbeat check.'); + } + this.resetIfChanged(target); + }, this.heartbeat); + } + + + + disableDirectEditing(target) { + // Remove contenteditable from non-whitelisted elements + const allEditableElements = target.querySelectorAll('[contenteditable="true"]'); + allEditableElements.forEach(el => { + if (!this.isWhitelisted(el)) { + el.contentEditable = "inherit"; + } else { + this.editableElements.add(el); + } + }); + } + + storeWhitelistElements(target) { + this.whitelist.forEach(selector => { + const elements = target.querySelectorAll(selector); + elements.forEach((el, index) => { + const key = `${selector}-${index}`; + const clone = el.cloneNode(true); + + // Preserve the original contentEditable state from the HTML + clone.contentEditable = el.contentEditable; + + this.whitelistMap.set(key, clone); + }); + }); +} + + + cloneContent(target) { + const clone = target.cloneNode(true); + + // Remove whitelisted elements from the clone + this.whitelist.forEach(selector => { + const elements = clone.querySelectorAll(selector); + elements.forEach(el => { + const placeholder = document.createElement('div'); + placeholder.dataset.whitelistPlaceholder = selector; + el.parentNode.replaceChild(placeholder, el); + }); + }); + + return clone; + } + + startObserving(target) { + this.observer = new MutationObserver(mutations => { + if (this.debug) { + console.log('StopEdit: Detected mutations', mutations); + } + + // Check if mutations affect whitelisted elements + const affectsWhitelist = mutations.some(mutation => { + return this.whitelist.some(selector => { + return mutation.target.matches?.(selector) || + mutation.target.closest?.(selector); + }); + }); + + if (!affectsWhitelist) { + clearTimeout(this.mutationTimeout); + this.mutationTimeout = setTimeout(() => this.resetIfChanged(target), 200); + } + }); + + this.observer.observe(target, { + childList: true, + subtree: true, + characterData: true, + attributes: false, + }); + } + + resetIfChanged(target) { + if (this.resetting) return; + + this.resetting = true; + this.observer.disconnect(); + + // Store current whitelist content + const whitelistContent = new Map(); + this.whitelist.forEach(selector => { + const elements = target.querySelectorAll(selector); + elements.forEach((el, index) => { + whitelistContent.set(`${selector}-${index}`, el.innerHTML); + }); + }); + + // Reset to original content + target.innerHTML = this.originalContent.innerHTML; + + // Restore whitelist elements with their current content + this.whitelist.forEach(selector => { + const placeholders = target.querySelectorAll(`[data-whitelist-placeholder="${selector}"]`); + placeholders.forEach((placeholder, index) => { + const key = `${selector}-${index}`; + const savedElement = this.whitelistMap.get(key); + if (savedElement) { + const restored = savedElement.cloneNode(true); + restored.innerHTML = whitelistContent.get(key) || restored.innerHTML; + placeholder.parentNode.replaceChild(restored, placeholder); + } + }); + }); + + // Reapply image protection after reset + this.initializeImageProtection(target); + + this.startObserving(target); + this.resetting = false; + } + + isWhitelisted(node) { + return this.whitelist.some(selector => { + return node.matches?.(selector) || node.closest?.(selector); + }); + } + + disable() { + if (this.observer) { + this.observer.disconnect(); + } + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + } + if (this.debug) { + console.log('StopEdit: Protection disabled.'); + } + } +} \ No newline at end of file