您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
bulk copy-paste all the chapters from Word on ficbook
当前为
- // ==UserScript==
- // @name ficUpdate
- // @namespace ficscript
- // @version 3.0.0
- // @description bulk copy-paste all the chapters from Word on ficbook
- // @author Dimava
- // @license MIT
- // @match https://ficbook.net/home/myfics*
- // @grant none
- // @require https://greasyfork.org/scripts/439153-poopjs/code/PoopJs.js?version=1012736
- // ==/UserScript==
- FicUpdate = class FicUpdate {
- debug = true;
- strings = {
- infoRoot: 'ⓘFicUpdate: Для обновления глав перейдите на страницу одного из фанфиков',
- infoEditor: 'ⓘFicUpdate: Скопируйте текст сюда',
- style: `
- .fu-container{display:grid;grid-template-areas: "buttons buttons" "infoEditor infoPrepared" "editor prepared";grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);overflow:hidden;}
- .fu-editor{background:white;border:1px dotted gray; grid-area:editor;}
- .fu-prepared{background:hsl(0,0%,95%);border:1px dotted gray;grid-area:prepared;}
- .fu-infoEditor{grid-area:infoEditor;}
- .fu-buttons{grid-area: buttons;}
- .fu-summary-buttons{display:inline-block;}
- .fu-rotate{animation:anim-fu-rotate 1s linear infinite;display:inline-block;}
- @keyframes anim-fu-rotate{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
- `,
- errNoHeaders: 'ⓘFicUpdate: Текст не содержит заголовков глав! (<H1>)',
- sucPrepared: 'ⓘFicUpdate: Текст подготовлен',
- infoStep: `ⓘFicUpdate: Преобразование текста`,
- infoPasted: `ⓘFicUpdate: Нажмите на эту кнопку чтобы подготовить текст`,
- statusTextSame: `ⓘтекст совпадает`,
- statusTextFromTo: (a, b) => `${a}ch↦${b}ch${b > a ? '' : ` (-${a - b})`}`,
- chReload: '⟳',
- chUpload: '⇑',
- chDownload: '⇓',
- };
- els = {};
- elm(s, ...a) {
- return elm(s+'.fic-update', ...a);
- }
- elmake(key, ...a) {
- if (key.startsWith('.fu-')) {
- return this.els[key.match(/\w{3,}/)[0]] = elm(key + '.fic-update', ...a);
- } else {
- return this.els[key] = elm(a[0] + '.fic-update', ...a.slice(1));
- }
- }
- constructor() {
- if (!location.pathname.startsWith('/home/myfics')) {
- return;
- }
- __init__;
- if (location.pathname == '/home/myfics') {
- this.elmake('.fu-infoRoot.btn.btn-info', this.strings.infoRoot);
- q('h1').after(this.els.infoRoot);
- return;
- }
- const els = this.els;
- this.elmake('.fu-container');
- q('.myfic').after(els.container);
- this.elmake('style', 'style').appendTo('head').innerHTML = this.strings.style;
- this.elmake('.fu-buttons').appendTo(els.container);
- this.elmake('.fu-infoEditor.btn.btn-info', this.strings.infoEditor
- , click=>this.prepareText()
- , paste=>this.onpaste).appendTo(els.container);
- this.elmake('.fu-editor').appendTo(els.container);
- els.editor.contentEditable = true;
- this.elmake('.fu-prepared').appendTo(els.container);
- }
- onpaste() {
- this.els.infoEditor.innerText = this.strings.infoPasted;
- }
- remove() {
- qq('.fic-update').map(e=>e.remove());
- }
- prepareTextStep(id, f) {
- let before = this.text;
- let after = f(before);
- this._textSteps[id] = {before, after};
- return this.text = after;
- }
- prepareText() {
- this.text = this.els.editor.innerHTML;
- this._textSteps = {init: this.text};
- this.refs = {};
- if (!this.text.match(/<h1/)) {
- this.els.infoEditor.innerText = this.strings.errNoHeaders;
- this.els.infoEditor.classList.toggle('btn-warning', true);
- return;
- }
- this.prepareTextStep('extractFootnotes1', t => {
- return t.replace(/<a\s+[^>]*name="_ftn[^]*?a>/g, s=>{
- let refm = s.match(/_ftn(ref)?(\d+)/);
- let refn = +refm[2];
- if (refm[1]) {
- refs[refn] = {
- n: refn,
- t: this.tosupnum(refn),
- };
- }
- return refm[1] ? '' : this.tosupnum(refn);
- });
- });
- this.prepareTextStep('extractFootnotes2', t => {
- return t.replace(/<div id="ftn(\d+)"[^]*?div>/g, (s,n)=>{
- this.refs[n].s = this.htmlToText(s).trim();
- return '';
- });
- });
- this.prepareTextStep('removeBadTags', t => {
- return t.replace(/<(?!\/?(h1|br|p|b|s|i|center|right)[\s|>])[^>]*>/g, '');
- });
- this.prepareTextStep('removeAttributes', t => {
- return t.replace(/<(\/?)(h1|br|b|s|i|center|right)(?=[\s|>])[^>]*>/g, '<$1$2>');
- });
- this.prepareTextStep('split', t => {
- return this.parts = t.split(/<h1[^>]*>/).map(e=>e.split('</h1>')).slice(1).map(([name, text])=>{
- name = this.htmlToText(name).replace(/\s+/g, ' ').trim()
- text = this.tabber(text);
- let o = {
- name,
- text,
- com: '',
- comp: true,
- refs: [],
- };
- text = text.replace(/\s*\/\*\s*([^]*?)\s*\*\/\s*/, (s,a,i,t)=>{
- o.com = a;
- o.comp = i > 100;
- return '';
- });
- let supi = 1;
- text = text.replace(/[⁰¹²³⁴⁵⁶⁷⁸⁹]+/g, (s,i,t)=>{
- let n = fromsupnum(s);
- let ref = refs[n];
- ref.n = supi;
- ref.t = tosupnum(supi);
- o.refs.push(ref);
- supi++;
- return ref.t;
- });
- o.text = text;
- return o;
- });
- });
- this.prepareTextStep('join', t => {
- return t
- .map(p => {
- let t = p.text.replace(/\n/g, '\n<br>');
- if (p.com || p.refs.length) {
- let com = p.com;
- let ref = p.refs.map(r=>r.t + this.htmlToText(' ') + r.s).join('\n<br>');
- let brc = com && ref ? '<br><br>' : '';
- t = p.comp ? `${t}<br><br><u>${ref}${brc}${com}</u>` : `<u>${com}${brc}${ref}</u><br><br>${t}`;
- }
- return `<details class="fu-chapter"><summary class="fu-summary" chapter="${p.name}">\n${p.name}\n</summary>${t}</details>\n`;
- })
- .join('\n');
- });
- this.prepareTextStep('display', t => {
- return this.els.prepared.innerHTML = t;
- });
- this.prepared = true;
- this.els.infoEditor.innerText = this.strings.sucPrepared;
- this.els.infoEditor.classList.toggle('btn-info', false);
- this.els.infoEditor.classList.toggle('btn-success', true);
- this.makePreparedButtons();
- }
- makePreparedButtons() {
- qq('.fu-summary').map(e => {
- let name = e.getAttribute('chapter');
- let chapter = this.parts.find(e => e.name == name);
- let a = qq('.parts .title a').find(e=>e.innerText == name);
- chapter.a = a;
- chapter.summary = e;
- console.log({chapter, a, name});
- chapter.buttons = elm('.fu-summary-buttons').appendTo(e);
- chapter.status = elm('sup.fu-chapter-status').appendTo(chapter.buttons);
- if (chapter.a) {
- elm('button.fu-sync-chapter', this.strings.chReload+this.strings.chDownload, click => {click.preventDefault(); this.syncChapter(chapter)})
- .appendTo(chapter.buttons);
- } else {
- chapter.status.innerText = this.strings.statusTextFromTo(0, chapter.text.length);
- elm('button.fu-chapter-make', this.strings.chUpload, click => {click.preventDefault(); this.makeChapter(chapter)})
- .appendTo(chapter.buttons);
- }
- });
- }
- async syncChapter(chapter) {
- console.log(chapter);
- chapter.summary.q('.fu-sync-chapter').classList.add('fu-rotate');
- console.log(window.chap = chapter);
- if (!chapter.a) {
- throw alert('wrong button!');
- }
- chapter.doc = await fetch.doc(chapter.a.href);
- chapter.summary.q('.fu-sync-chapter').remove();
- chapter.oldText = chapter.doc.q('textarea#content').value;
- console.log(chapter);
- chapter.isSame = chapter.text == chapter.oldText;
- chapter.status.innerText =
- chapter.isSame ? this.strings.statusTextSame: this.strings.statusTextFromTo(chapter.oldText.length, chapter.text.length);
- chapter.btnUpdate = elm('button.fu-chapter-update', this.strings.chUpload, click=>this.updateChapter(chapter));
- chapter.buttons.append(chapter.status);
- if (!chapter.isSame) {
- chapter.buttons.append(chapter.btnUpdate);
- }
- }
- async updateChapter(chapter) {
- console.log('upload', chapter);
- chapter.buttons.q('.fu-chapter-update').classList.add('fu-rotate');
- chapter.iframe = elm('iframe').appendTo(chapter.buttons);
- await this.iframeLoad(chapter.iframe, chapter.a.href);
- let ta = chapter.iframe.contentDocument.querySelector('textarea#content');
- ta.scrollIntoView();
- console.log('oldText: ', ta.value == chapter.oldText)
- if (ta.value != chapter.oldText) {
- alert('Error: can\'t update, chapter text has changed');
- throw new Error('oldText has changed!');
- }
- await Promise.frame(30);
- ta.value = chapter.text;
- console.log('text: ', ta.value == chapter.text);
- await Promise.frame(30);
- let bsave = chapter.iframe.contentDocument.querySelector('#save_part')
- bsave.scrollIntoView();
- await Promise.frame(30);
- await this.iframeLoad(chapter.iframe, () => bsave.click())
- console.log('frame loaded, chapter updated!');
- ta = chapter.iframe.contentDocument.querySelector('textarea#content');
- chapter.newText = ta.value;
- if (ta.value != chapter.text) {
- alert('Error: failed to update dunno why');
- throw new Error('failed to update dunno why!');
- }
- await Promise.frame(30);
- chapter.status.innerText = this.strings.statusTextSame;
- chapter.buttons.q('.fu-chapter-update').remove();
- chapter.iframe.remove();
- chapter.iframe = null;
- }
- async makeChapter(chapter) {
- console.log('upload', window.chap=chapter);
- chapter.buttons.q('.fu-chapter-make').classList.add('fu-rotate');
- chapter.iframe = elm('iframe').appendTo(chapter.buttons);
- let href = q('.add-part a[href*="addpart"]').href;
- await this.iframeLoad(chapter.iframe, href);
- let ta = chapter.iframe.contentDocument.querySelector('textarea#content');
- let ti = chapter.iframe.contentDocument.querySelector('#titleInput');
- let cb = chapter.iframe.contentDocument.querySelector('#not_published_chb');
- ti.scrollIntoView();
- ti.value = chapter.name;
- await Promise.frame(30);
- ta.scrollIntoView();
- await Promise.frame(30);
- ta.value = chapter.text;
- await Promise.frame(30);
- cb.scrollIntoView();
- cb.checked = true;
- await Promise.frame(30);
- let bsave = chapter.iframe.contentDocument.querySelector('button[type="submit"]')
- if (bsave?.innerText != 'Добавить часть') {
- alert('Кнопка не найдена, нажмите сами');
- }
- bsave?.scrollIntoView();
- await Promise.frame(30);
- await this.iframeLoad(chapter.iframe, () => bsave?.click());
- console.log('frame loaded, chapter updated!');
- await Promise.frame(30);
- chapter.status.innerText = this.strings.statusTextSame;
- chapter.buttons.q('.fu-chapter-make').remove();
- chapter.iframe.remove();
- chapter.iframe = null;
- }
- async iframeLoad(iframe, src='') {
- return new Promise(r => {
- iframe.addEventListener('load', r);
- if (src) {
- if (typeof src == 'string') iframe.src = src;
- if (typeof src == 'function') src(iframe);
- }
- });
- }
- htmlToText(h) {
- let a = document.createElement('a');
- a.innerHTML = h;
- return a.innerText;
- }
- tosupnum(t) {
- let num = '⁰¹²³⁴⁵⁶⁷⁸⁹'.split('');
- return (t + '').match(/\d/g).map(e=>num[e]).join('');
- }
- fromsupnum(t) {
- let num = '⁰¹²³⁴⁵⁶⁷⁸⁹'.split('');
- return +(t + '').match(/[⁰¹²³⁴⁵⁶⁷⁸⁹]/g).map(e=>num.indexOf(e)).join('');
- }
- tabber(s) {
- let hTexts = {};
- function hText(s) {
- if (hTexts[s])
- return hTexts[s];
- let a = document.createElement('a');
- a.innerHTML = s;
- return hTexts[s] = a.innerText;
- }
- const nbsp = '\xa0';
- //hText(' ');
- const emsp = '\u2003';
- //hText(' ');
- const ndash = '\u2013';
- //hText('–');
- const replacers = [
- [/\n/g, ' '],
- [/^\s+|\s+$/gm, '\n\n\n'],
- [/&[^;]{2,7};/g, hText],
- [/<br>|<.div><div[^>]*>|<.div>|<div[^>]*>/g, '\n'],
- [/<p[^>]*(center|right)[^>]*>([^]*?)<\/p>/g, '\n<$1>\n$2\n</$1>\n'],
- [/<\/p>\s*<p[^>]*>/g, '\n'],
- [/<\/p>\s*|\s*<p[^>]*>/g, '\n'],
- [/<script>[^]*?<.script>/, ''],
- [/\s*\n{4,}/g, '\n\n\n'], [/(\s*)(<(b|i|s)>)/g, '$2$1'],
- [/(^|[^\.])(…|\.{2,4}(?!\.))(?!\n\s)? /gm, '$1… '],
- [/(–|—|―)/gm, ' - '],
- [/--?(?![\-\wа-яёА-ЯЁ])|([^\-\wа-яёА-ЯЁ])-(?![\->\w])/g, `$1 - `],
- [/^((?=.)\s)*/gm, emsp + emsp],
- [/((?!\n)\s)+-\s+/gm, ' ' + ndash + nbsp],
- [/^\s*–/gm, emsp + nbsp + ndash],
- [/\n<center>\n([^]*?)\n<\/center>\n/g, s=>s.replace(/^\s*/gm, '')],
- [/\n<right>\n([^]*?)\n<\/right>\n/g, s=>s.replace(/^\s*/gm, '')],
- [/\s*<center>\s*([*][\s*]*[*])\s*<\/center>\n*|\n+\s*([*][\s*]*[*])\s*\n+/g, '\n\n\n<center>\n$1$2\n</center>\n\n'],
- [/\n(<.?(center|right)>)\n/g, '$1'],
- [/(<(b|i|s)>)(\s*)/g, '$3$1'],
- [/<(?!\/?(b|i|s|center|right))/g, '<'],
- [/^\s*\n|\n\s*$/g, '']
- ];
- replacers.forEach(rpl=>{
- s = s.replace(rpl[0], rpl[1]);
- });
- return s;
- }
- }
- window.ficUpdate = new FicUpdate();