User:Interaccoonale/DYKcheck.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// ***************************************************************** //
// DYKcheck tool //
// Version zh-1.1 //
// 要使用本脚本,请在您的个人js页面添加: //
// importScript('User:Interaccoonale/DYKcheck.js'); //
// 修改自英维[[en:User:Shubinator/DYKcheck.js]],原作者列于其历史 //
// 记录页面,以下内容为英维的原始注释: //
// ***************************************************************** //
// For quick installation, add //
// importScript('User:Shubinator/DYKcheck.js'); //
// to your vector.js //
// See [[en:User:Shubinator/DYKcheck]] for more info, including //
// configurable options and how to use the tool without installation //
// or logging in. //
// First version written by Shubinator in February 2009 //
// ***************************************************************** //
// 以下代码中的英文注释为英维原作者加入,中文注释为修改时添加。 //
mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
"use strict";
// 匹配CJK字符
// 注:参照StackOverflow的这个帖子来看:https://stackoverflow.com/questions/15389497
// JS不支持在正则表达式中使用基本多文种平面外的字符。
const cjkRegex = /[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF]/g;
// 匹配非文字符号,包括标点符号和其它特殊字符,参见条目:[[Unicode字符列表]]
const symbolRegex = /[\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u00A1-\u00BF\u00D7\u00F7\u2000-\u2BFF\u2E00-\u2E7F\u3001-\u303F\uFE30-\uFE4F\uFF01-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5b-\uFF65\uFF9E\uFF9F\uFFE0-\uFFE6\uFFE8-\uFFEE]/g;
const spaceRegex = /\s+/g;
// 条目扩充的比例,例如英维应该是5,中维要求「扩充量达修订期之前原文的2/3以上」
// 也就是意味着1.67倍。
const expansionScale = 1.67;
const sizeLowerBound = 1500;
const stubCat = 'Category:全部小作品';
const spdCat = 'Category:快速删除候选';
const afdCat = 'Category:条目删除候选';
const alertCat = 'Category:拒绝当选首页新条目推荐栏目的条目';
// dates 分别为:[创建页面或成为非重定向的时间、移动至主命名空间的时间、最后中断7日时间、最近一次获评DYK时间(英维原为获评GA时间)]
let currentTitle, currentBytes, includeList = false, includeTable = false, dates;
// Configurable options
const unlock = window.unlock;
const mwConfig = mw.config.get([
"wgAction",
"wgTitle",
"wgPageName",
"wgUserName",
"wgNamespaceNumber",
"skin"
]);
const api = new mw.Api();
// Polyfill String.prototype.includes so that we don't have to write "!== -1" all over
// the codebase.
if (!String.prototype.includes) {
String.prototype.includes = function () {
return String.prototype.indexOf.apply(this, arguments) !== -1;
};
}
function escapeHtml(s) {
// Use the browser's built-in ability to escape HTML.
const div = document.createElement('div');
div.appendChild(document.createTextNode(s));
return div.innerHTML;
}
function scanArticle(title, output, html) {
// the meat of the DYKcheck tool
// calculates prose size of the given html
// checks for inline citations and stub templates in the given html
// passes info to checkTalk(), getFirstRevision(), checkMove(), and checkExpansion()
// includeTable = window.confirm("是否要将条目中的表格计入字数?");
// includeList = window.confirm("是否要将条目中的点列式列表计入字数?");
dates = new Array(4);
// calculate prose character size
let [bytes, chars, words] = calculateProse(html, true);
currentBytes = bytes;
addLine(output, "dyk-prose", '<b>正文长度为</b>' + escapeHtml(currentBytes) + '个字节/位元组,' +
escapeHtml(chars) + '个字符,' + escapeHtml(words) + '个字及外文单词(按照可读散文长度计算)',
currentBytes < sizeLowerBound ? "pink" : "")
// check for inline citations
if (!html.innerHTML.includes('id="cite_ref-') && !html.innerHTML.includes('id=cite_ref-')) {
addLine(output, "no-ref", '条目缺少<b>内文引证</b>', 'pink')
}
// 检查顺序为:创建时间、页面移动历史、主页面质量问题、讨论页、扩充长度
// find creator of article and date
checkArticleCreate(title, output);
}
function addLine(output, id, inner, color) {
// 添加新的信息
const element = document.createElement("li");
element.id = id;
output.appendChild(element);
element.innerHTML = inner;
if (color !== '') {
element.style["background-color"] = color;
}
}
function checkDocument() {
// prepares for scan and passes info to scanArticle()
if (document.getElementById("dyk-stats-0")) {
// 移除之前的检测结果。
clearStats();
} else {
const output = document.createElement("ul");
output.id = "dyk-stats-0";
const body = getBody();
let dummy = body.getElementsByTagName("div")[0];
if (dummy.nextSibling && dummy.nextSibling.id === 'siteNotice') { // if siteNotice is below siteSub
dummy = dummy.nextSibling;
} else if (dummy.nextSibling.nextSibling && dummy.nextSibling.nextSibling.id === 'siteNotice') {
dummy = dummy.nextSibling.nextSibling;
}
dummy.parentNode.insertBefore(output, dummy.nextSibling);
createHeaderAndProcessing(output);
currentTitle = 0;
let title = mwConfig.wgTitle;
if (mwConfig.wgNamespaceNumber === 2) {
title = "User:" + title;
}
scanArticle(title, output, body);
}
}
function clearStats() {
// if scan results already exist, turn them off and remove highlighting
const oldStyle = document.getElementById("dyk-stats-0").className;
const mainContent = getBody();
const pList = mainContent.getElementsByTagName("p");
for (let iPara = 0; iPara < pList.length; iPara++) {
if (pList[iPara].parentNode === mainContent || pList[iPara].parentNode.parentNode === mainContent) {
pList[iPara].style.cssText = oldStyle;
}
}
if (document.getElementById("error-disp")) {
const errorDisp = document.getElementById("error-disp");
errorDisp.parentNode.removeChild(errorDisp);
}
let iStat = 0;
while (document.getElementById("dyk-stats-" + iStat)) {
const output = document.getElementById("dyk-stats-" + iStat);
output.parentNode.removeChild(output);
iStat++;
}
if (document.getElementById("hook-container")) {
const hookOutput = document.getElementById("hook-container");
hookOutput.parentNode.removeChild(hookOutput);
}
if (document.getElementById("dyk-header")) {
const header = document.getElementById("dyk-header");
header.parentNode.removeChild(header);
}
if (document.getElementById("dyk-processing")) {
const processing = document.getElementById("dyk-processing");
processing.parentNode.removeChild(processing);
}
}
function calculateProse(doc, visible) {
// calculates the prose of a given document
// this function and its helper below are modified versions of
// the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
let pList = doc.getElementsByTagName("p");
let prose = '';
let i = 0;
if (mwConfig.wgAction === 'submit' && visible) i = 1; // Avoid the "Remember that this is only a preview" text
for (; i < pList.length; i++) {
if (pList[i].parentNode.parentNode === doc || pList[i].parentNode.parentNode.parentNode.id === getBodyId()) {
prose += getReadable(pList[i], visible);
if (visible) {
pList[i].style.cssText = 'background-color:yellow';
}
}
}
prose = prose.replace(symbolRegex, "");
let bytes = (new TextEncoder().encode(prose)).length;
let chars = prose.length;
let words = 0;
// 计算CJK字数,一个字符算作一字
words += (prose.match(cjkRegex)?.join('') || '').length;
// 计算非CJK字符的单词数,以空格或CJK字符隔开,一个单词算作一字
words += prose.replace(cjkRegex, ' ').split(spaceRegex).length;
return [bytes, chars, words];
}
function getReadable(para, visible) {
// helper method for calculateProse()
let textReadable = '';
for (let i = 0; i < para.childNodes.length; i++) {
if (para.childNodes[i].nodeName === '#text') {
textReadable += para.childNodes[i].nodeValue;
} else if (para.childNodes[i].className !== 'reference' &&
!(para.childNodes[i].className && para.childNodes[i].className.includes('emplate')) &&
para.childNodes[i].id !== 'coordinates' && !para.childNodes[i].className.includes('noprint')) {
textReadable += getReadable(para.childNodes[i], visible);
} else if (visible) { // if it's an inline maintenance tag (like [citation needed]) or geocoordinates
if (document.getElementById("dyk-stats-0").className) {
para.childNodes[i].style.cssText = document.getElementById("dyk-stats-0").className;
} else {
para.childNodes[i].style.cssText = 'background-color:white';
}
}
}
return textReadable;
}
function checkExpansion(title, output, lastDYKDate) {
// finds the start of expansion date (last 500 edits)
// gets the last 500 unique revision ids for past revisions of the article and passes to helper function
const promise = getRevisions({
titles: title,
rvlimit: 500,
rvend: (lastDYKDate || new Date(0)).toISOString(),
rvprop: ['ids', 'timestamp', 'sha1'],
rvdir: 'older'
});
promise.done(function (revisions) {
const lastBreak = findLastSevenDayBreak(revisions, output, lastDYKDate);
if (lastBreak) {
checkExpansionHelper(output, lastBreak.revid);
} else if (lastDYKDate) {
checkExpansionFromDYKBreak(title, output, lastDYKDate)
} else {
doneProcessing(output, null);
}
});
promise.fail(function (error) {
doneProcessing(output, error);
})
}
function findLastSevenDayBreak(revisions, output, lastDYKDate) {
const lastTimestamp = new Date(revisions[0].timestamp);
const nowTimestamp = new Date();
const timeDifferenceInDays0 = (nowTimestamp - lastTimestamp) / (1000 * 60 * 60 * 24);
if (timeDifferenceInDays0 >= 7) {
addLine(output, "last-break", '最后编辑于' +
escapeHtml(toNormalDate(revisions[0].timestamp.substring(0, 10))) + ',至今超过7日未被编辑,<b>失去推荐资格</b>。',
"pink");
return null;
}
// 找到最后一次时间间隔大于七天的元素
for (let i = 0; i < revisions.length - 1; i++) {
const currentTimestamp = new Date(revisions[i].timestamp);
const previousTimestamp = new Date(revisions[i + 1].timestamp);
const timeDifferenceInDays1 = (currentTimestamp - previousTimestamp) / (1000 * 60 * 60 * 24);
if (timeDifferenceInDays1 >= 7) {
addLine(output, "last-break", '条目于' +
escapeHtml(toNormalDate(revisions[i + 1].timestamp.substring(0, 10))) + '至' +
escapeHtml(toNormalDate(revisions[i].timestamp.substring(0, 10))) + '超过7日未被编辑,视为一次<b>中断</b>。',
"");
dates[2] = previousTimestamp;
return revisions[i + 1];
}
}
addLine(output, "last-break", '条目' + (revisions.length < 500 ? ('自' + (lastDYKDate ? '上次通过推荐' : '创建') + '至今') : '最近500笔编辑内') + '没有超过7日的中断', "");
}
function checkExpansionFromDYKBreak(title, output, lastDYKDate) {
const promise = getRevisions({
titles: title,
rvlimit: 3,
rvstart: lastDYKDate.toISOString(),
rvprop: ['ids', 'timestamp', 'sha1'],
rvdir: 'older'
});
promise.done(function (revisions) {
if (revisions && revisions.length > 0) {
checkExpansionHelper(output, revisions[0].revid);
}
});
promise.fail(function (error) {
doneProcessing(output, error);
})
}
function checkExpansionHelper(output, revid) {
const promise = api.get({
format: 'json',
action: 'parse',
oldid: revid,
prop: 'text'
});
promise.done(function (obj) {
const expandTemp = document.createElement("div");
expandTemp.id = "expand-temp";
expandTemp.innerHTML = obj.parse.text['*'];
let [bytes, , ] = calculateProse(expandTemp, false);
// alert("Prose: " + prose + " 1x: " + current/5 + " Mid: " + mid + " Expand index: " + expandIndex);
// use above line to debug the expansion check
const scale = (currentBytes / bytes).toFixed(3)
if (scale > expansionScale) {
addLine(output, "expansion-check", '条目当前版本被扩充至上次中断时的' + scale + '倍,<b>符合扩充要求</b>', "");
} else {
addLine(output, "expansion-check", '条目当前版本被扩充至上次中断时的' + scale + '倍,<b>不符合扩充要求</b>', "pink");
}
doneProcessing(output, null);
});
promise.fail(function (error) {
doneProcessing(output, error);
});
}
function checkTalk(title0, output, stubChecked) {
let title;
// checks the talk page of the article for DYK, ITN, or stub templates
if (mwConfig.wgNamespaceNumber !== 2) {
title = "Talk:" + title0;
} else {
title = title0.replace("User:", "User talk:");
}
let lastDYKDate;
const promise = getRevisions({
titles: title,
rvprop: 'content'
});
promise.done(function (revisions) {
if (revisions && revisions[0]) {
const talkPage = revisions[0]['*'];
if (!stubChecked && talkPage.match(/class\s*=\s*[sS]tub/) &&
(document.getElementById("stub-alert") === null)) {
addLine(output, "talk-stub", '条目在讨论页上被评级为<b>小作品</b>。', 'yellow');
}
const dykTalkRegexMatches = talkPage.match(/{{\s*[dD](yk|YK\s?)talk[^}]*}}/g);
const dykArticleHistoryMatches = talkPage.match(/dyk\d+date\s*=\s*(\d{4}(-|年\|?)\d{1,2}([-月])\d{1,2})/g);
for (const matches of [dykTalkRegexMatches, dykArticleHistoryMatches]) {
if (matches) {
// if there's a DYK tag, try to find the date of previous appearance
for (const match of matches) {
const dateStr = match.match(/\d{4}(-|年\|?)\d{1,2}([-月])\d{1,2}/)[0];
// 注:中维DYKtalk模板有两种日期表示方式:{{DYKtalk|date=2020-03-07}}和{{DYKtalk|2006年|6月18日}},
// 另外实测{{DYKtalk|date=2020-3-7}}也可用,这个正则应当能比较好地匹配这些情况。
const [year, month, day] = dateStr.split(/\D+/);
const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
if (!lastDYKDate || date > lastDYKDate) {
lastDYKDate = date;
dates[3] = date;
}
}
}
}
if (lastDYKDate) {
addLine(output, "talk-dyk", '条目于' + toNormalDateYMD(lastDYKDate.getFullYear(), lastDYKDate.getMonth() + 1, lastDYKDate.getDate()) + '最近一次获评DYK,视为一次中断。', "");
}
}
// check for expansion start date, assuming now expanded to 5x (last 500 edits)
checkExpansion(title0, output, lastDYKDate);
});
promise.fail(function (error) {
doneProcessing(output, error);
});
}
function getCategories(options) {
// 修改自`getRevisions`
// 参见:https://www.mediawiki.org/wiki/API:Categories
options = options || {};
return api.get({
format: 'json',
action: 'query',
prop: 'categories',
titles: options.titles,
cllimit: options.cllimit,
clcategories: options.clcategories,
}).then(
// On success
function (obj) {
return Object.values(obj.query.pages)[0].categories;
},
// On failure
function (err) {
alert("API error");
return err;
}
);
}
function checkAlerts(title, output) {
const promise = getCategories({
titles: title,
cllimit: 5,
clcategories: [stubCat, spdCat, afdCat, alertCat],
});
promise.done(function (categories) {
// check for various tags
let stubChecked = false;
if (categories) {
for (let iCat = 0; iCat < categories.length; iCat++) {
let category = categories[iCat];
switch (category.title) {
case stubCat:
addLine(output, "page-stub", '条目当前为<b>小作品</b>。', "yellow");
stubChecked = true;
break;
case spdCat:
addLine(output, "page-spd", '条目已被标记为<b>快速删除候选</b>。', "pink");
break;
case afdCat:
addLine(output, "page-afd", '条目已被提出<b>存废讨论</b>。', "pink");
break;
case alertCat:
addLine(output, "page-alert", '条目<b>存在不能获选为DYK的质量问题</b>。', "yellow");
break;
default:
break;
}
}
}
// check if article is stub or if it has appeared in DYK or ITN
checkTalk(title, output, stubChecked); //check talk page
});
promise.fail(function (error) {
doneProcessing(output, error);
});
}
function getRevisions(options) {
// Returns a jQuery promise with an array of a title's revisions.
// The first parameter is an options object accepting the following API fields:
// - titles
// - rvlimit
// - rvprop
// - rvdir
// - rvstart
options = options || {};
return api.get({
format: 'json',
action: 'query',
prop: 'revisions',
titles: options.titles,
rvlimit: options.rvlimit,
rvprop: options.rvprop,
rvdir: options.rvdir,
rvstart: options.rvstart,
rvend: options.rvend,
indexpageids: true
}).then(
// On success
function (obj) {
const pageId = obj.query.pageids[0];
return obj.query.pages[pageId].revisions;
},
// On failure
function (err) {
alert("API error");
return err;
}
);
}
function checkArticleCreate(title, output) {
// finds the creator of the article, and the date created
// also checks if the article was created as a redirect, finds non-redirect date if so
const promise = getRevisions({
titles: title,
rvlimit: 4,
rvprop: ['timestamp', 'user', 'content'],
rvdir: 'newer'
});
promise.done(function (revisions) {
const user = revisions[0].user;
const timestamp = revisions[0].timestamp;
addLine(output, "creation-info", '条目由<b>编者</b>' + escapeHtml(user) +
'<b>创建于</b>' + escapeHtml(toNormalDate(timestamp.substring(0, 10))), "");
dates[0] = toDateObject(timestamp);
for (let i = 0; i < revisions.length; i++) {
const content = revisions[i]['*'];
const isRedirect = content.toUpperCase().match(/#(REDIRECT|重定向)( ?)\[\[/g);
if (i === 0 && !isRedirect) {
break;
} else if (!isRedirect) {
if (i !== 0) {
const urUser = revisions[i].user;
const urTimestamp = revisions[i].timestamp;
addLine(output, "expanded-from-redirect", '条目由<b>编者</b>' + escapeHtml(urUser) + '在' +
escapeHtml(toNormalDate(urTimestamp.substring(0, 10))) + '<b>自重定向页面扩充为条目</b>。', "");
dates[0] = toDateObject(urTimestamp);
}
break;
}
}
// check if the article has been moved from userspace within last 100 edits
checkMove(title, output);
});
promise.fail(function (error) {
doneProcessing(output, error);
});
}
function checkMove(title, output) {
if (mwConfig.wgNamespaceNumber !== 0) {
// 检查条目是否是小作品、提删、速删、以及其他拒绝当选的问题
checkAlerts(title, output);
}
//checks the last 100 edits of an article for a move from userspace or AfC to current location
const promise = getRevisions({
titles: title,
rvlimit: 100,
rvprop: ['flags', 'user', 'timestamp', 'comment'],
rvdir: 'older',
});
promise.done(function (revisions) {
for (let i = 0; i < revisions.length; i++) {
const comment = revisions[i].comment;
const commentMatch = comment.match(/(移動|移动)(頁面|页面)?((\[\[User:)|(\[\[Draft:)|(\[\[Wikipedia talk:Articles for creation\/))[\s\S]*至\[\[/);
if ((revisions[i].minor === "") && commentMatch) {
const match0 = commentMatch[0];
const movedFrom = match0.substring(match0.indexOf("[[") + 2, match0.indexOf("]]至"));
const date = revisions[i].timestamp;
addLine(output, "moved-userspace", '条目原位于' + escapeHtml(movedFrom) + ',后于' +
escapeHtml(toNormalDate(date.substring(0, 10))) + '移动入主命名空间,视为条目创建时间。', "");
dates[1] = toDateObject(date);
break;
}
}
// 检查条目是否是小作品、提删、速删、以及其他拒绝当选的问题
checkAlerts(title, output);
});
promise.fail(function (error) {
doneProcessing(output, error);
});
}
function doneProcessing(output, error) {
// checks if all parts are done processing
// if they are, the dates of creation and expansion are checked for within 10 days (rounded down, in nominator's favor)
// then the next title (for multiple article noms) is processed (required to combat asynchronous threads)
// if there are no more titles left (or not on T:TDYK), the processing message is removed
if (document.getElementById("dyk-processing")) {
if (error) {
addLine(output, 'end-check', '检测遇到错误而中断,请重试,如果持续遇到类似情况,请<a href="//zh.wikipedia.org/wiki/' +
'User talk:Interaccoonale">点此</a>回报问题。', 'pink')
} else {
addLine(output, 'end-check', '检测完毕。<br><small>此脚本不能替代人工,条目遇到破坏、侵权验证等情况可能会导致被清空,' +
'回退破坏或将排除侵权嫌疑的内容重新恢复不能被视为扩充达标,条目中如有复制或拆分自其它条目的内容也应当予以排除,' +
'被完全重写的条目可能字数上没有较大的增减,而被检测为不符合扩充标准,请注意人工处理这些情况。</small>', '')
}
// 结束
const processing = document.getElementById("dyk-processing");
processing.parentNode.removeChild(processing);
}
}
// taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
function getBodyId() {
let contentName;
if (mwConfig.skin === 'monobook' || mwConfig.skin === 'chick' || mwConfig.skin === 'mymwConfig.skin' || mwConfig.skin === 'simple') {
contentName = 'bodyContent';
} else if (mwConfig.skin === 'modern') {
contentName = 'mw_contentholder';
} else if (mwConfig.skin === 'standard' || mwConfig.skin === 'cologneblue' || mwConfig.skin === 'nostalgia') {
contentName = 'article';
} else {
// fallback case; the above covers all currently existing skins
contentName = 'bodyContent';
}
// Same for all skins if previewing page
if (mwConfig.wgAction === 'submit') contentName = 'wikiPreview';
return contentName;
}
function getBody() {
// gets the HTML body of the page
// taken from the prosesize tool (http://en.wikipedia.org/wiki/User:Dr_pda/prosesize.js)
return document.getElementById(getBodyId());
}
function createHeaderAndProcessing(output) {
// makes the header above the scan results
const header = document.createElement("span");
header.id = "dyk-header";
header.innerHTML = '<br /><b>新条目推荐(DYK)候选条目检测:<small>(<a href="//en.wikipedia.org/wiki/' +
'User:Shubinator/DYKcheck">点此</a>了解详情)</small></b>';
output.parentNode.insertBefore(header, output);
const processing = document.createElement("span");
processing.id = "dyk-processing";
processing.innerHTML = '<br /><b><span style="color: crimson; "> 正在进行检测…… </span></b>';
// 这一信息最终会被移除,因此不要使用`addLine`
output.parentNode.insertBefore(processing, header);
}
function toNormalDate(utc) {
// 修改时直接将其改为中文日期格式
return toNormalDateYMD(utc.substring(0, 4), utc.substring(5, 7) * 1, utc.substring(8, 10) * 1);
}
function toNormalDateYMD(year, month, day) {
return year + '年' + month + '月' + day + '日';
}
function toDateObject(timestamp) {
// converts a Wikipedia timestamp to a Javascript Date object
const date = new Date();
date.setUTCFullYear(timestamp.substring(0, 4), timestamp.substring(5, 7) - 1, timestamp.substring(8, 10));
date.setUTCHours(timestamp.substring(11, 13), timestamp.substring(14, 16), timestamp.substring(17, 19));
return date;
}
window.dykCheck = function () {
// this function for casual use and anons
if (((mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') &&
(mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2)) || unlock) {
checkDocument();
}
};
function addToolbarPortletLink(func, tooltip) {
const link = mw.util.addPortletLink(
'p-cactions',
'#',
'DYK check',
't-dyk-check',
tooltip
);
$(link).click(function (e) {
e.preventDefault();
func();
});
}
// 在Wiki Tools里面加入DYK Check。
if (unlock || (
(mwConfig.wgAction === 'view' || mwConfig.wgAction === 'submit' || mwConfig.wgAction === 'purge') &&
(mwConfig.wgNamespaceNumber === 0 || mwConfig.wgNamespaceNumber === 2)
)) {
addToolbarPortletLink(checkDocument, 'Check if this article qualifies for DYK');
}
});