利用者:MawaruNeko/StoredMarkAdmins.js
表示
お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。
多くの Windows や Linux のブラウザ
- Ctrl を押しながら F5 を押す。
Mac における Safari
Mac における Chrome や Firefox
- ⌘ Cmd と ⇧ Shift を押しながら R を押す。
詳細についてはWikipedia:キャッシュを消すをご覧ください。
/*
* ローカルストレージを使用したマークアドミンのカスタムJS
* Custom JS of MarkAdmins using local storage
*
* 説明:
* 一定期間ごとにAPIで管理者一覧を自動更新し、
* ローカルストレージを使用することで、
* 手作業による更新が不要になった[[Help:マークアドミン]]です。
* カスタムJSとして導入して下さい。
*
* Description:
* MarkAdmins without manual updating by using local storage.
* Use this file as custom JS.
*
* Global variables:
* 以下のグローバル変数を、このスクリプトを読み込むより前に設定することで、
* このスクリプトの動作を制御できます。
* (default)
* mw.libs.storedMarkAdmins = {
* validTermInMs: 30 * 24 * 60 * 60 * 1000, // 30 days
* apiCallInterval: 1000, // 1000ms
* apiMaxTry: 10, // up to 5000 users for each role
* apiNotifyMessage: '$role一覧を取得中…', // substitute '$role' to role name, null to not to show notify
* };
* mw.libs.storedMarkAdmins.validTermInMs:
* 管理者一覧の自動更新の期間をミリ秒で指定します。
* この期間が過ぎると、APIで自動的に管理者一覧を更新します。
* mw.libs.storedMarkAdmins.apiCallInterval:
* API呼び出しの間隔をミリ秒で指定します。
* 例えば、1000(ms)を指定すると、1秒ずつ間を空けてAPIを呼び出します。
* mw.libs.storedMarkAdmins.apiMaxTry:
* 権限毎のAPIの呼び出し回数の上限です。
* 1回あたり500人まで取得できるため、10で管理者が5000人まで動作します。
* 小さすぎると、動作がおかしくなります。
* mw.libs.storedMarkAdmins.apiNotifyMessage:
* API呼び出し時にメッセージを表示しますが、そのメッセージを指定します。
* '$role'を権限名に置き換えます。
* nullを指定することで、メッセージを表示しないことができます。
*
* Local storage:
* storageKeyName で指定されたLocal storageを使用します。
*
* このファイルはパブリックドメインとします。
* This file is public domain.
*/
(function () {
'use strict';
/*
* =============================================================================
* Settings
* =============================================================================
*/
var rolesMap = {
'sysop': {name: '管理者', abbr: '(管)', global: false },
'bureaucrat': {name: 'ビューロクラット', abbr: '(ビ)', global: false },
'steward': {name: 'スチュワード', abbr: '(ス)', global: true },
'checkuser': {name: 'チェックユーザー', abbr: '(CU)', global: false },
'ombuds': {name: 'オンブズマン', abbr: '(オ)', global: true },
'eliminator': {name: '削除者', abbr: '(削)', global: false },
'rollbacker': {name: '巻き戻し者', abbr: '(巻)', global: false },
'interface-admin': {name: 'インターフェース管理者', abbr: '(イ)', global: false },
'abusefilter': {name: '編集フィルター編集者', abbr: '(フ)', global: false },
};
var storageKeyName = 'mwStoredMarkAdmins-usersInRole';
var userNamespaceId = 2;
/*
* =============================================================================
* Utilities
* =============================================================================
*/
// try to parse json as JSON
// returns object or null if failed
function tryParseJson(json) {
if (json) {
try {
return JSON.parse(json);
} catch (e) {
return null;
}
} else {
return null;
}
}
// execute deferreds in series
// keys: Array
// deferredFunc: Function, which has one arguments from each element of keys, and returns deferred object
// interval: sleeps interval milliseconds if not 0 or null
// returns deferred object
// deferred return value: Object, which keys are parameter keys and values are the values of deferred object deferredFunc returned
// e.g.
// DeferredSeries(['a', 'b'], function (key) {
// return $.Deferred().resolve(key).promise();
// }, 1000).then(function (result) {
// console.log(result);
// }); // => {a: 'a', b: 'b'}
function deferredSeries(keys, deferredFunc, interval) {
if (keys.length === 0) {
return $.Deferred().resolve({}).promise();
} else {
var key0 = keys[0];
var keysRest = keys.slice(1);
return deferredFunc(key0).then(function (result0) {
var deferred = $.Deferred();
function resolveRest() {
deferredSeries(keysRest, deferredFunc, interval).then(function (results) {
results[key0] = result0;
deferred.resolve(results);
});
}
if (interval && keysRest.length > 0) {
setTimeout(function () {
resolveRest();
}, interval);
} else {
resolveRest();
}
return deferred.promise();
});
}
}
// overwrites obj1
// deepMerge({a: {b: [2], c: 3}, d: {e: {f: [4, 5]}}, g: 6}, {a: {b: [7], c: 8}, d: {e: {f: [9, 10]}}, h: 11})
// => {a: {b: [2, 7], c: 8}, d: {e: {f: [4, 5, 9, 10]}}, g: 6, h: 11}
function deepMerge(obj1, obj2) {
$.each(obj2, function (key, value2) {
if (key in obj1) {
var value1 = obj1[key];
if (Array.isArray(value1)) {
if (Array.isArray(value2)) {
obj1[key] = value1.concat(value2);
} else {
value1.push(value);
}
} else if (typeof value1 === 'object') {
if (typeof value2 === 'object') {
deepMerge(value1, value2);
} else {
obj1[key] = value2;
}
} else {
obj1[key] = value2;
}
} else {
obj1[key] = value2;
}
});
return obj1;
}
/*
* =============================================================================
* Mediawiki API Utilities
* =============================================================================
*/
// iterate getting query api if request returned continue
// api: mw.Api
// options: Object, get options
// maxTry: integer, nullable (default 10), max of iterates count
// interval: integer, nullable (default 1000), milliseconds to sleep between each query
// deferred: jQuery.Deferred, nullable
// currentResult: Object, nullable, current query result
// returns deferred object
// deferred return value: query result (data.query)
function iterateQuery(api, options, maxTry, interval, deferred, currentResult) {
if (typeof (maxTry) !== 'number') {
maxTry = 10;
}
interval = interval || 1000;
deferred = deferred || $.Deferred();
currentResult = currentResult || {
};
if (maxTry === 0) {
deferred.reject('maxTry is 0');
return deferred;
}
api.get($.extend({
action: 'query',
}, options)).done(function (data) {
currentResult = deepMerge(currentResult, data.query);
if (data.continue ) {
setTimeout(function () {
iterateQuery(api, $.extend(options, data.continue ), maxTry - 1, interval, deferred, currentResult);
}, interval);
} else {
deferred.resolve(currentResult);
}
});
return deferred.promise();
}
function getAllUsers(api, augroup, maxTry, interval, aulimit) {
aulimit = aulimit || 'max';
var options = {
list: 'allusers',
augroup: augroup,
aulimit: aulimit,
};
return iterateQuery(api, options, maxTry, interval).then(function(query){
return $.Deferred().resolve(query.allusers).promise();
});
}
function getGlobalAllUsers(api, agugroup, maxTry, interval, agulimit) {
agulimit = agulimit || 'max';
var options = {
list: 'globalallusers',
agugroup: agugroup,
agulimit: agulimit,
};
return iterateQuery(api, options, maxTry, interval).then(function(query){
return $.Deferred().resolve(query.globalallusers).promise();
});
}
/*
* =============================================================================
* Getting users in each role
* =============================================================================
*/
var roles = Object.keys(rolesMap);
function getUsersInRoleFromApi(api, options, callback) {
deferredSeries(roles, function (role) {
if (options.apiNotifyMessage) {
mw.notify(options.apiNotifyMessage.replace('$role', rolesMap[role].name));
}
if (rolesMap[role].global) {
return getGlobalAllUsers(api, role, options.apiMaxTry, options.apiCallInterval);
} else {
return getAllUsers(api, role, options.apiMaxTry, options.apiCallInterval);
}
}, options.apiCallInterval).done(function (usersInRoleResult) {
var usersInRole = {};
$.each(usersInRoleResult, function (role, usersArray) {
usersInRole[role] = usersArray.map(function (userInfo) { return userInfo.name; });
});
callback(usersInRole);
});
}
function usersInRoleToStoredObject(usersInRole) {
return {
usersInRole: usersInRole,
updatedAt: Date.now(),
};
}
function getUsersInRoleFromStore(options) {
var json = mw.storage.get(storageKeyName);
var storedObject = tryParseJson(json);
if (storedObject && storedObject.updatedAt && (Date.now() < storedObject.updatedAt + options.validTermInMs)) {
return storedObject.usersInRole;
} else {
return null;
}
}
function getUsersInRole(api, options) {
var deferred = $.Deferred();
var usersInRoleFromStore = getUsersInRoleFromStore(options);
if (usersInRoleFromStore) {
deferred.resolve(usersInRoleFromStore);
} else {
getUsersInRoleFromApi(api, options, function(usersInRole){
mw.storage.set(storageKeyName, JSON.stringify(usersInRoleToStoredObject(usersInRole)));
deferred.resolve(usersInRole);
});
}
return deferred.promise();
}
/*
* =============================================================================
* Getting users in each role
* =============================================================================
*/
function getNamespacesFromId(config, namespaceId) {
return Object.keys(config.wgNamespaceIds).filter(function (id) {
return config.wgNamespaceIds[id] === namespaceId;
});
}
function getArticleRegexp(config) {
return new RegExp('^' + mw.RegExp.escape(config.wgArticlePath).replace(mw.RegExp.escape('$1'), '(.+)') + '$');
}
function usersInRoleToPageNames(usersInRole, userNamespaces) {
var pageNamesInRole = {};
$.each(usersInRole, function (role, users) {
// used as flat map
pageNamesInRole[role] = $.map(userNamespaces, function (namespace) {
return $.map(users, function (user) { return namespace + ':' + user; });
});
});
return pageNamesInRole;
}
function getNarrowingDownRegexpFromPageNamesInRole(pageNamesInRole) {
var mergedPageNames = $.uniqueSort($.map(Object.keys(pageNamesInRole), function (role) {
return pageNamesInRole[role];
}));
return new RegExp(mergedPageNames.map(function (pageName) {
return mw.RegExp.escape(mw.util.wikiUrlencode(pageName));
}).join('|'), 'i');
}
function markUsers(usersInRole, config, userNamespaceId) {
var userNamespaces = getNamespacesFromId(config, userNamespaceId);
var pageNamesInRole = usersInRoleToPageNames(usersInRole, userNamespaces);
var narrowingDownRegexp = getNarrowingDownRegexpFromPageNamesInRole(pageNamesInRole);
var articleRegexp = getArticleRegexp(config);
mw.util.$content.find('a[href]').filter(function () {
return narrowingDownRegexp.test($(this).attr('href'));
}).each(function () {
var $link = $(this);
var href = $link.attr('href');
var articleMatch = new RegExp(articleRegexp).exec(href);
var articleName = (articleMatch && articleMatch[1]) || (new mw.Uri(href)).query.title;
if (articleName) {
var title = new mw.Title(decodeURI(articleName));
if (title.getNamespaceId() === userNamespaceId) {
var userText = title.getMainText();
$.each(usersInRole, function (role, users) {
if (users.indexOf(userText) != - 1) {
$('<span>').addClass('mark-admins mark-admins-' + role).text(rolesMap[role].abbr).appendTo($link);
}
});
}
}
});
}
function main(config, options) {
var api = new mw.Api();
getUsersInRole(api, options).then(function (usersInRole) {
markUsers(usersInRole, config, userNamespaceId);
});
mw.util.addCSS(
'.mark-admins{ padding-left: 1ex; font-weight: bold; }\n' +
''
);
}
$(function () {
if (!('storedMarkAdmins' in mw.libs)) {
mw.libs.storedMarkAdmins = {
validTermInMs: 30 * 24 * 60 * 60 * 1000, // 30 days
apiCallInterval: 1000, // 1000ms
apiMaxTry: 10, // up to 5000 users for each role
apiNotifyMessage: '$role一覧を取得中…', // substitute '$role' to role name, null to not to show notify
};
}
mw.loader.using([ 'mediawiki.api', 'mediawiki.util', 'mediawiki.storage',
'mediawiki.Title', 'mediawiki.Uri', 'mediawiki.util']).then(function () {
var config = mw.config.get(['wgAction', 'wgNamespaceNumber', 'wgNamespaceIds', 'wgArticlePath']);
var isArticleView = (config.wgAction === 'view') && (config.wgNamespaceNumber === 0);
if ((!isArticleView) && (config.wgAction !== 'edit')) {
main(config, mw.libs.storedMarkAdmins);
}
});
});
}) ();