Use Greasemonkey to release the copy and paste restrictions of web pages

Keywords: Javascript github

Our original blog address: https://blog.rxliuli.com/p/4b2822b2/
We have released an oil monkey script, which can be installed directly Remove page restrictions For a better experience.

scene

One thing that often happens when browsing the web, when we want to copy, suddenly we find that copying seems useless? ( Articles prohibited by Zhihu )Or there's a little bit more in the end of the copy.( Brief book ), or not at all( 360doc ) You may find that it can't be pasted all the time.( Alipay login).

problem

If you want to defeat the enemy first, you must first confuse the enemy. To remove the restrictions of copy and paste, it is necessary to know how they are implemented. In any case, all that can run on the browser is JavaScript, and they are all implemented using JavaScript. The implementation mode is basically to listen to the corresponding events (for example, onkeydown listens to Ctrl-C), and then do some special operations.

For example, only one sentence of code is needed to block the copy function.

document.oncopy = event => false

Yes, as long as false is returned, the copy will fail. There is a more annoying way to add inline events directly to the body element

<body oncopy="javascript: return false" />

Solve

As you can see, JavaScript is generally used to return false in the corresponding event to prevent the corresponding event. So, since the events have been stopped, does it mean that we are helpless? The solutions we can think of have three directions

  • Use JavaScript to monitor events and implement copy / cut / paste function

    • Advantage: after the implementation, no matter any website can be used, and it will not affect events other than listening, and it will not delete the same type of events listening, and it can release the restriction of browser itself (password box forbids copying)
    • Disadvantages: some functions are difficult to realize by themselves, such as selecting text
  • Re implement the addEventListener and delete the customized events of the website

    • Advantages: the event has a wide range of effectiveness and high universality. It can not only copy / cut / paste, but also release other types of events.
    • Disadvantages: the addEventListener event needs to be replaced early enough to implement. The default operation on the browser will not take effect (password box forbids copying), and some websites cannot be cracked.
  • Replace elements and remove event attributes on DOM

    • Advantages: it can ensure that the restrictions of website js are lifted, high universality and wide range of events.
    • Disadvantage: it may affect other types of events. When copying nodes, events added with addEventListener will not be copied.

      Note: this method is not demonstrated, and the defect is too large.

In short, if you really want to lift the restrictions, I'm afraid you need to use both ways.

Use JavaScript to monitor events and implement copy / cut / paste function

Implement forced replication

thinking

  1. Bubble monitoring copy event
  2. Get the currently selected content
  3. Set up cut content
  4. Prevent default event handling
// Compulsory replication
document.addEventListener(
  'copy',
  event => {
    event.clipboardData.setData(
      'text/plain',
      document.getSelection().toString(),
    )
    // Block default event handling
    event.preventDefault()
  },
  true,
)

Implement forced cutting

thinking

  1. Bubble listening to cut events
  2. Get the currently selected content
  3. Set up cut content
  4. If it is editable, delete the selected part.
  5. Prevent default event handling

You can see that the only thing that needs to be added is that you need to deal with the editable content. However, the amount of code exploded in a flash.

/**
 * String safe conversion to lowercase
 * @param {String} str Character string
 * @returns {String} All lowercase string after conversion
 */
function toLowerCase(str) {
  if (!str || typeof str !== 'string') {
    return str
  }
  return str.toLowerCase()
}

/**
 * Determine whether the specified element is an editable element
 * Note: editable elements are not necessarily editable, such as read-only input elements
 * @param {Element} el Elements to be judged
 * @returns {Boolean} Editable element or not
 */
function isEditable(el) {
  var inputEls = ['input', 'date', 'datetime', 'select', 'textarea']
  return (
    el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName)))
  )
}

/**
 * Get the cursor position in the input box
 * @param  {Element} el Input box elements to get
 * @returns {Number} Subscript of cursor position
 */
function getCusorPostion(el) {
  return el.selectionStart
}

