sayonara ankiweb
This commit is contained in:
parent
5c562c0288
commit
916751420e
@ -4,7 +4,6 @@
|
|||||||
<script src="../lib/handlebars.min.js"></script>
|
<script src="../lib/handlebars.min.js"></script>
|
||||||
<script src="../lib/dexie.min.js"></script>
|
<script src="../lib/dexie.min.js"></script>
|
||||||
<script src="../lib/wanakana.min.js"></script>
|
<script src="../lib/wanakana.min.js"></script>
|
||||||
<script src="js/ankiweb.js"></script>
|
|
||||||
<script src="js/ankiconnect.js"></script>
|
<script src="js/ankiconnect.js"></script>
|
||||||
<script src="js/ankinull.js"></script>
|
<script src="js/ankinull.js"></script>
|
||||||
<script src="js/templates.js"></script>
|
<script src="js/templates.js"></script>
|
||||||
|
@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2016 Alex Yatskov <alex@foosoft.net>
|
|
||||||
* Author: Alex Yatskov <alex@foosoft.net>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class AnkiWeb {
|
|
||||||
constructor(username, password) {
|
|
||||||
this.username = username;
|
|
||||||
this.password = password;
|
|
||||||
this.noteInfo = null;
|
|
||||||
|
|
||||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
|
||||||
details => {
|
|
||||||
details.requestHeaders.push({name: 'Origin', value: 'https://ankiweb.net'});
|
|
||||||
return {requestHeaders: details.requestHeaders};
|
|
||||||
},
|
|
||||||
{urls: ['https://ankiweb.net/*']},
|
|
||||||
['blocking', 'requestHeaders']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addNote(note) {
|
|
||||||
return this.retrieve().then(info => {
|
|
||||||
const model = info.models.find(m => m.name === note.modelName);
|
|
||||||
if (!model) {
|
|
||||||
return Promise.reject('cannot add note model provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = [];
|
|
||||||
for (const field of model.fields) {
|
|
||||||
fields.push(note.fields[field]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
data: JSON.stringify([fields, note.tags.join(' ')]),
|
|
||||||
mid: model.id,
|
|
||||||
deck: note.deckName,
|
|
||||||
csrf_token: info.token
|
|
||||||
};
|
|
||||||
|
|
||||||
return AnkiWeb.loadAccountPage('https://ankiweb.net/edit/save', data, this.username, this.password);
|
|
||||||
}).then(response => response !== '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
canAddNotes(notes) {
|
|
||||||
return Promise.resolve(new Array(notes.length).fill(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeckNames() {
|
|
||||||
return this.retrieve().then(info => info.deckNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
getModelNames() {
|
|
||||||
return this.retrieve().then(info => info.models.map(m => m.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
getModelFieldNames(modelName) {
|
|
||||||
return this.retrieve().then(info => {
|
|
||||||
const model = info.models.find(m => m.name === modelName);
|
|
||||||
return model ? model.fields : [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieve() {
|
|
||||||
if (this.noteInfo !== null) {
|
|
||||||
return Promise.resolve(this.noteInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnkiWeb.scrape(this.username, this.password).then(({deckNames, models, token}) => {
|
|
||||||
this.noteInfo = {deckNames, models, token};
|
|
||||||
return this.noteInfo;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logout() {
|
|
||||||
return AnkiWeb.loadPage('https://ankiweb.net/account/logout', null);
|
|
||||||
}
|
|
||||||
|
|
||||||
static scrape(username, password) {
|
|
||||||
return AnkiWeb.loadAccountPage('https://ankiweb.net/edit/', null, username, password).then(response => {
|
|
||||||
const modelsMatch = /editor\.models = (.*}]);/.exec(response);
|
|
||||||
if (modelsMatch === null) {
|
|
||||||
return Promise.reject('failed to scrape model data');
|
|
||||||
}
|
|
||||||
|
|
||||||
const decksMatch = /editor\.decks = (.*}});/.exec(response);
|
|
||||||
if (decksMatch === null) {
|
|
||||||
return Promise.reject('failed to scrape deck data');
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokenMatch = /editor\.csrf_token = \'(.*)\';/.exec(response);
|
|
||||||
if (tokenMatch === null) {
|
|
||||||
return Promise.reject('failed to acquire csrf_token');
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelsJson = JSON.parse(modelsMatch[1]);
|
|
||||||
const decksJson = JSON.parse(decksMatch[1]);
|
|
||||||
const token = tokenMatch[1];
|
|
||||||
|
|
||||||
const deckNames = Object.keys(decksJson).map(d => decksJson[d].name);
|
|
||||||
const models = [];
|
|
||||||
for (const modelJson of modelsJson) {
|
|
||||||
models.push({
|
|
||||||
name: modelJson.name,
|
|
||||||
id: modelJson.id,
|
|
||||||
fields: modelJson.flds.map(f => f.name)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {deckNames, models, token};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static login(username, password, token) {
|
|
||||||
if (username.length === 0 || password.length === 0) {
|
|
||||||
return Promise.reject('login credentials not specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {username, password, csrf_token: token, submitted: 1};
|
|
||||||
return AnkiWeb.loadPage('https://ankiweb.net/account/login', data).then(response => {
|
|
||||||
if (!response.includes('class="mitem"')) {
|
|
||||||
return Promise.reject('failed to authenticate');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static loadAccountPage(url, data, username, password) {
|
|
||||||
return AnkiWeb.loadPage(url, data).then(response => {
|
|
||||||
if (response.includes('name="password"')) {
|
|
||||||
const tokenMatch = /name="csrf_token" value="(.*)"/.exec(response);
|
|
||||||
if (tokenMatch === null) {
|
|
||||||
return Promise.reject('failed to acquire csrf_token');
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnkiWeb.login(username, password, tokenMatch[1]).then(() => AnkiWeb.loadPage(url, data));
|
|
||||||
} else {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static loadPage(url, data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let dataEnc = null;
|
|
||||||
if (data) {
|
|
||||||
const params = [];
|
|
||||||
for (const key in data) {
|
|
||||||
params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataEnc = params.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
xhr.addEventListener('error', () => reject('failed to execute network request'));
|
|
||||||
xhr.addEventListener('load', () => resolve(xhr.responseText));
|
|
||||||
if (dataEnc) {
|
|
||||||
xhr.open('POST', url);
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
||||||
xhr.send(dataEnc);
|
|
||||||
} else {
|
|
||||||
xhr.open('GET', url);
|
|
||||||
xhr.send();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,8 +39,6 @@ function getFormValues() {
|
|||||||
optsNew.scanLength = parseInt($('#scan-length').val(), 10);
|
optsNew.scanLength = parseInt($('#scan-length').val(), 10);
|
||||||
|
|
||||||
optsNew.ankiMethod = $('#anki-method').val();
|
optsNew.ankiMethod = $('#anki-method').val();
|
||||||
optsNew.ankiUsername = $('#anki-username').val();
|
|
||||||
optsNew.ankiPassword = $('#anki-password').val();
|
|
||||||
optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/);
|
optsNew.ankiCardTags = $('#anki-card-tags').val().split(/[,; ]+/);
|
||||||
optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10);
|
optsNew.sentenceExtent = parseInt($('#sentence-extent').val(), 10);
|
||||||
optsNew.ankiTermDeck = $('#anki-term-deck').val();
|
optsNew.ankiTermDeck = $('#anki-term-deck').val();
|
||||||
@ -67,13 +65,8 @@ function getFormValues() {
|
|||||||
|
|
||||||
function updateVisibility(opts) {
|
function updateVisibility(opts) {
|
||||||
switch (opts.ankiMethod) {
|
switch (opts.ankiMethod) {
|
||||||
case 'ankiweb':
|
|
||||||
$('#anki-general').show();
|
|
||||||
$('.anki-login').show();
|
|
||||||
break;
|
|
||||||
case 'ankiconnect':
|
case 'ankiconnect':
|
||||||
$('#anki-general').show();
|
$('#anki-general').show();
|
||||||
$('.anki-login').hide();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
$('#anki-general').hide();
|
$('#anki-general').hide();
|
||||||
@ -102,8 +95,6 @@ $(document).ready(() => {
|
|||||||
$('#scan-length').val(opts.scanLength);
|
$('#scan-length').val(opts.scanLength);
|
||||||
|
|
||||||
$('#anki-method').val(opts.ankiMethod);
|
$('#anki-method').val(opts.ankiMethod);
|
||||||
$('#anki-username').val(opts.ankiUsername);
|
|
||||||
$('#anki-password').val(opts.ankiPassword);
|
|
||||||
$('#anki-card-tags').val(opts.ankiCardTags.join(' '));
|
$('#anki-card-tags').val(opts.ankiCardTags.join(' '));
|
||||||
$('#sentence-extent').val(opts.sentenceExtent);
|
$('#sentence-extent').val(opts.sentenceExtent);
|
||||||
|
|
||||||
@ -430,16 +421,7 @@ function onOptionsChanged(e) {
|
|||||||
return saveOptions(optsNew).then(() => {
|
return saveOptions(optsNew).then(() => {
|
||||||
yomichan().setOptions(optsNew);
|
yomichan().setOptions(optsNew);
|
||||||
updateVisibility(optsNew);
|
updateVisibility(optsNew);
|
||||||
|
if (optsNew.ankiMethod !== optsOld.ankiMethod) {
|
||||||
const loginChanged =
|
|
||||||
optsNew.ankiUsername !== optsOld.ankiUsername ||
|
|
||||||
optsNew.ankiPassword !== optsOld.ankiPassword;
|
|
||||||
|
|
||||||
if (loginChanged && optsNew.ankiMethod === 'ankiweb') {
|
|
||||||
showAnkiError(null);
|
|
||||||
showAnkiSpinner(true);
|
|
||||||
return anki().logout().then(() => populateAnkiDeckAndModel(optsNew));
|
|
||||||
} else if (loginChanged || optsNew.ankiMethod !== optsOld.ankiMethod) {
|
|
||||||
showAnkiError(null);
|
showAnkiError(null);
|
||||||
showAnkiSpinner(true);
|
showAnkiSpinner(true);
|
||||||
return populateAnkiDeckAndModel(optsNew);
|
return populateAnkiDeckAndModel(optsNew);
|
||||||
|
@ -31,8 +31,6 @@ function sanitizeOptions(options) {
|
|||||||
dictionaries: {},
|
dictionaries: {},
|
||||||
|
|
||||||
ankiMethod: 'disabled',
|
ankiMethod: 'disabled',
|
||||||
ankiUsername: '',
|
|
||||||
ankiPassword: '',
|
|
||||||
ankiCardTags: ['yomichan'],
|
ankiCardTags: ['yomichan'],
|
||||||
sentenceExtent: 200,
|
sentenceExtent: 200,
|
||||||
|
|
||||||
@ -50,6 +48,10 @@ function sanitizeOptions(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.ankiMethod === 'ankiweb') {
|
||||||
|
options.ankiMethod = 'disabled';
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +94,6 @@ class Yomichan {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
switch (options.ankiMethod) {
|
switch (options.ankiMethod) {
|
||||||
case 'ankiweb':
|
|
||||||
this.anki = new AnkiWeb(options.ankiUsername, options.ankiPassword);
|
|
||||||
break;
|
|
||||||
case 'ankiconnect':
|
case 'ankiconnect':
|
||||||
this.anki = new AnkiConnect();
|
this.anki = new AnkiConnect();
|
||||||
break;
|
break;
|
||||||
|
@ -124,9 +124,8 @@
|
|||||||
|
|
||||||
<p class="help-block">
|
<p class="help-block">
|
||||||
Yomichan features automatic flashcard creation for <a href="http://ankisrs.net/">Anki</a>, a free application
|
Yomichan features automatic flashcard creation for <a href="http://ankisrs.net/">Anki</a>, a free application
|
||||||
designed to help you retain knowledge. While the <a href="https://foosoft.net/projects/anki-connect/">AnkiConnect</a> plugin
|
designed to help you retain knowledge. This functionality requires prior installation of the
|
||||||
offers the best experience, it is also possible to create flashcards through <a href="https://ankiweb.net/">AnkiWeb</a>,
|
<a href="https://foosoft.net/projects/anki-connect/">AnkiConnect</a> plugin.
|
||||||
provided you already have an account.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="alert alert-danger" id="anki-error">
|
<div class="alert alert-danger" id="anki-error">
|
||||||
@ -139,23 +138,10 @@
|
|||||||
<select class="form-control" id="anki-method">
|
<select class="form-control" id="anki-method">
|
||||||
<option value="disabled">Disabled (no auto flashcard creation)</option>
|
<option value="disabled">Disabled (no auto flashcard creation)</option>
|
||||||
<option value="ankiconnect">AnkiConnect (requires the AnkiConnect plugin)</option>
|
<option value="ankiconnect">AnkiConnect (requires the AnkiConnect plugin)</option>
|
||||||
<option value="ankiweb">AnkiWeb (requires an account on AnkiWeb)</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="anki-general">
|
<div id="anki-general">
|
||||||
<div class="row">
|
|
||||||
<div class="form-group anki-login col-xs-6">
|
|
||||||
<label for="anki-username">Username</label>
|
|
||||||
<input type="text" id="anki-username" class="form-control anki-credential">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group anki-login col-xs-6">
|
|
||||||
<label for="anki-password">Password</label>
|
|
||||||
<input type="password" id="anki-password" class="form-control anki-credential">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="anki-card-tags">Card tags (comma or space separated)</label>
|
<label for="anki-card-tags">Card tags (comma or space separated)</label>
|
||||||
<input type="text" id="anki-card-tags" class="form-control">
|
<input type="text" id="anki-card-tags" class="form-control">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Yomichan",
|
"name": "Yomichan",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
|
|
||||||
"description": "Japanese dictionary with Anki integration",
|
"description": "Japanese dictionary with Anki integration",
|
||||||
"icons": {"16": "img/icon16.png", "48": "img/icon48.png", "128": "img/icon128.png"},
|
"icons": {"16": "img/icon16.png", "48": "img/icon48.png", "128": "img/icon128.png"},
|
||||||
@ -25,11 +25,8 @@
|
|||||||
"page": "bg/options.html"
|
"page": "bg/options.html"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"webRequest",
|
|
||||||
"webRequestBlocking",
|
|
||||||
"file://*/*",
|
"file://*/*",
|
||||||
"http://127.0.0.1/*",
|
"http://127.0.0.1/*",
|
||||||
"https://ankiweb.net/*",
|
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
|
Loading…
Reference in New Issue
Block a user