I found the solution to use both language reactor and asbplayer very useful. I also wrote a simple tempermonkey addon to automatically copy and paste definition in language reactor’s dictionary to asbplayer’s anki exporter. I will post it here in case anyone is interested or want to improve it.
- This script works on both local videos and netflix. I have not tuned it for youtube yet. First you need to make sure both asbplayer and language reactor are installed and work smoothly. Notice that asbplayer and language reactor have conflicts in hotkeys like arrow keys, and make sure you adjust them.
- For local videos to work, you need to allow asbplayer to access local files (right click → manage extensions → Allow access to file URLs). Then, play local videos on Language Reactor and select the same subtitle for asbplayer.
- Set up anki connect as guided by asbplayer.
- Install violentmonkey (or tempermonkey etc) and create a new script, adding the following content.
- If the script runs correctly, when you press ctrl+shift+x (the hotkey for asbplayer), an additional button named “export” should appear. It will try to detect if you are currently looking up a word using language reactor’s mini dictionary. If yes, clicking the button automatically fills in the word and the definition to the anki exporter.
// ==UserScript==
// @name Language Learning Plugin
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Combine Language Reactor and absplayer
// @include https://www.languagereactor.com/*
// @include https://www.netflix.com/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const observeIframe = () => {
const iframeObserver = new MutationObserver(() => {
const iframe = document.querySelector('iframe.asbplayer-ui-frame:not(.asbplayer-hide)');
if (iframe && iframe.contentDocument) {
injectScriptIntoIframe(iframe);
}
});
iframeObserver.observe(document.body, { childList: true, subtree: true });
};
const getWordFromDictionary = () => {
const dictContainer = document.querySelector('.lln-full-dict');
if (!dictContainer) return null;
var wordElement = dictContainer.querySelector('div > div > div[style*="color: rgb(189, 189, 0)"]');
if (!wordElement) {
wordElement = document.querySelector('.lln-dict-section-full > div > div')
}
return wordElement ? wordElement.textContent.trim() : null;
};
const getDefinitionFromDictionary = () => {
const dictContainer = document.querySelector('.lln-full-dict');
if (!dictContainer) return null;
var wordElement = dictContainer.querySelector('div > div > div[style*="color: rgb(189, 189, 0)"]');
if (!wordElement) {
wordElement = document.querySelector('.lln-dict-section-full > div > div')
}
if (!wordElement) return null;
const outerDiv = wordElement.parentElement; // Navigate to the correct parent
if (!outerDiv) return null;
var pronDir = dictContainer.querySelector('span > span[style*="margin-left: 5px;"]');
if (!pronDir) {
pronDir = document.querySelector('.lln-dict-contextual > span[style*="color: rgb(189, 189, 0)"]')
}
if (!pronDir) return null;
// Collect all text from child divs, preserving line breaks
let definition = pronDir.textContent.trim().split(' ').pop() + '\n';
outerDiv.childNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const texts = node.textContent.trim().split('\n');
texts.forEach(
text => {
if (text.trim() != wordElement.textContent.trim()) definition += text.trim() + '\n';
}
)
}
});
return definition.trim();
};
const injectScriptIntoIframe = (iframe) => {
const iframeDoc = iframe.contentDocument;
const waitForBody = () => {
if (!iframeDoc.body) {
console.warn('iframe body not ready, retrying...');
setTimeout(waitForBody, 100); // Retry after 100ms
return;
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
iframeDoc.querySelectorAll('.MuiDialogActions-root').forEach(dialog => {
if (!dialog.querySelector('.extract-button')) {
const extractButton = document.createElement('button');
extractButton.className = 'MuiButtonBase-root MuiButton-root MuiButton-text extract-button';
extractButton.type = 'button';
extractButton.innerHTML = '<span class="MuiButton-label">Extract</span>';
extractButton.onclick = () => {
const wordInput = iframeDoc.querySelector('input.MuiInputBase-input.MuiFilledInput-input');
const definitionInput = iframeDoc.querySelector('textarea.MuiInputBase-input.MuiFilledInput-input');
const word = getWordFromDictionary();
const definition = getDefinitionFromDictionary();
if (wordInput && word) {
setReactValue(wordInput, word);
}
if (definitionInput && definition) {
setReactValue(definitionInput, definition);
}
console.log('Extract button clicked: autofill complete');
};
dialog.appendChild(extractButton);
// Automatically click the Extract button when the dialog opens
extractButton.click();
}
});
}
});
});
observer.observe(iframeDoc.body, { childList: true, subtree: true });
};
waitForBody();
};
const setReactValue = (element, value) => {
const descriptor = Object.getOwnPropertyDescriptor(element.__proto__, 'value');
const event = new Event('input', { bubbles: true });
descriptor.set.call(element, value); // Set the value using the descriptor
element.dispatchEvent(event); // Trigger React's input event listener
};
window.addEventListener('load', observeIframe);
})();