/**
 * Set the position of the selected text / cursor in the input box
 * @param {Element} el Input box elements to set
 * @param {Number} start Subscript of cursor position
 * @param {Number} {end} End position, default to end of input box
 */
function setCusorPostion(el, start, end = start) {
  el.focus()
  el.setSelectionRange(start, end)
}

/**
 * Delete text within the specified range
 * @param {Element} el Input box elements to set
 * @param {Number} {start} Start position, default to the currently selected start position
 * @param {Number} {end} End position, default to the currently selected end position
 */
function removeText(el, start = el.selectionStart, end = el.selectionEnd) {
  // You must [remember] the position of the current cursor before deleting
  var index = getCusorPostion(el)
  var value = el.value
  el.value = value.substr(0, start) + value.substr(end, value.length)
  setCusorPostion(el, index)
}

// Forced shear
document.addEventListener(
  'cut',
  event => {
    event.clipboardData.setData(
      'text/plain',
      document.getSelection().toString(),
    )
    // If it's an editable element, delete it.
    if (isEditable(event.target)) {
      removeText(event.target)
    }
    event.preventDefault()
  },
  true,
)

Implement forced paste

  1. Bubble listens for focus/blur to get the last editable element to get focus
  2. Bubble monitoring paste event
  3. Get cut content
  4. Get the last editable element to get focus
  5. Delete the currently selected text
  6. Insert text at current cursor
  7. Prevent default event handling
/**
 * Get to the last element in focus
 */
var getLastFocus = (lastFocusEl => {
  document.addEventListener(
    'focus',
    event => {
      lastFocusEl = event.target
    },
    true,
  )
  document.addEventListener(
    'blur',
    event => {
      lastFocusEl = null
    },
    true,
  )
  return () => lastFocusEl
})(null)

/**
 * String safe conversion to lowercase
 * @param {String} str Character string
 * @returns {String} All lowercase string after conversion
 */
function toLowerCase(str) {
  if (!str || typeof str !== 'string') {
    return str
  }
  return str.toLowerCase()
}

/**
 * Determine whether the specified element is an editable element
 * Note: editable elements are not necessarily editable, such as read-only input elements
 * @param {Element} el Elements to be judged
 * @returns {Boolean} Editable element or not
 */
function isEditable(el) {
  var inputEls = ['input', 'date', 'datetime', 'select', 'textarea']
  return (
    el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName)))
  )
}

/**
 * Get the cursor position in the input box
 * @param  {Element} el Input box elements to be retrieved
 * @returns {Number} Subscript of cursor position
 */
function getCusorPostion(el) {
  return el.selectionStart
}

/**
 * Set the position of the selected text / cursor in the input box
 * @param {Element} el Input box elements to set
 * @param {Number} start Subscript of cursor position
 * @param {Number} {end} End position, default to end of input box
 */
function setCusorPostion(el, start, end = start) {
  el.focus()
  el.setSelectionRange(start, end)
}

/**
 * Insert text after specified location
 * @param {Element} el Input box elements to set
 * @param {String} value Value to insert
 * @param {Number} {start} Start position, defaults to the current cursor position
 */
function insertText(el, text, start = getCusorPostion(el)) {
  var value = el.value
  el.value = value.substr(0, start) + text + value.substr(start)
  setCusorPostion(el, start + text.length)
}

/**
 * Delete text within the specified range
 * @param {Element} el Input box elements to set
 * @param {Number} {start} Start position, default to the currently selected start position
 * @param {Number} {end} End position, default to the currently selected end position
 */
function removeText(el, start = el.selectionStart, end = el.selectionEnd) {
  // You must [remember] the position of the current cursor before deleting
  var index = getCusorPostion(el)
  var value = el.value
  el.value = value.substr(0, start) + value.substr(end, value.length)
  setCusorPostion(el, index)
}

