利用者:KJP-System/common.js

提供: KPOP JPWiki
2026年8月25日 (火) 20:28時点におけるKJP-System (トーク | 投稿記録)による版
ナビゲーションに移動 検索に移動

注意: 保存後、変更を確認するにはブラウザーのキャッシュを消去する必要がある場合があります。

  • Firefox / Safari: Shift を押しながら 再読み込み をクリックするか、Ctrl-F5 または Ctrl-R を押してください (Mac では ⌘-R)
  • Google Chrome: Ctrl-Shift-R を押してください (Mac では ⌘-Shift-R)
  • Microsoft Edge: Ctrl を押しながら 最新の情報に更新 をクリックするか、Ctrl-F5 を押してください。
/**
 * KJP Harness
 * Owner-side maintenance helpers for KPOP JPWiki.
 *
 * ResourceLoader-compatible version.
 * - No automatic edits
 * - dryRun by default
 * - exact-match validation
 * - keeps an in-memory backup of the last patch
 */

window.KJPHarness = {

	lastBackup: [],
	
	reloadCommonCss: function () {
	var id = 'kjp-harness-common-css';
	var old = document.getElementById(id);

	if (old) {
		old.parentNode.removeChild(old);
	}

	var link = document.createElement('link');
	link.id = id;
	link.rel = 'stylesheet';
	link.href =
		mw.util.getUrl('MediaWiki:Common.css', {
			action: 'raw',
			ctype: 'text/css',
			v: Date.now()
		});

	document.head.appendChild(link);

	console.log('KJP Harness: Common.css reloaded.');
},

wrapEntityPages: function (options) {
	var self = this;
	var api = new mw.Api();

	var pages = options.pages;
	var summary = options.summary || 'KJP Harness: wrap entity pages';
	var dryRun =
		typeof options.dryRun === 'boolean'
			? options.dryRun
			: true;

	var startFrom =
		'<includeonly>\n__NOTOC__\n__NOEDITSECTION__';

	var startTo =
		'<includeonly>\n__NOTOC__\n__NOEDITSECTION__\n<div class="kjp-entity-page">';

	var endFrom = '</includeonly>';
	var endTo = '</div>\n</includeonly>';

	var plan = [];
	var backups = [];
	var chain = Promise.resolve();

	pages.forEach(function (title) {
		chain = chain.then(function () {
			return self.getSource(title).then(function (page) {

				if (page.text.indexOf('class="kjp-entity-page"') !== -1) {
					plan.push({
						title: title,
						status: 'SKIP_ALREADY_DONE'
					});
					return;
				}

				var startCount =
					page.text.split(startFrom).length - 1;

				var endCount =
					page.text.split(endFrom).length - 1;

				if (startCount !== 1 || endCount !== 1) {
					plan.push({
						title: title,
						status: 'SKIP_STRUCTURE',
						startMatches: startCount,
						endMatches: endCount
					});
					return;
				}

				plan.push({
					title: title,
					status: dryRun ? 'READY' : 'PATCH'
				});

				if (dryRun) {
					return;
				}

				backups.push({
					title: title,
					revid: page.revid,
					text: page.text
				});

				var updated = page.text
					.replace(startFrom, startTo)
					.replace(endFrom, endTo);

				return api.postWithToken('csrf', {
					action: 'edit',
					title: title,
					text: updated,
					summary: summary,
					minor: true,
					basetimestamp: page.timestamp,
					formatversion: 2
				});
			}).catch(function (e) {
				plan.push({
					title: title,
					status: 'ERROR'
				});

				console.error(title, e);
			});
		});
	});

	return chain.then(function () {
		if (!dryRun) {
			self.lastBackup = backups;
		}

		console.table(plan);

		if (dryRun) {
			console.log(
				'DRY RUN: Entityページは変更されていません。'
			);
		} else {
			console.log(
				'WRAP完了。バックアップ: ' +
				backups.length +
				'件'
			);
		}

		return plan;
	});
},

	getSource: function (title) {
		var api = new mw.Api();

		return api.get({
			action: 'query',
			prop: 'revisions',
			rvprop: 'content|ids|timestamp',
			rvslots: 'main',
			titles: title,
			formatversion: 2
		}).then(function (data) {
			var page = data.query.pages[0];
			var revision = null;
			var text = '';

			if (page.revisions && page.revisions.length > 0) {
				revision = page.revisions[0];

				if (
					revision.slots &&
					revision.slots.main &&
					typeof revision.slots.main.content === 'string'
				) {
					text = revision.slots.main.content;
				}
			}

			return {
				title: page.title,
				pageid: page.pageid,
				revid: revision ? revision.revid : null,
				timestamp: revision ? revision.timestamp : null,
				text: text
			};
		});
	},

	inspect: function (pages, needle) {
		var self = this;
		var results = [];
		var chain = Promise.resolve();

		pages.forEach(function (title) {
			chain = chain.then(function () {
				return self.getSource(title).then(function (page) {
					var count = page.text.split(needle).length - 1;

					results.push({
						title: title,
						matches: count,
						revid: page.revid
					});
				}).catch(function (e) {
					results.push({
						title: title,
						matches: 'ERROR',
						revid: null
					});

					console.error(title, e);
				});
			});
		});

		return chain.then(function () {
			console.table(results);
			return results;
		});
	},

	bulkReplace: function (options) {
		var self = this;
		var api = new mw.Api();

		var pages = options.pages;
		var from = options.from;
		var to = options.to;

		var summary = options.summary || 'KJP Harness bulk patch';

		var expectedMatches =
			typeof options.expectedMatches === 'number'
				? options.expectedMatches
				: 1;

		var dryRun =
			typeof options.dryRun === 'boolean'
				? options.dryRun
				: true;

		var plan = [];
		var backups = [];
		var chain = Promise.resolve();

		pages.forEach(function (title) {
			chain = chain.then(function () {

				return self.getSource(title).then(function (page) {

					if (page.text.indexOf(to) !== -1) {
						plan.push({
							title: title,
							status: 'SKIP_ALREADY_DONE'
						});

						return;
					}

					var count = page.text.split(from).length - 1;

					if (count !== expectedMatches) {
						plan.push({
							title: title,
							status: 'SKIP_MATCH_COUNT',
							matches: count
						});

						return;
					}

					plan.push({
						title: title,
						status: dryRun ? 'READY' : 'PATCH'
					});

					if (dryRun) {
						return;
					}

					backups.push({
						title: title,
						revid: page.revid,
						text: page.text
					});

					var updated = page.text.replace(from, to);

					return api.postWithToken('csrf', {
						action: 'edit',
						title: title,
						text: updated,
						summary: summary,
						minor: true,
						basetimestamp: page.timestamp,
						formatversion: 2
					});
				}).catch(function (e) {
					plan.push({
						title: title,
						status: 'ERROR'
					});

					console.error(title, e);
				});
			});
		});

		return chain.then(function () {

			if (!dryRun) {
				self.lastBackup = backups;
			}

			console.table(plan);

			if (dryRun) {
				console.log('DRY RUN: Wikiは変更されていません。');
			} else {
				console.log(
					'PATCH完了。バックアップ: ' +
					backups.length +
					'件'
				);
			}

			return plan;
		});
	}
};

