您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Import Anime and Manga Lists from Anilist (see https://anilist.co/forum/thread/2654 for more info)
- // ==UserScript==
- // @name Douki
- // @namespace douki-e7d98778-9b83-45eb-a189-456bd1ce2ee1
- // @description Import Anime and Manga Lists from Anilist (see https://anilist.co/forum/thread/2654 for more info)
- // @version 0.2.5
- // @include https://myanimelist.net/*
- // ==/UserScript==
- /******/ (() => { // webpackBootstrap
- /******/ "use strict";
- /******/ var __webpack_modules__ = ([
- /* 0 */,
- /* 1 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.Log = void 0;
- const const_1 = __webpack_require__(2);
- const Util_1 = __webpack_require__(3);
- const getCountLog = (operation, type) => document.querySelector(Util_1.id(`douki-${operation}-${type}-items`));
- class Log {
- constructor() {
- this.errorLogElement = null;
- this.syncLogElement = null;
- this.debugLogElement = null;
- }
- get errorLog() {
- if (!this.errorLogElement) {
- this.errorLogElement = document.querySelector(Util_1.id(const_1.ERROR_LOG_ID));
- }
- return this.errorLogElement;
- }
- get syncLog() {
- if (!this.syncLogElement) {
- this.syncLogElement = document.querySelector(Util_1.id(const_1.SYNC_LOG_ID));
- }
- return this.syncLogElement;
- }
- get debugLog() {
- if (!this.debugLogElement) {
- this.debugLogElement = document.querySelector(Util_1.id(const_1.DEBUG_LOG_ID));
- }
- return this.debugLogElement;
- }
- clearErrorLog() {
- if (this.errorLog) {
- this.errorLog.innerHTML = '';
- }
- }
- clearSyncLog() {
- if (this.syncLog) {
- this.syncLog.innerHTML = '';
- }
- }
- clearDebugLog() {
- if (this.debugLog) {
- this.debugLog.innerHTML = '';
- }
- }
- clear(type = '') {
- console.clear();
- if (type !== 'error')
- this.clearSyncLog();
- if (type !== 'sync')
- this.clearErrorLog();
- this.clearDebugLog();
- }
- error(msg) {
- if (this.errorLog) {
- this.errorLog.innerHTML += `<li>${msg}</li>`;
- }
- else {
- console.error(msg);
- }
- }
- info(msg) {
- if (this.syncLog) {
- this.syncLog.innerHTML += `<li>${msg}</li>`;
- }
- else {
- console.info(msg);
- }
- }
- debug(msg) {
- if (this.debugLog) {
- this.debugLog.innerHTML += `<li>${msg}</li>`;
- }
- else {
- console.debug(msg);
- }
- }
- addCountLog(operation, type, max) {
- const opName = Util_1.getOperationDisplayName(operation);
- const logId = `douki-${operation}-${type}-items`;
- this.info(`${opName} <span id="${logId}">0</span> of ${max} ${type} items.`);
- }
- updateCountLog(operation, type, count) {
- const countLog = getCountLog(operation, type);
- if (!countLog)
- return;
- countLog.innerHTML = `${count}`;
- }
- }
- exports.Log = Log;
- exports.default = new Log();
- /***/ }),
- /* 2 */
- /***/ ((__unused_webpack_module, exports) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.DEBUG_LOG_ID = exports.DEBUG_SETTING_ID = exports.DROPDOWN_ITEM_ID = exports.DATE_SETTINGS_KEY = exports.SETTINGS_KEY = exports.ANILIST_USERNAME_ID = exports.ERROR_LOG_DIV_ID = exports.ERROR_LOG_TOGGLE_ID = exports.ERROR_LOG_ID = exports.SYNC_LOG_ID = exports.DOUKI_IMPORT_BUTTON_ID = exports.CONTENT_ID = exports.DATE_SETTING_ID = exports.DOUKI_ANILIST_IMPORT_ID = exports.DOUKI_FORM_ID = void 0;
- exports.DOUKI_FORM_ID = 'douki-form';
- exports.DOUKI_ANILIST_IMPORT_ID = 'douki-anilist-import';
- exports.DATE_SETTING_ID = 'douki-date_format';
- exports.CONTENT_ID = 'content';
- exports.DOUKI_IMPORT_BUTTON_ID = 'douki-import';
- exports.SYNC_LOG_ID = 'douki-sync-log';
- exports.ERROR_LOG_ID = 'douki-error-log';
- exports.ERROR_LOG_TOGGLE_ID = 'douki-error-log-toggle';
- exports.ERROR_LOG_DIV_ID = 'douki-error-log-div';
- exports.ANILIST_USERNAME_ID = 'douki-anilist-username';
- exports.SETTINGS_KEY = 'douki-settings';
- exports.DATE_SETTINGS_KEY = 'douki-settings-date';
- exports.DROPDOWN_ITEM_ID = 'douki-sync';
- exports.DEBUG_SETTING_ID = 'douki-debug';
- exports.DEBUG_LOG_ID = 'douki-debug-log';
- /***/ }),
- /* 3 */
- /***/ ((__unused_webpack_module, exports) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.getOperationDisplayName = exports.id = exports.sleep = void 0;
- const sleep = (ms) => new Promise(resolve => setTimeout(() => resolve(null), ms));
- exports.sleep = sleep;
- const id = (str) => `#${str}`;
- exports.id = id;
- const getOperationDisplayName = (operation) => {
- switch (operation) {
- case 'add':
- return 'Adding';
- case 'edit':
- return 'Updating';
- case 'complete':
- return 'Fixing';
- default:
- throw new Error('Unknown operation type');
- }
- };
- exports.getOperationDisplayName = getOperationDisplayName;
- /***/ }),
- /* 4 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.DomMethods = void 0;
- const const_1 = __webpack_require__(2);
- const Util_1 = __webpack_require__(3);
- const importFormHTML = `
- <div id="${const_1.DOUKI_FORM_ID}">
- <h1 class="h1">Import From Anilist</h1>
- <div style="padding: 20px">
- <p><strong>NOTICE</strong>: Use this script at your own risk. The author takes no responsibility for any damages of any kind.</p>
- <p>It is <em>highly</em> recommended that you try this script out on a test MAL account before importing to your main account.</p>
- <p>Visit <a href="https://anilist.co/forum/thread/2654" target="_blank" rel="noopener noreferrer">the Anilist thread</a> for this script to ask questions or report problems.</p>
- <p>Please be patient. If the import goes any faster you will be in violation of MyAnimeList's Terms of Service.</p>
- </div>
- <form id="${const_1.DOUKI_ANILIST_IMPORT_ID}" style="padding: 5px 0px 10px 0px">
- <p style="margin: 10px"><label>Anilist Username: <input type="text" id="${const_1.ANILIST_USERNAME_ID}" /></label></p>
- <p style="margin: 10px">
- <label>Date Format:
- <select id="${const_1.DATE_SETTING_ID}" class="inputtext">
- <option value="a" selected>American (MM-DD-YY)
- <option value="e" >European (DD-MM-YY)
- </select>
- </label>
- <label>Debug Mode:
- <input id="${const_1.DEBUG_SETTING_ID}" type="checkbox" name="debug">
- </label>
- </p>
- <p style="margin: 10px"><button id="${const_1.DOUKI_IMPORT_BUTTON_ID}">Import</button></p>
- </form>
- <br />
- <ul id="${const_1.SYNC_LOG_ID}" style="list-type: none;"></ul>
- <p style="margin: 10px"><button id="${const_1.ERROR_LOG_TOGGLE_ID}" style="border: none">Show items that could not be synced</button></p>
- <div id="${const_1.ERROR_LOG_DIV_ID}" style="display: none;">
- <p style="margin: 10px">Anilist does not have a MAL ID for the following items. If a verified MAL entry exists for any of these, contact an Anilist data mod to have it added.</p>
- <ul id="${const_1.ERROR_LOG_ID}" style="list-type: none;"></ul>
- </div>
- <div>
- <ul id="${const_1.DEBUG_LOG_ID}" style="list-type: none;"></ul>
- </div>
- </div>
- `;
- const getLocalStorageSetting = (setting) => {
- if (localStorage) {
- const value = localStorage.getItem(setting);
- if (value)
- return JSON.parse(value);
- }
- return null;
- };
- const setLocalStorageSetting = (setting, value) => {
- if (localStorage) {
- localStorage.setItem(setting, JSON.stringify(value));
- }
- };
- class DomMethods {
- constructor() {
- this.csrfToken = null;
- }
- addDropDownItem() {
- if (document.querySelector(Util_1.id(const_1.DROPDOWN_ITEM_ID)))
- return;
- const selector = '.header-menu-dropdown > ul > li:last-child';
- const dropdown = document.querySelector(selector);
- if (dropdown) {
- const html = `<li><a aria-role="button" style="cursor: pointer" id="${const_1.DROPDOWN_ITEM_ID}">Import from Anilist</a></li>`;
- dropdown.insertAdjacentHTML('afterend', html);
- const link = document.querySelector(Util_1.id(const_1.DROPDOWN_ITEM_ID));
- link && link.addEventListener('click', function (e) {
- e.preventDefault();
- window.location.replace('https://myanimelist.net/import.php');
- });
- }
- }
- addImportForm(syncFn) {
- if (document.querySelector(Util_1.id(const_1.DOUKI_FORM_ID)))
- return;
- const element = document.querySelector(Util_1.id(const_1.CONTENT_ID));
- if (!element) {
- throw new Error('Unable to add form to page');
- }
- element.insertAdjacentHTML('afterend', importFormHTML);
- this.addImportFormEventListeners(syncFn);
- }
- // TODO break this up
- addImportFormEventListeners(syncFn) {
- const importButton = document.querySelector(Util_1.id(const_1.DOUKI_IMPORT_BUTTON_ID));
- importButton && importButton.addEventListener('click', function (e) {
- syncFn(e);
- });
- const textBox = document.querySelector(Util_1.id(const_1.ANILIST_USERNAME_ID));
- textBox && textBox.addEventListener('change', function (e) {
- setLocalStorageSetting(const_1.SETTINGS_KEY, e.target.value);
- });
- const username = getLocalStorageSetting(const_1.SETTINGS_KEY);
- if (username && textBox) {
- textBox.value = username;
- }
- const dateFormatPicker = document.querySelector(Util_1.id(const_1.DATE_SETTING_ID));
- dateFormatPicker && dateFormatPicker.addEventListener('change', function (e) {
- setLocalStorageSetting(const_1.DATE_SETTINGS_KEY, e.target.value);
- });
- const dateOption = getLocalStorageSetting(const_1.DATE_SETTINGS_KEY);
- if (dateOption && dateFormatPicker) {
- dateFormatPicker.value = dateOption;
- }
- const errorToggle = document.querySelector(Util_1.id(const_1.ERROR_LOG_TOGGLE_ID));
- errorToggle && errorToggle.addEventListener('click', function (e) {
- e.preventDefault();
- const errorLog = document.querySelector(Util_1.id(const_1.ERROR_LOG_DIV_ID));
- if (errorLog.style.display === 'none') {
- errorLog.style.display = 'block';
- }
- else {
- errorLog.style.display = 'none';
- }
- });
- }
- getDateSetting() {
- const dateSetting = document.querySelector(Util_1.id(const_1.DATE_SETTING_ID));
- if (!dateSetting || !dateSetting.value)
- throw new Error('Unable to get date setting');
- return dateSetting.value;
- }
- getDebugSetting() {
- const debugSetting = document.querySelector(Util_1.id(const_1.DEBUG_SETTING_ID));
- if (!debugSetting)
- throw new Error('Unable to get debug setting');
- return debugSetting.checked;
- }
- getCSRFToken() {
- if (this.csrfToken)
- return this.csrfToken;
- const csrfTokenMeta = document.querySelector('meta[name~="csrf_token"]');
- if (!csrfTokenMeta)
- throw new Error('Unable to get CSRF token - no meta element');
- const csrfToken = csrfTokenMeta.getAttribute('content');
- if (!csrfToken)
- throw new Error('Unable to get CSRF token - no content attribute');
- this.csrfToken = csrfToken;
- return csrfToken;
- }
- getMALUsername() {
- const malUsernameElement = document.querySelector('.header-profile-link');
- if (!malUsernameElement)
- return null;
- return malUsernameElement.innerText;
- }
- getAnilistUsername() {
- const anilistUserElement = document.querySelector('#douki-anilist-username');
- if (!anilistUserElement)
- throw new Error('Unable to get Anilist username');
- return anilistUserElement.value;
- }
- }
- exports.DomMethods = DomMethods;
- exports.default = new DomMethods();
- /***/ }),
- /* 5 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.getAnilistList = void 0;
- const Log_1 = __webpack_require__(1);
- const flatten = (obj) =>
- // Outer reduce concats arrays built by inner reduce
- Object.keys(obj).reduce((accumulator, list) =>
- // Inner reduce builds an array out of the lists
- accumulator.concat(Object.keys(obj[list]).reduce((acc2, item) =>
- // @ts-ignore
- acc2.concat(obj[list][item]), [])), []);
- const uniqify = (arr) => {
- const seen = new Set();
- return arr.filter(item => (seen.has(item.media.idMal) ? false : seen.add(item.media.idMal)));
- };
- // Anilist Functions
- const anilistCall = (query, variables) => fetch('https://graphql.anilist.co', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Accept: 'application/json',
- },
- body: JSON.stringify({
- query,
- variables,
- }),
- });
- const fetchList = (userName) => anilistCall(`
- query ($userName: String) {
- anime: MediaListCollection(userName: $userName, type: ANIME) {
- lists {
- entries {
- status
- score(format:POINT_10)
- progress
- startedAt {
- year
- month
- day
- }
- completedAt {
- year
- month
- day
- }
- repeat
- media {
- idMal
- title {
- romaji
- }
- }
- }
- }
- },
- manga: MediaListCollection(userName: $userName, type: MANGA) {
- lists {
- entries {
- status
- score(format:POINT_10)
- progress
- progressVolumes
- startedAt {
- year
- month
- day
- }
- completedAt {
- year
- month
- day
- }
- repeat
- media {
- idMal
- title {
- romaji
- }
- }
- }
- }
- }
- }
- `, {
- userName
- })
- .then(res => res.json())
- .then(res => res.data)
- .then(res => ({
- anime: uniqify(flatten(res.anime.lists)),
- manga: uniqify(flatten(res.manga.lists)),
- }));
- const sanitize = (item, type) => ({
- type,
- progress: item.progress,
- progressVolumes: item.progressVolumes,
- startedAt: {
- year: item.startedAt.year || 0,
- month: item.startedAt.month || 0,
- day: item.startedAt.day || 0,
- },
- completedAt: {
- year: item.completedAt.year || 0,
- month: item.completedAt.month || 0,
- day: item.completedAt.day || 0
- },
- repeat: item.repeat,
- status: item.status,
- score: item.score,
- id: item.media.idMal,
- title: item.media.title.romaji
- });
- const filterNoMalId = (item) => {
- if (item.id)
- return true;
- Log_1.default.error(`${item.type}: ${item.title}`);
- return false;
- };
- const getAnilistList = (username) => fetchList(username)
- .then(lists => ({
- anime: lists.anime
- .map(item => sanitize(item, 'anime'))
- .filter(item => filterNoMalId(item)),
- manga: lists.manga
- .map(item => sanitize(item, 'manga'))
- .filter(item => filterNoMalId(item)),
- }));
- exports.getAnilistList = getAnilistList;
- /***/ }),
- /* 6 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- const Util_1 = __webpack_require__(3);
- const MALEntry_1 = __webpack_require__(7);
- const Log_1 = __webpack_require__(1);
- const Dom_1 = __webpack_require__(4);
- class MAL {
- constructor(username, csrfToken, log = Log_1.default, dom = Dom_1.default) {
- this.username = username;
- this.csrfToken = csrfToken;
- this.Log = log;
- this.dom = dom;
- }
- createMALHashMap(malList, type) {
- const hashMap = {};
- malList.forEach(item => {
- hashMap[item[`${type}_id`]] = item;
- });
- return hashMap;
- }
- async getMALHashMap(type, list = [], page = 1) {
- const offset = (page - 1) * 300;
- const nextList = await fetch(`https://myanimelist.net/${type}list/${this.username}/load.json?offset=${offset}&status=7`)
- .then(async (res) => {
- if (res.status !== 200) {
- await Util_1.sleep(2000);
- return this.getMALHashMap(type, list, page);
- }
- return res.json();
- });
- if (nextList && nextList.length) {
- await Util_1.sleep(1500);
- return this.getMALHashMap(type, [...list, ...nextList], page + 1);
- }
- this.Log.info(`Fetched MyAnimeList ${type} list.`);
- return this.createMALHashMap([...list, ...nextList], type);
- }
- async getEntriesList(anilistList, type) {
- const malHashMap = await this.getMALHashMap(type);
- return anilistList.map(entry => MALEntry_1.createMALEntry(entry, malHashMap[entry.id], this.csrfToken, this.dom));
- }
- async malEdit(data) {
- const { type, id } = data;
- const formData = await data.formData();
- return fetch(`https://myanimelist.net/ownlist/${type}/${id}/edit?hideLayout`, {
- credentials: 'include',
- headers: {
- accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
- 'accept-language': 'en-US,en;q=0.9,ja;q=0.8',
- 'cache-control': 'max-age=0',
- 'content-type': 'application/x-www-form-urlencoded',
- 'upgrade-insecure-requests': '1'
- },
- referrer: `https://myanimelist.net/ownlist/${type}/${id}/edit?hideLayout`,
- referrerPolicy: 'no-referrer-when-downgrade',
- body: formData,
- method: 'POST',
- mode: 'cors'
- }).then((res) => {
- if (res.status === 200)
- return res;
- throw new Error(`Error updating ${type} id ${id}`);
- }).then((res) => res.text())
- .then((text) => {
- if (text.match(/.+Successfully updated entry.+/))
- return;
- throw new Error(`Error updating ${type} id ${id}`);
- });
- }
- malAdd(data) {
- return fetch(`https://myanimelist.net/ownlist/${data.type}/add.json`, {
- method: 'post',
- headers: {
- 'Accept': '*/*',
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
- 'x-requested-with': 'XMLHttpRequest'
- },
- body: JSON.stringify(data.postData)
- })
- .then((res) => {
- if (res.status === 200)
- return res;
- throw new Error(JSON.stringify(data));
- });
- }
- async syncList(type, list, operation) {
- if (!list || !list.length) {
- return;
- }
- this.Log.addCountLog(operation, type, list.length);
- let itemCount = 0;
- const fn = operation === 'add' ? this.malAdd : this.malEdit;
- for (let item of list) {
- await Util_1.sleep(500);
- try {
- await fn(item);
- itemCount++;
- this.Log.updateCountLog(operation, type, itemCount);
- }
- catch (e) {
- console.error(e);
- this.Log.info(`Error for ${type} <a href="https://myanimelist.net/${type}/${item.id}" target="_blank" rel="noopener noreferrer">${item.title}</a>. Try adding or updating it manually.`);
- }
- }
- }
- async syncType(type, anilistList) {
- this.Log.info(`Fetching MyAnimeList ${type} list...`);
- let list = await this.getEntriesList(anilistList, type);
- const addList = list.filter(entry => entry.shouldAdd());
- await this.syncList(type, addList, 'add');
- // Refresh list to get episode/chapter counts of new completed items
- if (addList.length) {
- this.Log.info(`Refreshing MyAnimeList ${type} list...`);
- list = await this.getEntriesList(anilistList, type);
- }
- const updateList = list.filter(entry => entry.shouldUpdate());
- await this.syncList(type, updateList, 'edit');
- }
- }
- exports.default = MAL;
- /***/ }),
- /* 7 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.MALEntryManga = exports.MALEntryAnime = exports.BaseMALEntry = exports.createMALEntry = void 0;
- const MALForm_1 = __webpack_require__(8);
- const Dom_1 = __webpack_require__(4);
- const Log_1 = __webpack_require__(1);
- const createMALEntry = (al, mal, csrfToken, dom) => al.type === 'anime' ?
- new MALEntryAnime(al, mal, csrfToken, dom) :
- new MALEntryManga(al, mal, csrfToken, dom);
- exports.createMALEntry = createMALEntry;
- const MALStatus = {
- Current: 1,
- Completed: 2,
- Paused: 3,
- Dropped: 4,
- Planning: 6
- };
- const getStatus = (status) => {
- // MAL status: 1/watching, 2/completed, 3/onhold, 4/dropped, 6/plantowatch
- // MAL handles REPEATING as a boolean, and keeps status as COMPLETE
- switch (status.trim()) {
- case 'CURRENT':
- return MALStatus.Current;
- case 'REPEATING':
- case 'COMPLETED':
- return MALStatus.Completed;
- case 'PAUSED':
- return MALStatus.Paused;
- case 'DROPPED':
- return MALStatus.Dropped;
- case 'PLANNING':
- return MALStatus.Planning;
- default:
- throw new Error(`unknown status "${status}"`);
- }
- };
- const createMALFormData = (malData) => {
- let formData = '';
- Object.keys(malData).forEach(key => {
- formData += `${encodeURIComponent(key)}=${encodeURIComponent(malData[key])}&`;
- });
- return formData.replace(/&$/, '');
- };
- class BaseMALEntry {
- constructor(al, mal, csrfToken = '', dom = Dom_1.default, log = Log_1.default) {
- this.alData = al;
- this.malData = mal;
- this.csrfToken = csrfToken;
- this._postData = this.createPostData();
- this.dom = dom;
- this.log = log;
- }
- createBaseMALPostItem() {
- return {
- status: getStatus(this.alData.status),
- csrf_token: this.csrfToken,
- score: this.alData.score || 0,
- finish_date: {
- year: this.alData.completedAt.year || 0,
- month: this.alData.completedAt.month || 0,
- day: this.alData.completedAt.day || 0
- },
- start_date: {
- year: this.alData.startedAt.year || 0,
- month: this.alData.startedAt.month || 0,
- day: this.alData.startedAt.day || 0
- }
- };
- }
- buildDateString(date) {
- if (date.month === 0 && date.day === 0 && date.year === 0)
- return null;
- const dateSetting = this.dom.getDateSetting();
- const month = `${String(date.month).length < 2 ? '0' : ''}${date.month}`;
- const day = `${String(date.day).length < 2 ? '0' : ''}${date.day}`;
- const year = `${date.year ? String(date.year).slice(-2) : 0}`;
- if (dateSetting === 'a') {
- return `${month}-${day}-${year}`;
- }
- return `${day}-${month}-${year}`;
- }
- shouldUpdate() {
- // If something went wrong or it didn't get added, update will not work
- if (!this.malData || !this._postData) {
- return false;
- }
- const debug = this.dom.getDebugSetting();
- return Object.keys(this._postData).some(key => {
- switch (key) {
- case 'csrf_token':
- case 'anime_id':
- case 'manga_id':
- // This data is not part of the load.json list and so can't be used as update test
- case 'num_watched_times':
- case 'num_read_times':
- return false;
- case 'start_date':
- case 'finish_date':
- {
- // @ts-ignore
- const dateString = this.buildDateString(this._postData[key]);
- if (dateString !== this.malData[`${key}_string`]) {
- if (debug) {
- this.log.debug(`${this.alData.title}: ${key} differs; MAL ${this.malData[`${key}_string`]} AL ${dateString}`);
- }
- return true;
- }
- return false;
- }
- case 'num_read_chapters':
- case 'num_read_volumes':
- case 'num_watched_episodes':
- // Anlist and MAL have different volume, episode, and chapter counts for some media;
- // If the item is marked as completed, ignore differences (Status 2 is COMPLETED)
- // EXCEPT when the count is 0, in which case this was newly added without a count and needs
- // to be updated now that the count is available
- {
- if (this.malData.status === MALStatus.Completed && this.malData[key] !== 0) {
- return false;
- }
- if (this._postData[key] !== this.malData[key]) {
- if (debug) {
- this.log.debug(`${this.alData.title} ${key} differs; MAL ${this.malData[key]} AL ${this._postData[key]}`);
- }
- return true;
- }
- return false;
- }
- default:
- {
- // Treat falsy values as equivalent (!= doesn't do the trick here)
- if (!this._postData[key] && !this.malData[key]) {
- return false;
- }
- if (this._postData[key] !== this.malData[key]) {
- if (debug) {
- this.log.debug(`${this.alData.title} ${key} differs; MAL ${this.malData[key]} AL ${this._postData[key]}`);
- }
- return true;
- }
- return false;
- }
- }
- });
- }
- shouldAdd() {
- return !this.malData;
- }
- formData() {
- throw new Error("Method not implemented.");
- }
- createPostData() {
- throw new Error("Method not implemented.");
- }
- get type() {
- return this.alData.type;
- }
- get id() {
- return this.alData.id;
- }
- get title() {
- return this.alData.title;
- }
- get postData() {
- return this._postData;
- }
- }
- exports.BaseMALEntry = BaseMALEntry;
- class MALEntryAnime extends BaseMALEntry {
- constructor(al, mal, csrfToken = '', dom = Dom_1.default) {
- super(al, mal, csrfToken, dom);
- }
- createPostData() {
- const result = this.createBaseMALPostItem();
- result.anime_id = this.alData.id;
- if (this.alData.repeat)
- result.num_watched_times = this.alData.repeat;
- /* Setting num_watched_episodes */
- // If this is a new item, malData is undefined, so set count to 0
- // When the list refreshes the count will be available and be set then
- if (!this.malData) {
- result.num_watched_episodes = 0;
- return result;
- }
- // If malData.anime_num_episodes is 0, the show is currently airing;
- // We're forced to use AL's count even though that might be wrong
- if (this.malData.anime_num_episodes === 0) {
- result.num_watched_episodes = this.alData.progress;
- return result;
- }
- // If the show is completed, use MAL's count in case AL's count is different;
- // We don't want MAL showing higher or lower than their own count
- if (result.status === MALStatus.Completed) {
- result.num_watched_episodes = this.malData.anime_num_episodes;
- return result;
- }
- // Othewrise, use MAL's count as a max
- result.num_watched_episodes = Math.min(this.alData.progress, this.malData.anime_num_episodes);
- return result;
- }
- async formData() {
- const malFormData = new MALForm_1.MALForm(this.alData.type, this.alData.id);
- await malFormData.get();
- const formData = {
- anime_id: this.malData.anime_id,
- aeps: this.malData.anime_num_episodes || 0,
- astatus: this.malData.anime_airing_status,
- 'add_anime[status]': this._postData.status,
- 'add_anime[num_watched_episodes]': this._postData.num_watched_episodes || 0,
- 'add_anime[score]': this._postData.score || '',
- 'add_anime[start_date][month]': this._postData.start_date && this._postData.start_date.month || '',
- 'add_anime[start_date][day]': this._postData.start_date && this._postData.start_date.day || '',
- 'add_anime[start_date][year]': this._postData.start_date && this._postData.start_date.year || '',
- 'add_anime[finish_date][month]': this._postData.finish_date && this._postData.finish_date.month || '',
- 'add_anime[finish_date][day]': this._postData.finish_date && this._postData.finish_date.day || '',
- 'add_anime[finish_date][year]': this._postData.finish_date && this._postData.finish_date.year || '',
- 'add_anime[tags]': this.malData.tags || '',
- 'add_anime[priority]': malFormData.priority,
- 'add_anime[storage_type]': malFormData.storageType,
- 'add_anime[storage_value]': malFormData.storageValue,
- 'add_anime[num_watched_times]': this._postData.num_watched_times || 0,
- 'add_anime[rewatch_value]': malFormData.rewatchValue,
- 'add_anime[comments]': malFormData.comments,
- 'add_anime[is_asked_to_discuss]': malFormData.discussionSetting,
- 'add_anime[sns_post_type]': malFormData.SNSSetting,
- submitIt: 0,
- csrf_token: this.csrfToken,
- };
- if (this.alData.status === 'REPEATING') {
- formData['add_anime[is_rewatching]'] = 1;
- }
- return createMALFormData(formData);
- }
- }
- exports.MALEntryAnime = MALEntryAnime;
- class MALEntryManga extends BaseMALEntry {
- constructor(al, mal, csrfToken = '', dom = Dom_1.default) {
- super(al, mal, csrfToken, dom);
- }
- createPostData() {
- const result = this.createBaseMALPostItem();
- result.manga_id = this.alData.id;
- if (this.alData.repeat)
- result.num_read_times = this.alData.repeat;
- /* Setting num_read_chapters and num_read_volumes */
- // If this is a new item, malData is undefined, so set count to 0
- // When the list refreshes the count will be available and be set then
- if (!this.malData) {
- result.num_read_chapters = 0;
- result.num_read_volumes = 0;
- return result;
- }
- // If malData.manga_num_chapters is 0, the manga is still publishing;
- // We're forced to use AL's count even though that might be wrong
- if (this.malData.manga_num_chapters === 0) {
- result.num_read_chapters = this.alData.progress;
- result.num_read_volumes = this.alData.progressVolumes;
- return result;
- }
- // If the manga is completed, use MAL's count in case AL's count is different;
- // We don't want MAL showing higher or lower than their own count
- if (result.status === MALStatus.Completed) {
- result.num_read_chapters = this.malData.manga_num_chapters;
- result.num_read_volumes = this.malData.manga_num_volumes;
- return result;
- }
- // Othewrise, use MAL's count as a max
- result.num_read_chapters = Math.min(this.alData.progress, this.malData.manga_num_chapters);
- result.num_read_volumes = Math.min(this.alData.progressVolumes, this.malData.manga_num_volumes);
- return result;
- }
- async formData() {
- const malFormData = new MALForm_1.MALForm(this.alData.type, this.alData.id);
- await malFormData.get();
- const formData = {
- entry_id: 0,
- manga_id: this.malData.manga_id,
- 'add_manga[status]': this._postData.status,
- 'add_manga[num_read_volumes]': this._postData.num_read_volumes || 0,
- last_completed_vol: '',
- 'add_manga[num_read_chapters]': this._postData.num_read_chapters || 0,
- 'add_manga[score]': this._postData.score || '',
- 'add_manga[start_date][month]': this._postData.start_date && this._postData.start_date.month || '',
- 'add_manga[start_date][day]': this._postData.start_date && this._postData.start_date.day || '',
- 'add_manga[start_date][year]': this._postData.start_date && this._postData.start_date.year || '',
- 'add_manga[finish_date][month]': this._postData.finish_date && this._postData.finish_date.month || '',
- 'add_manga[finish_date][day]': this._postData.finish_date && this._postData.finish_date.day || '',
- 'add_manga[finish_date][year]': this._postData.finish_date && this._postData.finish_date.year || '',
- 'add_manga[tags]': this.malData.tags || '',
- 'add_manga[priority]': malFormData.priority,
- 'add_manga[storage_type]': malFormData.storageType,
- 'add_manga[num_retail_volumes]': malFormData.numRetailVolumes,
- 'add_manga[num_read_times]': this._postData.num_read_times || 0,
- 'add_manga[reread_value]': malFormData.rereadValue,
- 'add_manga[comments]': malFormData.comments,
- 'add_manga[is_asked_to_discuss]': malFormData.discussionSetting,
- 'add_manga[sns_post_type]': malFormData.SNSSetting,
- csrf_token: this.csrfToken,
- submitIt: 0
- };
- if (this.alData.status === 'REPEATING') {
- formData['add_manga[is_rewatching]'] = 1;
- }
- return createMALFormData(formData);
- }
- }
- exports.MALEntryManga = MALEntryManga;
- /***/ }),
- /* 8 */
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- exports.MALForm = void 0;
- const Util_1 = __webpack_require__(3);
- class MALForm {
- constructor(type, id) {
- this.document = null;
- this.type = type;
- this.id = id;
- }
- fetchDocument(type, id) {
- return new Promise((resolve, reject) => {
- const xhr = new XMLHttpRequest();
- xhr.onload = function () {
- return resolve(this.responseXML ? this.responseXML : null);
- };
- xhr.onerror = function (e) {
- reject(e);
- };
- xhr.open('GET', `https://myanimelist.net/ownlist/${type}/${id}/edit`);
- xhr.responseType = 'document';
- xhr.send();
- });
- }
- getElement(id) {
- if (!this.document)
- throw new Error('Document not loaded');
- return this.document.querySelector(`#add_${this.type}_${id}`);
- }
- async get() {
- await Util_1.sleep(500);
- const document = await this.fetchDocument(this.type, this.id);
- if (document) {
- this.document = document;
- }
- else {
- throw new Error('Unable to fetch form data');
- }
- }
- get priority() {
- const el = this.getElement('priority');
- if (!el)
- throw new Error('Unable to get priority');
- return el.value;
- }
- get storageType() {
- const el = this.getElement('storage_type');
- if (!el)
- throw new Error('Unable to get storage type');
- return el.value;
- }
- get storageValue() {
- const el = this.getElement('storage_value');
- if (!el)
- return '0';
- return el.value;
- }
- get numRetailVolumes() {
- const el = this.getElement('num_retail_volumes');
- if (!el)
- return '0';
- return el.value;
- }
- get rewatchValue() {
- const el = this.getElement('rewatch_value');
- if (!el)
- throw new Error('Unable to get rewatch value');
- return el.value;
- }
- get rereadValue() {
- const el = this.getElement('reread_value');
- if (!el)
- throw new Error('Unable to get reread value');
- return el.value;
- }
- get comments() {
- const el = this.getElement('comments');
- if (!el)
- throw new Error('Unable to get comments');
- return el.value;
- }
- get discussionSetting() {
- const el = this.getElement('is_asked_to_discuss');
- if (!el)
- throw new Error('Unable to get discussion value');
- return el.value;
- }
- get SNSSetting() {
- const el = this.getElement('sns_post_type');
- if (!el)
- throw new Error('Unable to get SNS setting');
- return el.value;
- }
- }
- exports.MALForm = MALForm;
- /***/ })
- /******/ ]);
- /************************************************************************/
- /******/ // The module cache
- /******/ var __webpack_module_cache__ = {};
- /******/
- /******/ // The require function
- /******/ function __webpack_require__(moduleId) {
- /******/ // Check if module is in cache
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
- /******/ if (cachedModule !== undefined) {
- /******/ return cachedModule.exports;
- /******/ }
- /******/ // Create a new module (and put it into the cache)
- /******/ var module = __webpack_module_cache__[moduleId] = {
- /******/ // no module.id needed
- /******/ // no module.loaded needed
- /******/ exports: {}
- /******/ };
- /******/
- /******/ // Execute the module function
- /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
- /******/
- /******/ // Return the exports of the module
- /******/ return module.exports;
- /******/ }
- /******/
- /************************************************************************/
- var __webpack_exports__ = {};
- // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
- (() => {
- var exports = __webpack_exports__;
- Object.defineProperty(exports, "__esModule", ({ value: true }));
- const Log_1 = __webpack_require__(1);
- const Dom_1 = __webpack_require__(4);
- const Anilist_1 = __webpack_require__(5);
- const MAL_1 = __webpack_require__(6);
- // Main business logic
- const sync = async (e) => {
- e.preventDefault();
- const anilistUsername = Dom_1.default.getAnilistUsername();
- if (!anilistUsername)
- return;
- const malUsername = Dom_1.default.getMALUsername();
- if (!malUsername) {
- Log_1.default.info('You must be logged in!');
- return;
- }
- const csrfToken = Dom_1.default.getCSRFToken();
- Log_1.default.clear();
- Log_1.default.info(`Fetching data from Anilist...`);
- const anilistList = await Anilist_1.getAnilistList(anilistUsername);
- if (!anilistList) {
- Log_1.default.info(`No data found for user ${anilistUsername}.`);
- return;
- }
- Log_1.default.info(`Fetched Anilist data.`);
- const mal = new MAL_1.default(malUsername, csrfToken);
- await mal.syncType('anime', anilistList.anime);
- await mal.syncType('manga', anilistList.manga);
- Log_1.default.info('Import complete.');
- };
- // Entrypoint
- (() => {
- 'use strict';
- Dom_1.default.addDropDownItem();
- if (window.location.pathname === '/import.php') {
- Dom_1.default.addImportForm(sync);
- }
- })();
- })();
- /******/ })()
- ;
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址