// Mandatory paste
document.addEventListener(
  'paste',
  event => {
    // Get current clipboard content
    var clipboardData = event.clipboardData
    var items = clipboardData.items
    var item = items[0]
    if (item.kind !== 'string') {
      return
    }
    var text = clipboardData.getData(item.type)
    // Get current focus element
    // Can't get focus when pasting?
    var focusEl = getLastFocus()
    // Isn't input an editable element?
    if (isEditable(focusEl)) {
      removeText(focusEl)
      insertText(focusEl, text)
      event.preventDefault()
    }
  },
  true,
)

summary

Full text of script

;(function() {
  'use strict'

  /**
   * Two ideas:
   * 1. Self realization
   * 2. Replacement elements
   */

  /**
   * Get to the last element in focus
   */
  var getLastFocus = (lastFocusEl => {
    document.addEventListener(
      'focus',
      event => {
        lastFocusEl = event.target
      },
      true,
    )
    document.addEventListener(
      'blur',
      event => {
        lastFocusEl = null
      },
      true,
    )
    return () => lastFocusEl
  })(null)

  /**
   * String safe conversion to lowercase
   * @param {String} str Character string
   * @returns {String} All lowercase string after conversion
   */
  function toLowerCase(str) {
    if (!str || typeof str !== 'string') {
      return str
    }
    return str.toLowerCase()
  }

  /**
   * String safe conversion to uppercase
   * @param {String} str Character string
   * @returns {String} All uppercase string after conversion
   */
  function toUpperCase(str) {
    if (!str || typeof str !== 'string') {
      return str
    }
    return str.toUpperCase()
  }

  /**
   * Determine whether the specified element is an editable element
   * Note: editable elements are not necessarily editable, such as read-only input elements
   * @param {Element} el Elements to be judged
   * @returns {Boolean} Editable element or not
   */
  function isEditable(el) {
    var inputEls = ['input', 'date', 'datetime', 'select', 'textarea']
    return (
      el && (el.isContentEditable || inputEls.includes(toLowerCase(el.tagName)))
    )
  }

  /**
   * Get the cursor position in the input box
   * @param  {Element} el Input box elements to get
   * @returns {Number} Subscript of cursor position
   */
  function getCusorPostion(el) {
    return el.selectionStart
  }

  /**
   * Set the position of the selected text / cursor in the input box
   * @param {Element} el Input box elements to set
   * @param {Number} start Subscription of cursor position
   * @param {Number} {end} End position, default to end of input box
   */
  function setCusorPostion(el, start, end = start) {
    el.focus()
    el.setSelectionRange(start, end)
  }

  /**
   * Insert text after the specified location
   * @param {Element} el Input box elements to set
   * @param {String} value Value to insert
   * @param {Number} {start} Start position, defaults to the current cursor position
   */
  function insertText(el, text, start = getCusorPostion(el)) {
    var value = el.value
    el.value = value.substr(0, start) + text + value.substr(start)
    setCusorPostion(el, start + text.length)
  }

  /**
   * Delete text within the specified range
   * @param {Element} el Input box elements to set
   * @param {Number} {start} Start position, default to the currently selected start position
   * @param {Number} {end} End position, default to the currently selected end position
   */
  function removeText(el, start = el.selectionStart, end = el.selectionEnd) {
    // You must [remember] the position of the current cursor before deleting
    var index = getCusorPostion(el)
    var value = el.value
    el.value = value.substr(0, start) + value.substr(end, value.length)
    setCusorPostion(el, index)
  }

  // Compulsory replication
  document.addEventListener(
    'copy',
    event => {
      event.clipboardData.setData(
        'text/plain',
        document.getSelection().toString(),
      )
      event.preventDefault()
    },
    true,
  )

  // Forced shear
  document.addEventListener(
    'cut',
    event => {
      event.clipboardData.setData(
        'text/plain',
        document.getSelection().toString(),
      )
      // If it's an editable element, delete it.
      if (isEditable(event.target)) {
        removeText(event.target)
      }
      event.preventDefault()
    },
    true,
  )

  // Mandatory paste
  document.addEventListener(
    'paste',
    event => {
      // Get current clipboard content
      var clipboardData = event.clipboardData
      var items = clipboardData.items
      var item = items[0]
      if (item.kind !== 'string') {
        return
      }
      var text = clipboardData.getData(item.type)
      // Get current focus element
      // Can't get focus when pasting?
      var focusEl = getLastFocus()
      // Isn't input an editable element?
      if (isEditable(focusEl)) {
        removeText(focusEl)
        insertText(focusEl, text)
        event.preventDefault()
      }
    },
    true,
  )

  function selection() {
    var dom
    document.onmousedown = event => {
      dom = event.target
      // console.log('click:, dom)
      debugger
      console.log('Where the cursor is: ', getCusorPostion(dom))
    }
    document.onmousemove = event => {
      console.log('move: ', dom)
    }
    document.onmouseup = event => {
      console.log('Release: ', dom)
    }
  }
})()

