Skip to content

Instantly share code, notes, and snippets.

@rynomad
Last active April 11, 2023 19:21
Show Gist options
  • Save rynomad/c2ceac925a9572bb3924b943968529d2 to your computer and use it in GitHub Desktop.
Save rynomad/c2ceac925a9572bb3924b943968529d2 to your computer and use it in GitHub Desktop.
A ViolentMonkey userscript using BrokerClient
// ==UserScript==
// @name My Userscript
// @namespace http://example.com/
// @version 1.0
// @description A testrunner for VM userscripts
// @include *
// @grant GM.addStyle
// @grant GM.deleteValue
// @grant GM.getValue
// @grant GM.listValues
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM.notification
// @grant GM.openInTab
// @grant GM.getResourceUrl
// @grant GM.setClipboard
// @grant GM.info
// @grant GM.registerMenuCommand
// @grant GM.unregisterMenuCommand
// @grant GM.download
// @grant GM.getTab
// @grant GM.saveTab
// @grant GM.getTabs
// @grant GM.connect
// @grant GM.setIcon
// @grant GM.fetch
// @grant GM.communicator
// @inject-into content
// ==/UserScript==
(function() {
'use strict';
function isUserscript(code) {
const regex = /\/\/\s?==UserScript==[\s\S]*\/\/\s?==\/UserScript==/;
return regex.test(code);
}
function getIncludePatterns(code) {
const regex = /\/\/\s?@(include|match)\s+(.*)/g;
const patterns = [];
let match;
while ((match = regex.exec(code)) !== null) {
patterns.push(match[2]);
}
return patterns;
}
function isMatchPattern(url, pattern) {
if (pattern.includes('*')) {
// Handle @include patterns with wildcards
const regex = new RegExp(
'^' +
pattern
.replace(/[-[\]{}()+!<=:?.\\^$|#\s,]/g, '\\$&')
.replace(/\*/g, '.*')
.replace(/\?/g, '.') +
'$'
);
return regex.test(url);
} else {
// Handle @match patterns
const matchPatternToRegExp = (pattern) => {
const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(
'^' +
escapeRegExp(pattern)
.replace(/\\\*/g, '.*')
.replace(/\\\?/g, '.') +
'$'
);
};
return matchPatternToRegExp(pattern).test(url);
}
}
function createIcon(iconType, onClick) {
const icon = document.createElement('div');
icon.textContent = iconType === 'smiley' ? '😊' : '⚠️';
icon.style.position = 'fixed';
icon.style.top = '10px';
icon.style.right = iconType === 'smiley' ? '50px' : '10px';
icon.style.fontSize = '24px';
icon.style.cursor = 'pointer';
icon.style.zIndex = '99999';
icon.addEventListener('click', onClick);
return icon;
}
function handleTestScript(code, sendResponse) {
console.log(code, location.href)
if (!isUserscript(code)) {
console.log('ignoring non-userscript', code)
return;
}
const includePatterns = getIncludePatterns(code);
if (!includePatterns.some((pattern) => isMatchPattern(location.href, pattern))) {
console.log('doesnt match page')
return;
}
try {
const executeScript = new Function('GM', code);
executeScript(GM);
const smileyIcon = createIcon('smiley', () => {
smileyIcon.remove();
alertIcon.remove();
sendResponse({
success: true,
message: 'Script executed successfully.',
});
});
const alertIcon = createIcon('alert', () => {
smileyIcon.remove();
alertIcon.remove();
const userFeedback = prompt('Please describe the problem:');
sendResponse({
success: false,
message: userFeedback,
});
});
document.body.appendChild(smileyIcon);
document.body.appendChild(alertIcon);
} catch (error) {
console.log(error, code)
sendResponse({
success: false,
message: error.message,
stackTrace: error.stack,
});
}
}
const brokerClient = GM.communicator();
brokerClient.registerHandler('TEST_SCRIPT', handleTestScript);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment