Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 209 additions & 65 deletions background/background.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,230 @@
const GOOGLE_SPEECH_URI = 'https://www.google.com/speech-api/v1/synthesize',

DEFAULT_HISTORY_SETTING = {
enabled: true
};
const DEFAULT_HISTORY_SETTING = { enabled: true };

browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
const { word, lang } = request,
url = `https://www.google.com/search?hl=${lang}&q=define+${word}&gl=US`;

fetch(url, {
method: 'GET'
})
.then((response) => response.text())
.then((text) => {
const document = new DOMParser().parseFromString(text, 'text/html'),
content = extractMeaning(document, { word, lang });

lookupWord(request.word, request.lang)
.then((content) => {
sendResponse({ content });

content && browser.storage.local.get().then((results) => {
let history = results.history || DEFAULT_HISTORY_SETTING;

history.enabled && saveWord(content)
});
if (content) {
browser.storage.local.get().then((results) => {
const history = results.history || DEFAULT_HISTORY_SETTING;
if (history.enabled) {
saveWord(content);
}
});
}
})
.catch((error) => {
console.error("Dictionary lookup failed:", error);
sendResponse({ content: null });
});

return true;
});

function extractMeaning (document, context) {
if (!document.querySelector("[data-dobid='hdw']")) { return null; }

var word = document.querySelector("[data-dobid='hdw']").textContent,
definitionDiv = document.querySelector("div[data-dobid='dfn']"),
meaning = "";

if (definitionDiv) {
definitionDiv.querySelectorAll("span").forEach(function(span){
if(!span.querySelector("sup"))
meaning = meaning + span.textContent;
async function lookupWord(rawWord, language) {
const word = (rawWord || "").trim();
if (!word) {
return null;
}

if (language === "de") {
return lookupGerman(word);
}
if (language === "fr") {
return lookupFrench(word);
}
if (language === "es") {
return lookupSpanish(word);
}

return lookupEnglish(word);
}

async function lookupEnglish(word) {
const url = `https://en.wiktionary.org/api/rest_v1/page/definition/${encodeURIComponent(word)}`;
const response = await fetch(url);
if (!response.ok) {
return null;
}

const result = await response.json();
const entries = result.en;
if (!entries || !entries.length) {
return null;
}

const definitions = [];
entries.forEach((entry) => {
(entry.definitions || []).forEach((item) => {
if (item.definition && definitions.length < 3) {
definitions.push(htmlToText(item.definition));
}
});
});

return createContent(word, definitions);
}

async function lookupGerman(word) {
const parsed = await fetchWiktionaryPage("de", word);
if (!parsed) {
return null;
}

meaning = meaning[0].toUpperCase() + meaning.substring(1);
const heading = Array.from(parsed.querySelectorAll("p")).find((node) => {
return node.textContent.trim().replace(/:$/, "") === "Bedeutungen";
});
const definitionList = heading && heading.nextElementSibling;

if (!definitionList || definitionList.tagName !== "DL") {
return null;
}

var audio = document.querySelector("audio[jsname='QInZvb']"),
source = document.querySelector("audio[jsname='QInZvb'] source"),
audioSrc = source && source.getAttribute('src');
const definitions = Array.from(definitionList.querySelectorAll(":scope > dd"))
.slice(0, 3)
.map(cleanDefinitionNode)
.filter(Boolean);

if (audioSrc) {
!audioSrc.includes("http") && (audioSrc = audioSrc.replace("//", "https://"));
return createContent(word, definitions);
}

async function lookupFrench(word) {
const parsed = await fetchWiktionaryPage("fr", word);
if (!parsed) {
return null;
}
else if (audio) {
let exactWord = word.replace(/·/g, ''), // We do not want syllable seperator to be present.

queryString = new URLSearchParams({
text: exactWord,
enc: 'mpeg',
lang: context.lang,
speed: '0.4',
client: 'lr-language-tts',
use_google_only_voices: 1
}).toString();

audioSrc = `${GOOGLE_SPEECH_URI}?${queryString}`;
const section = getLanguageSection(parsed, "fr");
const definitionList = findFirstInSection(section, "ol:not(.references)");
if (!definitionList) {
return null;
}

return { word: word, meaning: meaning, audioSrc: audioSrc };
};
const definitions = Array.from(definitionList.children)
.filter((node) => node.tagName === "LI")
.slice(0, 3)
.map(cleanDefinitionNode)
.filter(Boolean);

function saveWord (content) {
let word = content.word,
meaning = content.meaning,

storageItem = browser.storage.local.get('definitions');
return createContent(word, definitions);
}

storageItem.then((results) => {
let definitions = results.definitions || {};
async function lookupSpanish(word) {
const parsed = await fetchWiktionaryPage("es", word);
if (!parsed) {
return null;
}

definitions[word] = meaning;
browser.storage.local.set({
definitions
});
})
}
const section = getLanguageSection(parsed, "es");
const definitions = [];

section.some((element) => {
const lists = [];
if (element.matches && element.matches("dl")) {
lists.push(element);
}
lists.push(...element.querySelectorAll("dl"));

lists.forEach((list) => {
const definition = list.querySelector(":scope > dd");
if (definition && definitions.length < 3) {
definitions.push(cleanDefinitionNode(definition));
}
});

return definitions.length >= 3;
});

return createContent(word, definitions.filter(Boolean));
}

async function fetchWiktionaryPage(language, word) {
const params = new URLSearchParams({
action: "parse",
page: word,
prop: "text",
format: "json",
origin: "*"
});
const url = `https://${language}.wiktionary.org/w/api.php?${params.toString()}`;
const response = await fetch(url);

if (!response.ok) {
return null;
}

const result = await response.json();
if (!result.parse || !result.parse.text) {
return null;
}

return new DOMParser().parseFromString(result.parse.text["*"], "text/html");
}

function getLanguageSection(parsed, languageId) {
const marker = parsed.getElementById(languageId);
const heading = marker && marker.closest("h2");
const headingContainer = heading && heading.parentElement;
const elements = [];

if (!headingContainer) {
return elements;
}

let current = headingContainer.nextElementSibling;
while (current && !current.classList.contains("mw-heading2")) {
elements.push(current);
current = current.nextElementSibling;
}

return elements;
}

function findFirstInSection(section, selector) {
for (const element of section) {
if (element.matches && element.matches(selector)) {
return element;
}

const match = element.querySelector(selector);
if (match) {
return match;
}
}

return null;
}

function cleanDefinitionNode(node) {
const copy = node.cloneNode(true);
copy.querySelectorAll("ul, ol, dl, sup, .example, .sources").forEach((child) => {
child.remove();
});
return copy.textContent.replace(/^\s*\[?\d+[\],.\s]*/, "").replace(/\s+/g, " ").trim();
}

function createContent(word, definitions) {
if (!definitions || !definitions.length) {
return null;
}

return {
word,
meaning: definitions.join("; "),
audioSrc: null
};
}

function htmlToText(html) {
const parsed = new DOMParser().parseFromString(html, "text/html");
return parsed.body.textContent.replace(/\s+/g, " ").trim();
}

function saveWord(content) {
browser.storage.local.get("definitions").then((results) => {
const definitions = results.definitions || {};
definitions[content.word] = content.meaning;
browser.storage.local.set({ definitions });
});
}
17 changes: 15 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@

"manifest_version": 2,
"name": "Dictionary Anywhere",
"version": "1.2.0",
"version": "1.3.0",

"description": "View definitions easily as you browse the web. Double-click any word to view its definition in a small pop-up bubble.",

"browser_specific_settings": {
"gecko": {
"id": "dictionary-anywhere-addon@addons.mozilla.org",
"data_collection_permissions": {
"required": ["none"]
}
}
},

"icons": {
"48": "icons/Dictionary-48.png",
"64": "icons/Dictionary-64.png",
Expand Down Expand Up @@ -40,6 +49,10 @@

"permissions": [
"storage",
"https://www.google.com/"
"https://www.google.com/",
"https://en.wiktionary.org/*",
"https://de.wiktionary.org/*",
"https://fr.wiktionary.org/*",
"https://es.wiktionary.org/*"
]
}