console.log('KJP Harness loaded.');

// KJP Harness: audit public guidance pages
KJPHarness.auditPublicPages = function () {
    var pages = [
        {
            title: 'Help:編集ガイド',
            required: ['編集の基本方針', '出典', 'やってはいけない編集']
        },
        {
            title: 'Help:出典ガイド',
            required: ['信頼性Tier', 'Evidence', 'S — Primary / Official']
        },
        {
            title: 'Project:方針',
            required: ['基本原則', '存命人物に関する情報', 'サイト管理']
        },
        {
            title: 'Project:画像利用方針',
            required: ['基本原則', 'フォトカード画像', '権利者からの申立て']
        },
        {
            title: 'KPOP JPWikiについて',
            required: ['KPOP JPWikiが目指すもの', 'EXPLORE', 'COMMUNITY']
        },
        {
            title: 'Project:お問い合わせ',
            required: ['サイトに関する問い合わせ', '著作権・画像・その他の権利に関する申立て', 'セキュリティ上の問題']
        }
    ];

    return Promise.all(
        pages.map(function (page) {
            return KJPHarness.getSource(page.title).then(function (source) {
                var missing = page.required.filter(function (text) {
                    return source.indexOf(text) === -1;
                });

                return {
                    page: page.title,
                    exists: source.length > 0,
                    length: source.length,
                    requiredSections: missing.length === 0 ? 'OK' : 'NG',
                    missing: missing.join(', ')
                };
            }).catch(function (error) {
                return {
                    page: page.title,
                    exists: false,
                    length: 0,
                    requiredSections: 'ERROR',
                    missing: String(error)
                };
            });
        })
    ).then(function (results) {
        console.table(results);
        return results;
    });
};