您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically adds french synonyms to all your unlocked items from WaniKani
// ==UserScript== // @name WaniKani French Synoynms // @namespace wk.french.synonyms // @version 2.0.2 // @description Automatically adds french synonyms to all your unlocked items from WaniKani // @author Acaretia (Code Revamp: Acaretia; Original Author: Norman Sue) // @include /^https://(www|preview).wanikani.com// // @copyright 2022+, Acaretia // @license MIT; http://opensource.org/licenses/MIT // @run-at document-end // @grant none // ==/UserScript== const log = (...args) => { return console.log(`%c[WaniKani French Synonyms] %c${args}`, 'color: rgb(239, 68, 68); font-weight:800;', 'color: rgb(132, 204, 22)' ); }; const error_log = (...args) => { return console.log(`%c[WaniKani French Synonyms] %c${args}`, 'color: rgb(239, 68, 68); font-weight:800;', 'color: rgb(255, 0, 0)' ); }; async function wkFrenchSynonyms() { // =========================================================================== // Defining variables == // =========================================================================== // WaniKani set a limit of 8 synonyms per item, so we need to split the list // by selecting the first eight elements of the list. const MAX_SYNONYMS = 8; // Rate Limit is set to 60 requests per minute. // We will wait for 1 minute before making another request. const RATE_LIMIT = 60; let number_of_requests = 0; // IF set to true, the script will automatically delete the user's synonyms // after the script is finished. // // TODO: Make this configurable. const DELETE_SYNONYMS = false; // =========================================================================== // Fetch french synonyms from Github == // =========================================================================== const radicals_synonyms_url = 'https://raw.githubusercontent.com/acaretia/WaniKani-French-Data/master/radicals.json'; const kanji_synonyms_url = 'https://raw.githubusercontent.com/acaretia/WaniKani-French-Data/master/kanji.json'; const words_synonyms_url = 'https://raw.githubusercontent.com/acaretia/WaniKani-French-Data/master/words.json'; const radicals_synonyms = await fetch(radicals_synonyms_url).then(response => response.json()).then(data => data); const kanji_synonyms = await fetch(kanji_synonyms_url).then(response => response.json()).then(data => data); const words_synonyms = await fetch(words_synonyms_url).then(response => response.json()).then(data => data); log('Fetched french synonyms from Github'); log(`[GITHUB] Radicals: ${Object.keys(radicals_synonyms).length}; Kanji: ${Object.keys(kanji_synonyms).length}; Words: ${Object.keys(words_synonyms).length}.`); // =========================================================================== // Getting WaniKani data and updating synonyms == // =========================================================================== // Check https://github.com/rfindley/wanikani-open-framework for // more information on the WaniKani Open Framework. const items = await wkof.ItemData.get_items({ 'wk_items': { 'options': { 'subjects': true, 'study_materials': true, 'assignments': true }, 'filters': { 'item_type': ['rad', 'kan', 'voc'], 'level': `1..${wkof.user.level}`, } } }); log('Got WaniKani data'); // From the items, get the radicals, kanji and words into separate arrays // and sort them alphabetically using the first meaning of each item // then sort the arrays by item level. const wk_radicals = items.filter(item => item.object === 'radical') .sort((a, b) => a.data.slug.localeCompare(b.data.slug)) .sort((a, b) => a.data.level - b.data.level); const wk_kanjis = items.filter(item => item.object === 'kanji') .sort((a, b) => a.data.meanings[0].meaning.localeCompare(b.data.meanings[0].meaning)) .sort((a, b) => a.data.level - b.data.level); const wk_vocabulary = items.filter(item => item.object === 'vocabulary') .sort((a, b) => a.data.meanings[0].meaning.localeCompare(b.data.meanings[0].meaning)) .sort((a, b) => a.data.level - b.data.level); log(`[wkof] Radicals: ${wk_radicals.length}; Kanji: ${wk_kanjis.length}; Words: ${wk_vocabulary.length}.`); // =========================================================================== // Synchronizing WaniKani data with french synonyms == // =========================================================================== // Function to update synonyms for a given item. const update_synonyms = async (wk_item) => { if (number_of_requests >= RATE_LIMIT) { return error_log('/!\\ Rate limit has been reached! Please wait one minute before refreshing the page.'); } if (!wk_item.assignments) { return log(`[${wk_item.object}] ${wk_item.data.slug} is not unlocked for the moment.`); } const french_synonyms = []; // Get the synonyms from the french synonyms data. // If the item is a radical, get the synonyms from the radicals synonyms data. if (wk_item.object === 'radical') { if (!radicals_synonyms[wk_item.data.slug]) return log(`[${wk_item.object}] ${wk_item.data.slug} has no french synonyms.`); french_synonyms.push(...radicals_synonyms[wk_item.data.slug]); } // If the item is a kanji, get the synonyms from the kanji synonyms data. if (wk_item.object === 'kanji') { if (!kanji_synonyms[wk_item.data.slug]) return log(`[${wk_item.object}] ${wk_item.data.slug} has no french synonyms.`); french_synonyms.push(...kanji_synonyms[wk_item.data.slug]); } // If the item is a word, get the synonyms from the words synonyms data. if (wk_item.object === 'vocabulary') { if (!words_synonyms[wk_item.data.slug]) return log(`[${wk_item.object}] ${wk_item.data.slug} has no french synonyms.`); french_synonyms.push(...words_synonyms[wk_item.data.slug]); } // If the item has no synonyms, return. if (!french_synonyms.length) { return; } // If the item has synonyms, update them. log(`[${wk_item.object}] ${wk_item.data.slug} has ${french_synonyms.length} french synonyms. (${french_synonyms.join(', ')})`); // If the item has too many synonyms, remove the oldest ones. if (french_synonyms.length > MAX_SYNONYMS) { french_synonyms.splice(0, french_synonyms.length - MAX_SYNONYMS); log(`[${wk_item.object}] ${wk_item.data.slug} has too many french synonyms. ${french_synonyms.length} synonyms will be kept.`); } let final_synonyms = []; // Add all the synonyms to the final synonyms array. if (wk_item.study_materials) { final_synonyms.push(...wk_item.study_materials.meaning_synonyms); } final_synonyms.push(...french_synonyms); // Remove duplicates. final_synonyms = [...new Set(final_synonyms)]; // Check if final synonyms array is exceeding the maximum allowed length. if (final_synonyms.length > MAX_SYNONYMS) { final_synonyms.splice(0, final_synonyms.length - MAX_SYNONYMS); } // Check if final synonyms are already the same as the current synonyms. if (wk_item.study_materials && final_synonyms.length === wk_item.study_materials.meaning_synonyms.length && final_synonyms.every((synonym, index) => synonym === wk_item.study_materials.meaning_synonyms[index])) { return log(`[${wk_item.object}] ${wk_item.data.slug} has the same french synonyms as before.`); } // Update the synonyms in the WaniKani item by doing a PUT request to the API // using a JQuery AJAX request. (TODO: Finding a way to do this without using JQuery.) const data = { 'study_material': { 'subject_type': wk_item.object, 'subject_id': wk_item.id, 'meaning_synonyms': final_synonyms } }; // Log the number of requests made. number_of_requests++; log(`Requests number ${number_of_requests} (of ${RATE_LIMIT}).`); return $.ajax({ type: 'put', url: `/study_materials/${wk_item.id}`, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), }).done(() => { log(`[${wk_item.object}] ${wk_item.data.slug} synonyms updated.`); return Promise.resolve(); }).fail((jqXHR, textStatus, errorThrown) => { log(`[${wk_item.object}] ${wk_item.data.slug} synonyms update failed.`); return Promise.reject(errorThrown); }).always(() => { return Promise.resolve(); }).promise(); }; log('[wkof] Synchronizing french synonyms...'); // Synchronize radicals // -------------------------------------------------------------------------- // For each radical in WaniKani, check if it has a french synonym. If it // does, update the radical's meaning with the french synonym. // -------------------------------------------------------------------------- for (const wk_radical of wk_radicals) { await update_synonyms(wk_radical); } // Synchronize kanji // -------------------------------------------------------------------------- // For each kanji in WaniKani, check if it has a french synonym. If it // does, update the kanji's meaning with the french synonym. // -------------------------------------------------------------------------- for (const wk_kanji of wk_kanjis) { await update_synonyms(wk_kanji); } // Synchronize words // -------------------------------------------------------------------------- // For each word in WaniKani, check if it has a french synonym. If it // does, update the word's meaning with the french synonym. // -------------------------------------------------------------------------- for (const wk_word of wk_vocabulary) { await update_synonyms(wk_word); } log('[wkof] Synchronization complete.'); log(`[wkof] Requests made: ${number_of_requests} (of ${RATE_LIMIT}).`); // =========================================================================== }; (function () { 'use strict'; const SCRIPT_NAME = 'WaniKani French Synonyms'; const SCRIPT_VERSION = '1.0.0'; console.log(`%c[${SCRIPT_NAME}] %cv${SCRIPT_VERSION} %c| %cLoading...`, 'color: rgb(239, 68, 68); font-weight:800;', 'color: rgb(255, 0, 0)', 'color: rgb(115, 115, 115)', 'color: rgb(132, 204, 22)' ); // =========================================================================== // Check if WaniKani Open Framework is installed and at == // the required version. If not, redirect to the install page. == // =========================================================================== if (!wkof) { log('WaniKani Open Framework is not installed'); if (confirm('WaniKani Open Framework is not installed.\n\nDo you want to install it?')) { window.location.href = 'https://greasyfork.org/en/scripts/38582-wanikani-open-framework'; } else { log('User doesn\'t want to install WaniKani Open Framework.'); } } const WKOF_VERSION_NEEDED = '1.0.58'; if (!wkof.version || wkof.version.compare_to(WKOF_VERSION_NEEDED) === 'older') { log('WaniKani Open Framework is not at the required version.'); if (confirm('WaniKani Open Framework is not at the required version.\n\nDo you want to update it?')) { window.location.href = 'https://greasyfork.org/en/scripts/38582-wanikani-open-framework'; } else { log('User doesn\'t want to update WaniKani Open Framework.'); } } log('WaniKani Open Framework is installed and at the required version.'); wkof.include('ItemData,Menu,Settings'); // =========================================================================== wkof.ready('ItemData,Menu,Settings').then(() => { log('[WKOF] ItemData included.'); if (!wkof.user) { return log('[WKOF] User is undefined.'); } wkFrenchSynonyms(); }); })();