Re implement the addEventListener and delete the customized events of the website

The realization is inspired by https://greasyfork.org/en/scr... , which almost perfectly realizes the function of removing restrictions

The principle is very simple. Modify the prototype and re implement the addEventListener function of EventTarget and docuement.

// ==UserScript==
// @name remove web page restrictions
// @namespace    http://github.com/rxliuli
// @version      1.0
// @description crack the website with copy / cut / paste / select / right-click menu forbidden
// @author       rxliuli
// @include      https://www.jianshu.com/*
// @grant        GM.getValue
// @grant        GM.setValue
// The @ run at here is very important. It is set to load the script at the beginning of the document.
// @run-at       document-start
// ==/UserScript==

;(() => {
  /**
   * Listen to all addeventlistener and removeeventlistener events
   */
  var documentAddEventListener = document.addEventListener
  var eventTargetAddEventListener = EventTarget.prototype.addEventListener
  var documentRemoveEventListener = document.removeEventListener
  var eventTargetRemoveEventListener = EventTarget.prototype.removeEventListener
  var events = []

  /**
   * Used to save the monitored event information
   */
  class Event {
    constructor(el, type, listener, useCapture) {
      this.el = el
      this.type = type
      this.listener = listener
      this.useCapture = useCapture
    }
  }

  /**
   * Custom add event listener function
   * @param {String} type Event type
   * @param {EventListener} listener Event listening function
   * @param {Boolean} {useCapture} Whether to capture event bubbles, default to false
   */
  function addEventListener(type, listener, useCapture = false) {
    var _this = this
    var $addEventListener =
      _this === document
        ? documentAddEventListener
        : eventTargetAddEventListener
    events.push(new Event(_this, type, listener, useCapture))
    $addEventListener.apply(this, arguments)
  }

  /**
   * Custom delete event functions by type
   * This method will delete all listening functions of this type, regardless of the number
   * @param {String} type Event type
   */
  function removeEventListenerByType(type) {
    var _this = this
    var $removeEventListener =
      _this === document
        ? documentRemoveEventListener
        : eventTargetRemoveEventListener
    var removeIndexs = events
      .map((e, i) => (e.el === _this || e.type === arguments[0] ? i : -1))
      .filter(i => i !== -1)
    removeIndexs.forEach(i => {
      var e = events[i]
      $removeEventListener.apply(e.el, [e.type, e.listener, e.useCapture])
    })
    removeIndexs.sort((a, b) => b - a).forEach(i => events.splice(i, 1))
  }

  function clearEvent() {
    var eventTypes = [
      'copy',
      'cut',
      'select',
      'contextmenu',
      'selectstart',
      'dragstart',
    ]
    document.querySelectorAll('*').forEach(el => {
      eventTypes.forEach(type => el.removeEventListenerByType(type))
    })
  }

  ;(function() {
    document.addEventListener = EventTarget.prototype.addEventListener = addEventListener
    document.removeEventListenerByType = EventTarget.prototype.removeEventListenerByType = removeEventListenerByType
  })()

  window.onload = function() {
    clearEvent()
  }
})()

Finally, there are many JavaScript hook skills. It's true that writing Greasemonkey scripts is a lot used (๑ > ᴗ < ๑)

Posted by agulaid on Tue, 15 Oct 2019 22:11:06 -0700