(function($) {

/*
 and Special:Contributions

/**
 Twinklefluff revert and antivandalism utility

Twinkle.fluff = function twinklefluff {
	if (mw.config.get('wgIsProbablyEditable')) {
		// Only proceed if the user can actually edit the page
		// in question .
		// wgIsProbablyEditable should take care of
		// namespace/contentModel restrictions as well as
		// explicit protections; it won't take care of
		// cascading or TitleBlacklist restrictions
		if (mw.config.get('wgDiffNewId') || mw.config.get('wgDiffOldId')) { // wgDiffOldId included for clarity in if else loop phab:T214985
			mw.hook('wikipage.diff').add(function { // Reload alongside the revision slider
				Twinkle.fluff.addLinks.diff;
			Twinkle.fluff.addLinks.oldid;
			Twinkle.fluff.addLinks.history;
		Twinkle.fluff.skipTalk = !Twinkle.getPref('openTalkPageOnAutoRevert');
		Twinkle.fluff.rollbackInPlace = Twinkle.getPref('rollbackInPlace');

		if (mw.config.get('wgCanonicalSpecialPageName') === 'Contributions') {
			Twinkle.fluff.addLinks.contributions;
			// Reload with recent changes updates
			// structuredChangeFilters.ui.initialized is just on load
			mw.hook('wikipage.content').add(function(item) {
				if (item.is('div')) {
					Twinkle.fluff.addLinks.recentchanges;

// A list of usernames, usually only bots, that vandalism revert is jumped
// over; that is, if vandalism revert was chosen on such username, then its
// target is on the revision before. This is for handling quick bots that
// makes edits seconds after the original edit is made. This only affects
// vandalism rollback; for good faith rollback, it will stop, indicating a bot
// has no faith, and for normal rollback, it will rollback that edit.
Twinkle.fluff.trustedBots = ['AnomieBOT', 'SineBot'];
Twinkle.fluff.skipTalk = null;
Twinkle.fluff.rollbackInPlace = null;
// String to insert when a username is hidden
Twinkle.fluff.hiddenName = '알 수 없는 사용자';

// Consolidated construction of fluff links
Twinkle.fluff.linkBuilder = {
	spanTag: function {
		var span = document.createElement('span');
		span.style.color = color;
		span.appendChild(document.createTextNode(content));
		return span;

	buildLink: function {
		var link = document.createElement('a');
		link.appendChild(Twinkle.fluff.linkBuilder.spanTag);
		link.appendChild(Twinkle.fluff.linkBuilder.spanTag);
		link.appendChild(Twinkle.fluff.linkBuilder.spanTag);
		link.href = '#';
		return link;

	/**
	rollbackLinks: function {
		var elem = inline ? 'span' : 'div';
		var revNode = document.createElement(elem);

		rev = parseInt;
		if (rev) {
			revNode.setAttribute;
			revNode.setAttribute;

		var normNode = document.createElement('strong');
		var vandNode = document.createElement('strong');

		var normLink = Twinkle.fluff.linkBuilder.buildLink;
		var vandLink = Twinkle.fluff.linkBuilder.buildLink;

		$(normLink).click(function {
			Twinkle.fluff.revert;
			Twinkle.fluff.disableLinks(revNode);
		$(vandLink).click(function {
			Twinkle.fluff.revert;
			Twinkle.fluff.disableLinks(revNode);

		vandNode.appendChild(vandLink);
		normNode.appendChild(normLink);

		var separator = inline ? ' ' : ' || ';

		if (!inline) {
			var agfNode = document.createElement('strong');
			var agfLink = Twinkle.fluff.linkBuilder.buildLink('DarkOliveGreen', '일괄 되돌리기 (선의의 편집)');
			$(agfLink).click(function {
				Twinkle.fluff.revert;
				// Twinkle.fluff.disableLinks(revNode); // rollbackInPlace not relevant for any inline situations
			agfNode.appendChild(agfLink);
			revNode.appendChild(agfNode);
		revNode.appendChild(document.createTextNode(separator));
		revNode.appendChild(normNode);
		revNode.appendChild(document.createTextNode(separator));
		revNode.appendChild(vandNode);

		return revNode;

	// Build [restore this revision] links
	restoreThisRevisionLink: function {
		// If not a specific revision number, should be wgDiffNewId/wgDiffOldId/wgRevisionId
		revisionRef = typeof revisionRef === 'number' ? revisionRef : mw.config.get(revisionRef);

		var elem = inline ? 'span' : 'div';
		var revertToRevisionNode = document.createElement(elem);

		revertToRevisionNode.setAttribute;
		revertToRevisionNode.style.fontWeight = 'bold';

		var revertToRevisionLink = Twinkle.fluff.linkBuilder.buildLink;
		$(revertToRevisionLink).click(function {
			Twinkle.fluff.revertToRevision(revisionRef);

		if (inline) {
			revertToRevisionNode.appendChild(document.createTextNode(' '));
		revertToRevisionNode.appendChild(revertToRevisionLink);
		return revertToRevisionNode;

Twinkle.fluff.addLinks = {
	contributions: function {
		// $('sp-contributions-footer-anon-range') relies on the fmbox
		// id in MediaWiki:Sp-contributions-footer-anon-range and
		// is used to show rollback/vandalism links for IP ranges
		var isRange = !!$('#sp-contributions-footer-anon-range')[0];
		if (mw.config.exists('wgRelevantUserName') || isRange) {
			// Get the username these contributions are for
			var username = mw.config.get('wgRelevantUserName');
			if (Twinkle.getPref('showRollbackLinks').indexOf('contribs') !== -1 ||
				(mw.config.get('wgUserName') !== username && Twinkle.getPref('showRollbackLinks').indexOf('others') !== -1) ||
				(mw.config.get('wgUserName') === username && Twinkle.getPref('showRollbackLinks').indexOf('mine') !== -1)) {
				var $list = $('#mw-content-text').find('ul li:has(span.mw-uctop):has(.mw-changeslist-diff)');

				$list.each(function {
					// revid is also available in the href of both
					// .mw-changeslist-date or .mw-changeslist-diff
					var page = $(current).find('.mw-contributions-title').text;

					// Get username for IP ranges (wgRelevantUserName is null)
					if (isRange) {
						// The :not is possibly unnecessary, as it appears that
						// .mw-userlink is simply not present if the username is hidden
						username = $(current).find('.mw-userlink:not(.history-deleted)').text;

					// It's unlikely, but we can't easily check for revdel'd usernames
					// since only a strong element is provided, with no easy selector phab:T255903
					current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks);

	recentchanges: function {
		if (Twinkle.getPref('showRollbackLinks').indexOf('recent') !== -1) {
			// Latest and revertable 
			var $list = $('.mw-changeslist .mw-changeslist-last.mw-changeslist-src-mw-edit');
			// Exclude top-level header if "group changes" preference is used
			// and find only individual lines or nested lines
			$list = $list.not('.mw-rcfilters-ui-highlights-enhanced-toplevel').find;

			$list.each(function {
				// The :not is possibly unnecessary, as it appears that
				// .mw-userlink is simply not present if the username is hidden
				var vandal = $(current).find('.mw-userlink:not(.history-deleted)').text;
				var href = $(current).find('.mw-changeslist-diff').attr('href');
				var rev = mw.util.getParamValue;
				var page = current.dataset.targetPage;
				current.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks);

	history: function {
		if (Twinkle.getPref('showRollbackLinks').indexOf('history') !== -1) {
			// All revs
			var histList = $('#pagehistory li').toArray;

			// On first page of results, so add revert/rollback
			// links to the top revision
			if (!$('.mw-firstlink').length) {
				var first = histList.shift;
				var vandal = $(first).find('.mw-userlink:not(.history-deleted)').text;

				first.appendChild(Twinkle.fluff.linkBuilder.rollbackLinks);

			// oldid
			histList.forEach(function(rev) {
				// From restoreThisRevision, non-transferable
				// If the text has been revdel'd, it gets wrapped in a span with .history-deleted,
				// and href will be undefined (and thus oldid is NaN)
				var href = rev.querySelector('.mw-changeslist-date').href;
				var oldid = parseInt(mw.util.getParamValue, 10);
				if (!isNaN(oldid)) {
					rev.appendChild(Twinkle.fluff.linkBuilder.restoreThisRevisionLink);


	diff: function {
		// Autofill user talk links on diffs with vanarticle for easy warning, but don't autowarn
		var warnFromTalk = function(xtitle) {
			var talkLink = $('#mw-diff-' + xtitle + '2 .mw-usertoollinks a').first;
			if (talkLink.length) {
				var extraParams = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm) + '&' + 'noautowarn=true';
				// diffIDs for vanarticlerevid
				extraParams += '&vanarticlerevid=';
				extraParams += xtitle === 'otitle' ? mw.config.get('wgDiffOldId') : mw.config.get('wgDiffNewId');

				var href = talkLink.attr('href');
				if (href.indexOf('?') === -1) {
					talkLink.attr;
					talkLink.attr;

		// Older revision
		warnFromTalk('otitle'); // Add quick-warn link to user talk link
		// Don't load if there's a single revision or weird diff (cur on latest)
		if (mw.config.get('wgDiffOldId') && (mw.config.get('wgDiffOldId') !== mw.config.get('wgDiffNewId'))) {
			// Add a [restore this revision] link to the older revision
			var oldTitle = document.getElementById('mw-diff-otitle1').parentNode;
			oldTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffOldId'), oldTitle.firstChild);

		// Newer revision
		warnFromTalk('ntitle'); // Add quick-warn link to user talk link
		// Add either restore or rollback links to the newer revision
		// Don't show if there's a single revision or weird diff (prev on first)
		if (document.getElementById('differences-nextlink')) {
			// Not latest revision, add [restore this revision] link to newer revision
			var newTitle = document.getElementById('mw-diff-ntitle1').parentNode;
			newTitle.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgDiffNewId'), newTitle.firstChild);
			// Normally .mw-userlink is a link, but if the
			// username is hidden, it will be a span with
			// .history-deleted as well. When a sysop views the
			// hidden content, the span contains the username in a
			// link element, which will *just* have
			// .mw-userlink. The below thus finds the first
			// instance of the class, which if hidden is the span
			// and thus text returns undefined. Technically, this
			// is a place where sysops *could* have more
			// information available to them , since the username will be available by
			// checking a.mw-userlink instead, but revert will
			// need reworking around userHidden
			var vandal = $('#mw-diff-ntitle2').find('.mw-userlink')[0].text;
			var ntitle = document.getElementById('mw-diff-ntitle1').parentNode;

			ntitle.insertBefore(Twinkle.fluff.linkBuilder.rollbackLinks(vandal), ntitle.firstChild);

	oldid: function { // Add a [restore this revision] link on old revisions
		var title = document.getElementById('mw-revision-info').parentNode;
		title.insertBefore(Twinkle.fluff.linkBuilder.restoreThisRevisionLink('wgRevisionId'), title.firstChild);

Twinkle.fluff.disableLinks = function disablelinks(parentNode) {
	// Array.from not available in IE11 :(
	$(parentNode).children.each(function {
		node.innerHTML = node.textContent; // Feels like cheating
		$(node).css.css;

Twinkle.fluff.revert = function revertPage {
	if (mw.util.isIPv6Address(vandal)) {
		vandal = Morebits.sanitizeIPv6(vandal);

	var pagename = page || mw.config.get('wgPageName');
	var revid = rev || mw.config.get('wgCurRevisionId');

	if (Twinkle.fluff.rollbackInPlace) {
		var notifyStatus = document.createElement('span');
		mw.notify(notifyStatus, {
			autoHide: false,
			title: '일괄 되돌리기 대상 문서: ' + page,
			tag: 'twinklefluff_' + rev // Shouldn't be necessary given disableLink
		Morebits.status.init(notifyStatus);
		Morebits.status.init(document.getElementById('mw-content-text'));
		$('#catlinks').remove;

	var params = {
		type: type,
		user: vandal || Twinkle.fluff.hiddenName,
		userHidden: !vandal, // Keep track of whether the username was hidden
		pagename: pagename,
		revid: revid

	var query = {
		'action': 'query',
		'prop': ['info', 'revisions', 'flagged'],
		'titles': pagename,
		'intestactions': 'edit',
		'rvlimit': Twinkle.getPref('revertMaxRevisions'),
		'rvprop': [ 'ids', 'timestamp', 'user' ],
		'curtimestamp': '',
		'meta': 'tokens',
		'type': 'csrf'
	var wikipedia_api = new Morebits.wiki.api;
	wikipedia_api.params = params;
	wikipedia_api.post;

Twinkle.fluff.revertToRevision = function revertToRevision(oldrev) {

	Morebits.status.init(document.getElementById('mw-content-text'));

	var query = {
		'action': 'query',
		'prop': ['info', 'revisions'],
		'titles': mw.config.get('wgPageName'),
		'rvlimit': 1,
		'rvstartid': oldrev,
		'rvprop': [ 'ids', 'user' ],
		'format': 'xml',
		'curtimestamp': '',
		'meta': 'tokens',
		'type': 'csrf'
	var wikipedia_api = new Morebits.wiki.api;
	wikipedia_api.params = { rev: oldrev };
	wikipedia_api.post;

Twinkle.fluff.userIpLink = function(user) {
	return (mw.util.isIPAddress(user) ? '' + user + '';

Twinkle.fluff.callbacks = {
	toRevision: function(apiobj) {
		var xmlDoc = apiobj.responseXML;

		var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
		var touched = $(xmlDoc).find('page').attr('touched');
		var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
		var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');
		var revertToRevID = parseInt($(xmlDoc).find('rev').attr('revid'), 10);

		var revertToUser = $(xmlDoc).find('rev').attr('user');
		var revertToUserHidden = typeof $(xmlDoc).find('rev').attr('userhidden') === 'string';

		if (revertToRevID !== apiobj.params.rev) {
			apiobj.statelem.error('검색한 판이 요청한 판과 일치하지 않습니다. 되돌리기를 중단합니다.');
			return;

		var optional_summary = prompt; // padded out to widen prompt in Firefox
		if (optional_summary === null) {
			apiobj.statelem.error('사용자에 의해 중단되었습니다.');
			return;

		var summary = Twinkle.fluff.formatSummary;

		var query = {
			'action': 'edit',
			'title': mw.config.get('wgPageName'),
			'summary': summary,
			'token': csrftoken,
			'undo': lastrevid,
			'undoafter': revertToRevID,
			'basetimestamp': touched,
			'starttimestamp': loadtimestamp,
			'watchlist': Twinkle.getPref('watchRevertedPages').indexOf('torev') !== -1 ? 'watch' : undefined,
			'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf('torev') !== -1 ? true : undefined

		Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName');
		Morebits.wiki.actionCompleted.notice = '되돌리기 완료';

		var wikipedia_api = new Morebits.wiki.api;
		wikipedia_api.params = apiobj.params;
		wikipedia_api.post;

	main: function(apiobj) {
		var xmlDoc = apiobj.responseXML;

		if (typeof $(xmlDoc).find('actions').attr('edit') === 'undefined') {
			apiobj.statelem.error("문서를 편집할 수 없습니다. 문서가 보호되어 있을 수 있습니다.");
			return;

		var lastrevid = parseInt($(xmlDoc).find('page').attr('lastrevid'), 10);
		var touched = $(xmlDoc).find('page').attr('touched');
		var loadtimestamp = $(xmlDoc).find('api').attr('curtimestamp');
		var csrftoken = $(xmlDoc).find('tokens').attr('csrftoken');

		var revs = $(xmlDoc).find('rev');

		var statelem = apiobj.statelem;
		var params = apiobj.params;

		if (revs.length < 1) {
			statelem.error('추가적인 판 수가 한 개 미만이므로 되돌릴 수 없습니다.');
			return;
		var top = revs[0];
		var lastuser = top.getAttribute('user');

		if (lastrevid < params.revid) {
			Morebits.status.error('Error', [ '서버로부터 받은 가장 최근의 판 ID ', Morebits.htmlNode, '는 표시된 판의 ID보다 더 작습니다. 현재의 판이 삭제되었거나 서버 지연이 있거나 잘못된 데이터를 수신했음을 의미할 수 있습니다. 되돌리기를 중단합니다.' ]);
			return;
		var index = 1;
		if (params.revid !== lastrevid) {
			Morebits.status.warn('Warning', [ '마지막 판 ', Morebits.htmlNode, '은 보유 중인 다음 판과 동일하지 않습니다: ', Morebits.htmlNode ]);
			if (lastuser === params.user) {
				switch (params.type) {
					case 'vand':
						Morebits.status.info('Info', [ '마지막 판 기여자는 ', Morebits.htmlNode, '님입니다. 문서 훼손으로 추정하고 되돌리기를 진행합니다.' ]);
						break;
					case 'agf':
						Morebits.status.warn('Warning', [ '마지막 판 기여자는 ', Morebits.htmlNode, '님입니다. 선의의 기여로 추정하고 되돌리기를 중단합니다. 문제가 수정되었을 수 있습니다.' ]);
						return;
					default:
						Morebits.status.warn('Notice', [ '마지막 판 기여자는 ', Morebits.htmlNode, '님이지만 되돌리기를 중단합니다.' ]);
						return;
					// Okay to test on user since it will either fail or sysop will correctly access it
					// Besides, none of the trusted bots are going to be revdel'd
					Twinkle.fluff.trustedBots.indexOf(top.getAttribute('user')) !== -1 && revs.length > 1 &&
					revs[1].getAttribute('pageId') === params.revid) {
				Morebits.status.info('Info', [ '마지막 판 기여자는 신뢰할 수 있는 봇 ', Morebits.htmlNode, '님이며 그 이전의 판은 문서 훼손이 있던 판이므로 되돌리기를 진행합니다.' ]);
				index = 2;
				Morebits.status.error('Error', [ '마지막 판 기여자는 ', Morebits.htmlNode, '님이므로 이미 되돌려졌을 수 있습니다. 되돌리기를 중단합니다.']);
				return;

		if (Twinkle.fluff.trustedBots.indexOf(params.user) !== -1) {
			switch (params.type) {
				case 'vand':
					Morebits.status.info('Info', [ '문서 훼손 되돌리기를 위해 선택된 사용자는 ', Morebits.htmlNode, '님입니다. 신뢰할 수 있는 봇이므로 당신은 이전 사용자의 문서 훼손을 되돌리고 싶어한 것으로 추정합니다.' ]);
					index = 2;
					params.user = revs[1].getAttribute('user');
					params.userHidden = revs[1].getAttribute('userhidden') === '';
					break;
				case 'agf':
					Morebits.status.warn('Notice', [ '선의의 되돌리기를 위해 선택된 사용자는 ', Morebits.htmlNode, '님입니다. 신뢰할 수 있는 봇이므로 선의의 봇 일괄 되돌리기는 진행하지 않습니다.' ]);
					return;
				case 'norm':
				/* falls through */
				default:
					var cont = confirm('일반 되돌리기가 선택되었으나 가장 최근의 편집은 신뢰할 수 있는 봇(' + params.user + ')에 의해 이루어졌습니다. 그 이전 판으로 되돌리시겠습니까?');
					if (cont) {
						Morebits.status.info('Info', [ '일반 되돌리기를 위해 선택된 사용자는 ', Morebits.htmlNode, '님입니다. 신뢰할 수 있는 봇이지만 확인하신 바에 따라 이전 판으로 되돌립니다.' ]);
						index = 2;
						params.user = revs[1].getAttribute('user');
						params.userHidden = revs[1].getAttribute('userhidden') === '';
						Morebits.status.warn('Notice', [ '일반 되돌리기를 위해 선택된 사용자는 ', Morebits.htmlNode, '님입니다. 신뢰할 수 잇는 봇이지만 확인하신 바에 따라 선택된 판으로 되돌리기를 진행합니다.' ]);
					break;
		var found = false;
		var count = 0;

		for (var i = index; i < revs.length; ++i) {
			++count;
			if (revs[i].getAttribute('user') !== params.user) {
				found = i;
				break;

		if (!found) {
			statelem.error([ '이전 판이 없습니다. 아마도 ', Morebits.htmlNode, '님이 유일한 기여자이거나, 사용자가 한 번에 ' + mw.language.convertNumber(Twinkle.getPref('revertMaxRevisions')) + '건을 초과하는 편집을 했습니다.' ]);
			return;

		if (!count) {
			Morebits.status.error;
			return;

		var good_revision = revs[found];
		var userHasAlreadyConfirmedAction = false;
		if (params.type !== 'vand' && count > 1) {
			if (!confirm(params.user + '님이 한 번에 편집 ' + mw.language.convertNumber(count) + '개를 했습니다. 이 편집을 모두 되돌리시겠습니까?')) {
				Morebits.status.info;
				return;
			userHasAlreadyConfirmedAction = true;

		params.count = count;

		params.goodid = good_revision.getAttribute('revid');
		params.gooduser = good_revision.getAttribute('user');
		params.gooduserHidden = good_revision.getAttribute('userhidden') === '';

		statelem.status([ ' revision ', Morebits.htmlNode, ' that was made ', Morebits.htmlNode('strong', mw.language.convertNumber(count)), ' revisions ago by ', Morebits.htmlNode ]);

		var summary, extra_summary;
		switch (params.type) {
			case 'agf':
				extra_summary = prompt('편집 요약 (선택사항): ', ''); // padded out to widen prompt in Firefox
				if (extra_summary === null) {
					statelem.error('사용자에 의해 중단되었습니다.');
					return;
				userHasAlreadyConfirmedAction = true;

				summary = Twinkle.fluff.formatSummary;
				break;

			case 'vand':
				summary = Twinkle.fluff.formatSummary(
					'$USER의 편집 ' + params.count + '개를 ' +
					(params.gooduserHidden ? Twinkle.fluff.hiddenName : params.gooduser) +
					'의 마지막 판으로 되돌림', params.userHidden ? null : params.user)
				break;

			case 'norm':
			/* falls through */
			default:
				if (Twinkle.getPref('offerReasonOnNormalRevert')) {
					extra_summary = prompt('편집 요약 (선택사항): ', ''); // padded out to widen prompt in Firefox
					if (extra_summary === null) {
						statelem.error('사용자에 의해 중단되었습니다.');
						return;
					userHasAlreadyConfirmedAction = true;

				summary = Twinkle.fluff.formatSummary;
				break;

		if (Twinkle.getPref('confirmOnFluff') && !userHasAlreadyConfirmedAction && !confirm('문서 되돌리기: 계속하시겠습니까?')) {
			statelem.error('사용자에 의해 중단되었습니다.');
			return;

		// Decide whether to notify the user on success
		if (!Twinkle.fluff.skipTalk && Twinkle.getPref('openTalkPage').indexOf(params.type) !== -1 &&
			params.notifyUser = true;
			// Pass along to the warn module
			params.vantimestamp = top.getAttribute('timestamp');

		// figure out whether we need to/can review the edit
		var $flagged = $(xmlDoc).find('flagged');
		if ((Morebits.userIsInGroup('reviewer') || Morebits.userIsSysop) &&
				$flagged.length &&
				$flagged.attr('stable_revid') >= params.goodid &&
				$flagged.attr('pending_since')) {
			params.reviewRevert = true;
			params.csrftoken = csrftoken;

		var query = {
			'action': 'edit',
			'title': params.pagename,
			'summary': summary,
			'token': csrftoken,
			'undo': lastrevid,
			'undoafter': params.goodid,
			'basetimestamp': touched,
			'starttimestamp': loadtimestamp,
			'watchlist': Twinkle.getPref('watchRevertedPages').indexOf(params.type) !== -1 ? 'watch' : undefined,
			'minor': Twinkle.getPref('markRevertedPagesAsMinor').indexOf(params.type) !== -1 ? true : undefined

		if (!Twinkle.fluff.rollbackInPlace) {
			Morebits.wiki.actionCompleted.redirect = params.pagename;
		Morebits.wiki.actionCompleted.notice = '되돌리기 완료';

		var wikipedia_api = new Morebits.wiki.api;
		wikipedia_api.params = params;
		wikipedia_api.post;

	complete: function (apiobj) {
		// TODO Most of this is copy-pasted from Morebits.wiki.page#fnSaveSuccess. Unify it
		var xml = apiobj.getXML;
		var $edit = $(xml).find('edit');

		if ($(xml).find('captcha').length > 0) {
			apiobj.statelem.error('위키 서버가 CAPTCHA를 요청하였으므로 일괄 되돌리기를 수행하지 못했습니다.');
			apiobj.statelem.error('되돌린 이후의 판이 현재의 판과 동일합니다. 되돌리기를 중단합니다.');
			apiobj.statelem.info('done');
			var params = apiobj.params;

			if (params.notifyUser && !params.userHidden) { // notifyUser only from main, not from toRevision
				Morebits.status.info('Info', [ '사용자를 위한 사용자 토론 문서 편집 폼을 여는 중: ', Morebits.htmlNode ]);

				var windowQuery = {
					'title': '사용자토론:' + params.user,
					'action': 'edit',
					'preview': 'yes',
					'vanarticle': params.pagename.replace,
					'vanarticlerevid': params.revid,
					'vantimestamp': params.vantimestamp,
					'vanarticlegoodrevid': params.goodid,
					'type': params.type,
					'count': params.count

				switch (Twinkle.getPref('userTalkPageMode')) {
					case 'tab':
						window.open(mw.util.getUrl, '_blank');
						break;
					case 'blank':
						window.open(mw.util.getUrl, '_blank',
							'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
						break;
					case 'window':
					/* falls through */
					default:
						window.open(mw.util.getUrl,
							window.name === 'twinklewarnwindow' ? '_blank' : 'twinklewarnwindow',
							'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800');
						break;

			// review the revert, if needed
			if (apiobj.params.reviewRevert) {
				var query = {
					'action': 'review',
					'revid': $edit.attr('newrevid'),
					'token': apiobj.params.csrftoken,
					'comment': Twinkle.getPref('summaryAd').trim
				var wikipedia_api = new Morebits.wiki.api;
				wikipedia_api.post;

// If builtInString contains the string "$USER", it will be replaced
// by an appropriate user link if a user name is provided
Twinkle.fluff.formatSummary = function {
	var result = builtInString;

	// append user's custom reason
	if (customString) {
		result += ': ' + Morebits.string.toUpperCaseFirstChar(customString);
	result += Twinkle.getPref('summaryAd');

	// find number of UTF-8 bytes the resulting string takes up, and possibly add
	// a contributions or contributions+talk link if it doesn't push the edit summary
	// over the 499-byte limit
	if (/\$USER/.test(builtInString)) {
		if (userName) {
			var resultLen = unescape(encodeURIComponent(result.replace)).length;
			var contribsLink = '' + userName + '';
			var contribsLen = unescape(encodeURIComponent(contribsLink)).length;
			if (resultLen + contribsLen <= 499) {
				var talkLink = '(토론)';
				if (resultLen + contribsLen + unescape(encodeURIComponent(talkLink)).length <= 499) {
					result = Morebits.string.safeReplace;
					result = Morebits.string.safeReplace;
				result = Morebits.string.safeReplace;
			result = Morebits.string.safeReplace;

	return result;

Twinkle.addInitCallback;