tentative ankiconnect support

This commit is contained in:
wareya committed 2018-11-01 03:18:36 -04:00
1 parent 3f0459e155
commit 5b3ae7f2e9
7 files changed
+486 -12

No files matched your search

+14
View File
@@ -1731,6 +1731,20 @@ browser.runtime.onMessage.addListener((req, sender, sendResponse) =>
{ {
refresh_epwing(); refresh_epwing();
} }
else if (req.type == "ankiconnect_mine")
{
console.log(req.command);
let xhr = new XMLHttpRequest();
xhr.open("POST", req.host, true);
//xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
xhr.addEventListener('load', () =>
{
console.log(xhr.responseText);
let response = JSON.parse(xhr.responseText);
console.log(response);
});
xhr.send(req.command);
}
else else
{ {
return; return;
+87
View File
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Nazeka: Configuration for Live Mining</title>
<script type="text/javascript" src="livemining.js"></script>
</head>
<body>
<h2>Configuration for Live Mining</h2>
<p>Live Mining requires <a href="https://apps.ankiweb.net/">Anki</a> and the <a href="https://ankiweb.net/shared/info/2055492159">Ankiconnect</a> plugin.</p>
<p>If Live Mining is enabled, then when you mine notes to the mining storage, Nazeka will also attempt to connect to Ankiconnect and tell it to insert a new note.</p>
<p><b>Nazeka will NOT show any error indications</b> if Ankiconnect fails to insert the new note (such as <b>if Anki or Ankiconnect isn't running</b>, or the card model/template changed since configuration).</p>
<h2>Deck and Model</h2>
<input type="text" value="Default" id="deckname"><br>
<label for="deckname">Deck name. Must be exact.</label><br>
<input type="text" value="Basic" id="modelname"><br>
<label for="modelname">Card model/template name. Must be exact.</label><br>
<h2>Fields</h2>
<input type="button" id="autofields" value="Populate"><br>
<label for="autofields">Button will connect to Ankiconnect and automatically populate a list of field inputs based on card model/template name, overwriting EVERYTHING that is currently here.</label><br>
<div id="containfields">
(Unpopulated)
</div>
<input type="button" id="applyfields" value="Apply">
<input type="button" id="loadfields" value="Reset">
<h2>Fields Help</h2>
<p>Behavior notes:</p>
<ul>
<li>The "found spelling" is the spelling you clicked on to mine the word. You can only mine by clicking on the main highlighted spelling.</li>
<li>If you moused over non-kana, the "found spelling" is the matching non-kana spelling, and only the readings compatible with the "found spelling" are listed.</li>
<li>if you moused over kana, the "found spelling" is JMDict's favorite spelling, and all readings are listed (this part may change in the future).</li>
<li>If you moused over kana but there was no non-kana spelling, the reading from which the word was found is moved into the "found spelling" slot, <b>and removed from the base list of readings</b>.</li>
<li>The readings are filtered to be limited to the ones compatible with the "found spelling".</li>
<li>If you have a JSON dictionary imported, its definition will be appended to the JMDict definition.</li>
<li>Sentence detection is very lazy and will have weird cutoffs in certain situations.</li>
<li>Not all words have audio.</li>
</ul>
<p>About formatting identities:</p>
<p style="white-space:pre-line;font-family: monospace"
>nothing - Nothing.
found_spelling - The spelling you clicked on to mine the word (e.g. "行く") (this is always the "found spelling" described above)
readings - Readings (this is the list of readings you see in the mining UI when mining the word)
definitions - Definition (HTML; has spans with class names, newlines are &lt;br&gt; tags)
definitions_raw - Definition (plaintext, linebreaks are LF characters; only for templates with special CSS or JS)
found_text - Text the word was found as (e.g. "行きます"). Also known as "found text".
context - The sentence you found the word in. Sentence boundaries are a guess, so it might be cut off funny and need manual editing inside of anki.
context_left - The left half of the sentence you found the word in, until the found text.
context_right - The right half of the sentence you found the word in, after the found text.
audio_anki - anki audio tag (e.g. "[sound:nazeka_audio_ねにもつ_根に持つ.mp3]"), downloaded by Nazeka, sent to anki with Ankiconnect's storeMediaFile command; added even if the storeMediaFile command fails; not all words have audio, may be blank
audio_kanji - spelling portion of audio file's name, does no downloading of files. if there are no spellings, it's identical to audio_reading. may be blank.
audio_reading - reading portion of audio file's name, does no downloading of files. may be blank.
If you use the "audio" field, then the audio file is downloaded from jpod101's servers when you mine the card, and sent to Anki's media folder with Ankiconnect's storeMediaFile action. This only works when you're connected to the internet.
The "audio_kanji" and "audio_reading" fields are for people with the JDIC audio pack, or recovering the audio manually if you mined while your internet was down, or for people who want to play the audio file from jpod101's servers directly so they don't need to store it as a media file. (Not sure whether Anki currently supports that, but it should be possible in the future.)
jmdict - JMDict numeric ID
url - URL of the page the card was mined from (if it was the reader page, it will look like jibberish)
time_unix - Time when card was mined (unix timestamp, milliseconds, can be converted to a localized date within Anki by using javascript in the card model/template)
time_local - Time when card was mined (localized date, depends on browser and OS)
readings_raw - The unfiltered list of readings associated with the given word. Restriction information, like さくや not being a reading of 夕べ, is stripped.
spellings_raw - The unfiltered list of spellings associated with the given word. Restriction information is stripped.
</p>
<h2>Advanced</h2>
<input type="text" value="http://localhost:8765/" id="host"><br>
<label for="host">Ankiconnect host (default: http://localhost:8765/) (You probably don't want to change this!)</label><br>
</body>
</html>
+151
View File
@@ -0,0 +1,151 @@
'use strict';
function reset_fields()
{
document.getElementById("containfields").innerHTML = "";
}
function add_option(input, text, selected=false)
{
let option = document.createElement("option");
option.value = text;
option.innerText = text;
option.selected = selected;
input.appendChild(option);
}
function add_options(input, preselect="")
{
let list = [
"nothing",
"found_spelling",
"readings",
"definitions",
"definitions_raw",
"found_text",
"context",
"context_left",
"context_right",
"audio_anki",
"audio_kanji",
"audio_reading",
"jmdict_id",
"url",
"time_unix",
"time_local",
"readings_raw",
"spellings_raw",
];
for (let ident of list)
{
add_option(input, ident, (ident == preselect));
}
}
function add_field(fieldname, preselect="")
{
let div = document.createElement("div");
let input = document.createElement("select");
input.id = fieldname;
add_options(input, preselect);
let label = document.createElement("label");
label.for = fieldname;
label.innerText = fieldname;
div.appendChild(input);
div.appendChild(label);
document.getElementById("containfields").appendChild(div);
}
function saveConfig()
{
let mappings = new Array();
for(let element of document.querySelectorAll("#containfields > div"))
{
console.log(element);
mappings.push([element.querySelector("label").innerText, element.querySelector("select").value]);
}
console.log(mappings);
let setstuff =
{
"livemining_deckname": document.querySelector("#deckname").value,
"livemining_modelname": document.querySelector("#modelname").value,
"livemining_fields": mappings,
"livemining_host": document.querySelector("#host").value
};
console.log(setstuff);
browser.storage.local.set(setstuff);
}
function loadConfig()
{
let promise = browser.storage.local.get(["livemining_deckname", "livemining_modelname", "livemining_fields", "livemining_host"]);
promise.then((storage) =>
{
if(storage["livemining_deckname"] !== undefined)
document.querySelector("#deckname").value = storage["livemining_deckname"];
if(storage["livemining_modelname"] !== undefined)
document.querySelector("#modelname").value = storage["livemining_modelname"];
if(storage["livemining_host"] !== undefined)
document.querySelector("#host").value = storage["livemining_host"];
reset_fields();
for(let pair of storage["livemining_fields"])
add_field(pair[0], pair[1]);
}, (e) => {console.log(e);});
}
function buildpage()
{
document.getElementById("autofields").onclick = function(e)
{
let host = document.getElementById("host").value;
let request = JSON.parse(`
{"action":"modelFieldNames",
"version":6,
"params": {"modelName":"Basic"}
}`);
request["params"]["modelName"] = document.getElementById("modelname").value;
let json = JSON.stringify(request);
console.log(json);
let xhr = new XMLHttpRequest();
xhr.open("POST", host, true);
//xhr.setRequestHeader('Content-type','application/json; charset=utf-8');
xhr.addEventListener('load', () =>
{
let response = JSON.parse(xhr.responseText);
if (response["result"])
{
reset_fields();
for(let fieldname of response["result"])
add_field(fieldname);
}
});
xhr.send(json);
e.preventDefault();
};
document.getElementById("applyfields").onclick = saveConfig;
loadConfig();
document.getElementById("loadfields").onclick = loadConfig;
}
if (document.readyState == "complete")
{
buildpage();
}
else
{
document.addEventListener("DOMContentLoaded", buildpage);
}
+4 -1
View File
@@ -17,7 +17,8 @@
"contextMenus", "contextMenus",
"storage", "storage",
"tabs", "tabs",
"clipboardRead" "clipboardRead",
"https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?*"
], ],
"background": { "background": {
@@ -44,6 +45,8 @@
"reader.html", "reader.html",
"mining.js", "mining.js",
"mining.html", "mining.html",
"livemining.js",
"livemining.html",
"jmdictabbreviations.html" "jmdictabbreviations.html"
], ],
+5 -1
View File
@@ -17,11 +17,15 @@
</div> </div>
</form> </form>
<div>
<p><button id="livemininglink">Configure live mining</button></p>
</div>
<div class=dictstuff> <div class=dictstuff>
<h2>About</h2> <h2>About</h2>
<p>This extension uses JMdict, which is Copyright © 2018 The Electronic Dictionary Research and Development Group and licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0</a> license.</p> <p>This extension uses JMdict, which is Copyright © 2018 The Electronic Dictionary Research and Development Group and licensed under the <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0</a> license.</p>
<p>This extension is otherwise licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, version 2.0</a>.</p> <p>This extension is otherwise licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License, version 2.0</a>.</p>
<p><a id="abbrlink" target="_blank" href="#" >View JMDict abbreviation definitions</a></p> <p><button id="abbrlink">View JMDict abbreviation definitions</button></p>
</div> </div>
</div> </div>
</body> </body>
+12 -1
View File
@@ -80,6 +80,12 @@ function defaults()
default: false, default: false,
label: "Ignore hard line breaks" label: "Ignore hard line breaks"
}); });
settings.push({
id: "live_mining",
kind: "checkbox",
default: false,
label: "Enable live mining (configured via the button below under \"Other\")"
});
// display // display
settings.push({ settings.push({
@@ -161,7 +167,7 @@ function defaults()
kind: "combobox", kind: "combobox",
options: ["Flip", "Push"], options: ["Flip", "Push"],
default: 0, default: 0,
label: "How to keep the popup on the screen vertically (at least one of these two options should be \"Push\")" label: "How to keep the popup on the screen vertically (at least one of these two options should be \"Flip\")"
}); });
// theme // theme
@@ -430,6 +436,11 @@ function buildpage()
optionsection.appendChild(container); optionsection.appendChild(container);
} }
let livemininglink = document.getElementById("livemininglink");
livemininglink.onclick = function(e){browser.windows.create({url:browser.extension.getURL('livemining.html'),type:'popup'}); e.preventDefault();};
optionsection.appendChild(document.createElement("hr"));
let file = document.createElement("input"); let file = document.createElement("input");
file.type = "file"; file.type = "file";
file.id = "epwing_file"; file.id = "epwing_file";
+213 -9
View File
@@ -55,6 +55,7 @@ hotkey_audio: "p",
hotkey_nudge_left: "ArrowLeft", hotkey_nudge_left: "ArrowLeft",
hotkey_nudge_right: "ArrowRight", hotkey_nudge_right: "ArrowRight",
volume: 0.2, volume: 0.2,
live_mining: false
}; };
let platform = "win"; let platform = "win";
@@ -526,6 +527,7 @@ function build_div_inner(text, result, moreText, index, first_of_many = false)
else else
continue; // shouldn't happen, but again, just in case of broken data continue; // shouldn't happen, but again, just in case of broken data
if(term.has_audio.length > 0) if(term.has_audio.length > 0)
{ {
for(let audio of term.has_audio) for(let audio of term.has_audio)
@@ -538,6 +540,29 @@ function build_div_inner(text, result, moreText, index, first_of_many = false)
} }
} }
// for mining
let reading_data_holder = document.createElement("div");
reading_data_holder.className = "nazeka_mining_readings";
reading_data_holder.style.display = "none";
let reading_data_list = new Array();
for(let r of term.r_ele)
reading_data_list.push(r.reb);
reading_data_holder.innerText = reading_data_list.join("、");
container.appendChild(reading_data_holder);
let spelling_data_holder = document.createElement("div");
spelling_data_holder.className = "nazeka_mining_spellings";
spelling_data_holder.style.display = "none";
let spelling_data_list = new Array();
if(term.k_ele)
for(let k of term.k_ele)
spelling_data_list.push(k.keb);
spelling_data_holder.innerText = spelling_data_list.join("、");
container.appendChild(spelling_data_holder);
// end of "for mining"
let temptag = document.createElement("span"); let temptag = document.createElement("span");
temptag.className = "nazeka_word"; temptag.className = "nazeka_word";
let original_kana = ""; let original_kana = "";
@@ -995,6 +1020,7 @@ async function settings_init()
getvar("hotkey_nudge_left", "ArrowLeft"); getvar("hotkey_nudge_left", "ArrowLeft");
getvar("hotkey_nudge_right", "ArrowRight"); getvar("hotkey_nudge_right", "ArrowRight");
getvar("volume", 0.2); getvar("volume", 0.2);
getvar("live_mining", false);
if(!settings.enabled && exists_div()) if(!settings.enabled && exists_div())
delete_div(); delete_div();
@@ -1575,32 +1601,160 @@ async function mine_to_storage(object)
browser.storage.local.set({cards:cards}); browser.storage.local.set({cards:cards});
} }
async function try_to_play_audio(object) function get_audio_text(element) // [reading, spelling]
{ {
if(!exists_div()) let mydiv = element.querySelector(".nazeka_audioref");
return;
let mydiv = get_div().querySelector(".nazeka_audioref");
if(mydiv) if(mydiv)
{ {
let text = mydiv.innerText; let text = mydiv.innerText;
let url;
if(text.includes(";")) if(text.includes(";"))
{ {
let fields = text.split(";"); let fields = text.split(";");
url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + fields[0] + "&kanji=" + fields[1]; return [fields[0], fields[1]];
} }
else else
url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + text + "&kanji=" + text; return [text, text];
}
return undefined;
}
async function try_to_play_audio()
{
if(!exists_div())
return;
let fields = get_audio_text(get_div());
if(fields)
{
let url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + fields[0] + "&kanji=" + fields[1];
let audio = new Audio(url); let audio = new Audio(url);
audio.volume = settings.volume; audio.volume = settings.volume;
audio.play(); audio.play();
} }
} }
async function try_to_play_audio()
{
if(!exists_div())
return;
let fields = get_audio_text(get_div());
if(fields)
{
let url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + fields[0] + "&kanji=" + fields[1];
let audio = new Audio(url);
audio.volume = settings.volume;
audio.play();
}
}
function send_ankiconnect_command(host, command)
{
let json = JSON.stringify(command);
browser.runtime.sendMessage({type:"ankiconnect_mine", host:host, command:json});
}
async function mine_to_ankiconnect_with_base64_audio(object, audiofname, audiodata)
{
let object_keys = Object.keys(object);
let promise = browser.storage.local.get(["livemining_deckname", "livemining_modelname", "livemining_fields", "livemining_host"]);
promise.then((storage) =>
{
if(storage["livemining_host"] === undefined)
storage["livemining_host"] = "http://localhost:8765/";
if(storage["livemining_deckname"] === undefined)
storage["livemining_deckname"] = "Default";
if(storage["livemining_modelname"] === undefined)
storage["livemining_modelname"] = "Basic";
if(storage["livemining_fields"] === undefined)
storage["livemining_fields"] = [["Front", "found_spelling"], ["Back", "readings"]];
console.log("attempting to mine card");
let note = {
"action": "addNote",
"version": 6,
"params": {
"note": {
"deckName": storage["livemining_deckname"],
"modelName": storage["livemining_modelname"],
"fields": {},
"tags": [
"nazeka"
]
}
}
};
let audioadded = false;
let fields = {};
console.log(storage["livemining_fields"]);
console.log("starting loop");
for(let pair of storage["livemining_fields"])
{
console.log("start of iteration");
console.log(pair);
if(pair[1] != "audio_anki" && object_keys.includes(pair[1]))
fields[pair[0]] = String(object[pair[1]]);
if(pair[1] == "audio_anki")
{
if(audiofname != "" && audiodata != "")
{
if(!audioadded)
{
let mediafilecommand = {
"action": "storeMediaFile",
"version": 6,
"params": {
"filename": audiofname,
"data": audiodata
}
};
console.log("starting command");
send_ankiconnect_command(storage["livemining_host"], mediafilecommand);
audioadded = true;
}
// for some reason not even anki 2.0 supports this
//if(pair[1] == "audio_html")
// fields[pair[0]] = "<audio controls><source src=\"" + audiofname + "\"></audio>";
//else
fields[pair[0]] = "[sound:" + audiofname + "]";
}
else
fields[pair[0]] = "";
}
console.log("end of iteration");
}
console.log("ending loop");
note["params"]["note"]["fields"] = fields;
send_ankiconnect_command(storage["livemining_host"], note);
console.log("test");
}, (e) => {console.log(e);});
}
async function mine_to_ankiconnect(object)
{
console.log(object);
let audio_base64 = "";
if(object.audio !== undefined)
{
let url = "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kana=" + object.audio[0] + "&kanji=" + object.audio[1];
console.log(url);
let reader = new FileReader();
reader.onload = () =>
{
let fname = "nazeka_audio_" + object.audio[0] + "_" + object.audio[1] + ".mp3";
let result = reader.result.replace(/[^,]*,/, "");
mine_to_ankiconnect_with_base64_audio(object, fname, result);
};
fetch(url)
.then((response) => response.blob())
.then((myBlob) => reader.readAsDataURL(myBlob));
}
else
mine_to_ankiconnect_with_base64_audio(object, "", "");
}
function mine(highlight) function mine(highlight)
{ {
// TODO mine audio information
let front = highlight.textContent; let front = highlight.textContent;
let word = highlight.parentElement.parentElement; let word = highlight.parentElement.parentElement;
let readings = ""; let readings = "";
@@ -1614,6 +1768,56 @@ function mine(highlight)
let seq = word.getAttribute("nazeka_seq"); let seq = word.getAttribute("nazeka_seq");
mine_to_storage({front: front, readings: readings, definitions: definitions, lookup: lookup.textContent, sentence: sentence.textContent, index: index.textContent, seq: seq}); mine_to_storage({front: front, readings: readings, definitions: definitions, lookup: lookup.textContent, sentence: sentence.textContent, index: index.textContent, seq: seq});
if(settings.live_mining)
{
let now = new Date();
let readings_raw = word.querySelector(".nazeka_mining_readings").innerText;
let spellings_raw = word.querySelector(".nazeka_mining_spellings").innerText;
let audiostuff = get_audio_text(word);
let audio_kanji = "";
let audio_reading = "";
if(readings.length >= 2)
readings = readings.substring(1, readings.length-1);
console.log(audiostuff);
if(audiostuff !== undefined)
{
audio_kanji = audiostuff[1];
audio_reading = audiostuff[0];
}
mine_to_ankiconnect(
{
nothing:"",
found_spelling:front,
readings:readings,
definitions:definitions.innerHTML,
definitions_raw:definitions.innerText,
found_text: lookup.textContent,
context: sentence.textContent,
context_left: sentence.textContent.substring(0, index.textContent),
context_right: sentence.textContent.substring(parseInt(index.textContent) + lookup.textContent.length),
audio: audiostuff,
audio_kanji: audio_kanji,
audio_reading: audio_reading,
jmdict_id: seq,
url: get_doc().URL,
time_unix: now.getTime(),
time_local: now.toLocaleString(),
readings_raw: readings_raw,
spellings_raw: spellings_raw,
});
}
} }
function keytest(event) function keytest(event)