Module:Speedy
local getArgs = require("Module:Arguments").getArgs
local pageType = require("Module:Pagetype")
local mbox = require("Module:Message box")
local yesno = require("Module:Yesno")
local button = require('Module:Clickable button')
local preview = require('Module:If preview')
local p = {}
local config = mw.loadData('Module:Speedy/config')
local timeAgo = require('Module:Time ago')
----------------------------------------------------------------------------
-- message function from Module:Documentation
----------------------------------------------------------------------------
local fillStringWithArgs
local function message(cfgKey, valArray, expectType)
--[[
-- Gets a message from the cfg table and formats it if appropriate.
-- The function raises an error if the value from the cfg table is not
-- of the type expectType. The default type for expectType is 'string'.
-- If the table valArray is present, strings such as $1, $2 etc. in the
-- message are substituted with values from the table keys [1], [2] etc.
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
--]]
local msg = config.messages[cfgKey]
expectType = expectType or 'string'
if type(msg) ~= expectType then
error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
end
if not valArray then
return msg
end
return fillStringWithArgs(msg, valArray)
end
function fillStringWithArgs(text, valArray)
if not valArray then
return text
end
local function getVal(match)
match = tonumber(match)
return valArray[match] or ''
end
return mw.ustring.gsub(text, '$([1-9][0-9]*)', getVal) .. ''
end
local function detectParameters(text)
return text and mw.ustring.find(text, '$([1-9][0-9]*)') and true or false
end
local function makeUnorderedList(array)
local ul = mw.html.create('ul')
for k,v in pairs(array) do
local li = ul:tag('li')
li:wikitext(v)
li:done()
end
ul:allDone()
return tostring(ul)
end
local function makeWikiList(array)
local out = ''
for k,v in pairs(array) do
out = out .. '* ' .. v .. '\n'
end
return out
end
----------------------------------------------------------------------------
-- Argument processing (from Module:Documentation)
----------------------------------------------------------------------------
local function makeInvokeFunc(funcName)
return function (frame)
local args = getArgs(frame, {
valueFunc = function (key, value)
if type(value) == 'string' then
value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
if key == 'heading' or value ~= '' then
return value
else
return nil
end
else
return value
end
end
})
return p[funcName](args)
end
end
----------------------------------------------------------------------------
-- Miscellaneous functions related to speedy deletion
----------------------------------------------------------------------------
local function getDeletionEntry(code)
return config.deletionCodes[code]
end
local function yn(input, default)
local res = yesno(input, nil)
if (res == nil) then return default
else return res end
end
local function processDeletionArgs(iparams)
local args = {
deletionReasons = {},
deletionReasonsNotice = {},
entries = {},
numberOfEntries = 0,
hideButton = true,
highestMessage = 0,
drv = false,
willProvide = false,
hide = false,
blank = false
}
local entry = nil
local replaceParams = false
local params = {iparams.page or mw.title.getCurrentTitle().fullText}
local paramNo = 2
local skipped = true
local function cleanupLeftover(v)
if entry then
table.insert(args.deletionReasons, '' .. fillStringWithArgs(entry.description, params) .. '. ' .. (entry.more and ' ' .. entry.more .. ' ' or '') .. '' .. message('deleteIntroCriteriaLink', {entry.code}) .. '.')
table.insert(args.deletionReasonsNotice, fillStringWithArgs(entry.description, params) .. ' (CSD ' .. entry.code .. '). ' .. (entry.additionalMessage and fillStringWithArgs(entry.additionalMessage, params) or ''))
table.insert(args.entries, entry)
else
if (v ~= '') then
table.insert(args.deletionReasons, v)
table.insert(args.deletionReasonsNotice, v)
end
table.insert(args.entries, {})
end
params = {iparams.page or mw.title.getCurrentTitle().fullText}
paramNo = 2
args.customHeader = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customHeader or args.customHeader
args.customIntro = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntro or args.customIntroDeleted
args.customIntroDeleted = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customIntroDeleted or args.customIntroDeleted
args.customCloser = entry and ((entry.notice or 2) >= args.highestMessage) and entry.customCloser or args.customCloser
args.numberOfEntries = args.numberOfEntries + 1
args.hideButton = entry and (args.hideButton and (entry.notice or 2) == 0) or false
args.highestMessage = entry and ((entry.notice or 2) >= args.highestMessage) and entry.notice or args.highestMessage
args.drv = entry and (args.drv or entry.drv) or args.drv
args.willProvide = entry and entry.willProvide or args.willProvide
args.hide = entry and (entry.hide or args.hide) or args.hide
args.blank = entry and (entry.blank or args.blank) or args.blank
end
for k,v in ipairs(iparams) do
if type(k) == type(1) then
skipped = false
if (replaceParams) then
local pName = fillStringWithArgs(entry and entry.inputFormat[paramNo - 1] or '$2', {iparams.page or mw.title.getCurrentTitle().fullText, v})
paramNo = paramNo + 1
table.insert(params, pName)
else
entry = getDeletionEntry(v) or nil
replaceParams = entry and detectParameters(entry.description) or false
end
if not replaceParams then
cleanupLeftover(v)
end
end
end
if replaceParams then
cleanupLeftover('')
end
if skipped then
args.hideButton = false
end
args.help = yn(iparams.help, true)
args.nocat = yn(iparams.nocat, false)
args.bot = yn(iparams.bot, false)
args.noHeader = yn(iparams.noheader, false)
args.additionalNote = iparams.additionalnote
args.pageName = iparams.page
args.notice = yn(iparams.notice, false)
args.date = iparams.date or mw.getCurrentFrame():preprocess('{{safesubst:REVISIONTIMESTAMP}}')
return args
end
local isSubstituted = mw.isSubsting;
----------------------------------------------------------------------------
-- Entry point
----------------------------------------------------------------------------
p.main = makeInvokeFunc('_main')
function p._main(params)
-- get page
local args = processDeletionArgs(params)
local out = ''
if args.notice then
-- we are handling a note for the talk page, not a note for deletion
if not args.pageName then
return preview._warning({'No page name specified. Proceeding will do nothing.'})
end
if not args.noHeader then
out = out .. (args.customHeader and '== ' .. fillStringWithArgs(args.customHeader, {args.pageName}) .. ' ==' or "== " .. message("noticeHeader", {args.pageName}) .. " ==") .. '\n'
end
local messageType = args.highestMessage == 1 and 'welcome' or 'notice'
out = out .. (message('level' .. args.highestMessage .. 'icon') == and or '40px ')
if mw.title.new(args.pageName).exists then
-- if the page exists then show the "page may be deleted" message.
if args.customIntro then
out = out .. fillStringWithArgs(args.customIntro, {
args.pageName,
args.deletionReasonsNotice[1]
}) .. '\n\n'
elseif args.numberOfEntries == 1 then
out = out .. message(messageType .. 'Message', {
args.pageName,
args.deletionReasonsNotice[1]
}) .. '\n\n'
elseif args.numberOfEntries > 1 then
out = out .. message(messageType .. 'MessageMultiple', {
args.pageName,
'\n' .. makeWikiList(args.deletionReasonsNotice)
}) .. '\n'
else
out = out .. message(messageType .. 'MessageMultiple', {
args.pageName,
message("seePageForWhy")
}) .. '\n\n'
end
if args.customCloser then
out = out .. args.customCloser
else
-- this ifexist checks if the page has possibly been deleted or moved
-- ifexist is expensive so it may return false from time to time, even if the page exists
-- fortunately this is handled in the message by saying "appears" rather than "has been"
out = out .. '{{#ifexist:' .. args.pageName .. '|'
if args.hideButton then
out = out .. message("removeSpeedyMessage", {args.pageName}) .. ' '
else
out = out .. message("contestMessage", {args.pageName, '{{button|' .. message("contestButton") .. '}}'}) .. ' '
out = out .. message('closingWarning', {message('removeSpeedyWarning')}) .. ' '
end
if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageIsDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
out = out .. '|'
out = out .. message('deletedAfterMessage') .. ' '
if not args.hideButton then out = out .. message('closingWarning', {message('recreateWarning')}) .. ' ' end
if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageShouldNotHaveBeenDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
out = out .. '}}'
end
else
-- if the page does not exist then show the "page has been deleted" message
if args.customIntroDeleted then
out = out .. fillStringWithArgs(args.customIntroDeleted, {
args.pageName,
args.deletionReasonsNotice[1]
}) .. '\n\n'
elseif args.numberOfEntries == 1 then
out = out .. message(messageType .. 'MessageDeleted', {
args.pageName,
args.deletionReasonsNotice[1]
}) .. '\n\n'
elseif args.numberOfEntries > 1 then
out = out .. message(messageType .. 'MessageDeletedMultiple', {
args.pageName,
'\n' .. makeWikiList(args.deletionReasonsNotice)
}) .. '\n\n'
else
out = out .. message(messageType .. 'MessageDeletedMultiple', {
args.pageName,
message("seePageForWhy")
}) .. '\n\n'
end
if args.customCloser then
out = out .. args.customCloser
else
out = out .. message('deletedAfterMessage')
if not args.hideButton then out = out .. message('closingWarning', {message('recreateWarning')}) .. ' ' end
if args.willProvide then out = out .. message('undeleteSuggestion', {message('pageShouldNotHaveBeenDeleted'), args.pageName, message(args.drv and 'requestDeletionReview' or 'requestUndeletion')}) end
end
end
return mw.getCurrentFrame():preprocess(out)
else
-- we are handling a deletion template message
if isSubstituted() then
-- if substituted then just prefill parameters
-- simpler than the unsubst module because this just autofills the parameters then returns the text needed for the specific deletion template
local out = '{{db/sandbox' -- to be changed when module is fully implemented
for k,v in ipairs(params) do
out = out .. '|' .. v
end
out = out .. (args.help and '' or '|help=off')
out = out .. (args.nocat and '|nocat=yes' or '')
out = out .. (args.bot and '|bot=yes' or '')
out = out .. (args.additionalNote and '|additionalnote=' .. args.additionalNote or '')
out = out .. (args.date and '|date=' .. args.date or '{{safesubst:REVISIONTIMESTAMP}}')
out = out .. '}}'
return out
else
local titleOfPage = mw.title.getCurrentTitle()
local pt = pageType._main({page = titleOfPage.fullText})
local introPrefixToUse = args.bot and 'bot' or 'delete'
local mainNotice = mw.html.create('div')
mainNotice:wikitext('\n')
local intro = mw.html.create('span')
intro:css{["font-style"] = "italic", ["font-weight"] = "bold"}
if args.numberOfEntries == 1 then
intro:wikitext(message(introPrefixToUse .. 'Intro', {
pt,
args.deletionReasons[1]
}))
elseif args.numberOfEntries > 1 then
intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', {
pt,
makeUnorderedList(args.deletionReasons)
}))
else
intro:wikitext(message(introPrefixToUse .. 'IntroMultiple', {
pt,
message("noReasonWarning")
}))
end
intro:allDone()
mainNotice:wikitext(tostring(intro))
if args.additionalNote then
mainNotice:wikitext('\n\n' .. message('additionalNote', {
args.additionalNote
}))
end
if args.hideButton then
mainNotice:wikitext('\n\n' .. message("removeNoticeNoButton", {pt})):allDone()
else
mainNotice:wikitext('\n\n' ..
message("removeNotice", {
pt,
message('removeNoticeWarning', {
titleOfPage.isTalkPage and message('checkBelow') or '' .. message('visitTheTalkPage') .. ''
})
})
):done()
local contestButton = mw.html.create('div')
contestButton:css{['margin-left'] = 'auto', ['margin-right'] = 'auto', ['text-align'] = 'center'}
if args.numberOfEntries == 1 then
contestButton:wikitext(button.main({
message("contestButton"),
class="mw-ui-progressive",
url="{{fullurl:" .. titleOfPage.talkPageTitle.fullText .. '|action=edit§ion=new&preloadtitle={{urlencode:' .. message('contestPreloadTitle') .. '}}&preload={{urlencode:' .. (mw.title.new(message('contestPreload', {args.entries[1].code})).exists and message('contestPreload', {args.entries[1].code}) or message('contestPreloadGeneric')) .. '}}&editintro={{urlencode:' .. message('contestPreloadEditintro') .. '}}}}'
})):done()
else
contestButton:wikitext(button.main({
message("contestButton"),
class="mw-ui-progressive",
style="text-align:center;",
url="{{fullurl:" .. titleOfPage.talkPageTitle.fullText .. '|action=edit§ion=new&preload={{urlencode:' .. message('contestPreloadGeneric') .. '}}&editintro={{urlencode:' .. message('contestPreloadEditintro') .. '}}}}'
})):done()
end
mainNotice:wikitext('\n\n' .. tostring(contestButton))
mainNotice:wikitext('\n\n' .. message("deleteCloser", {
pt,
titleOfPage.isTalkPage and message("deleteCloserProvidedBelowNotice") or message("deleteCloserProvidedOnTalkPage")
})):done()
if (args.help) then
local templateCall = '
for k,v in pairs(params) do
templateCall = templateCall .. '|' .. k .. '=' .. v
end
templateCall = templateCall .. '|notice=yes}} ~~' .. '~~'
mainNotice:wikitext('\n\n' .. message('deleteNoticeTemplate', {templateCall}))
end
local hangOn = mw.html.create('span')
if titleOfPage.talkPageTitle.exists then
hangOn:addClass('sysop-show')
hangOn:wikitext(
message("hangOnAdmin", {
pt,
titleOfPage.isTalkPage and message("checkBelow") or message("hangOnTalkPage")
})
):done()
else
hangOn:wikitext(message('hangOn', {pt})):done()
end
mainNotice:wikitext('\n\n' .. tostring(hangOn))
end
if args.numberOfEntries == 1 then
if args.entries[1].notes then
mainNotice:wikitext(args.entries[1].notes):done()
end
end
local deleteReasonSummary = ''
if args.numberOfEntries > 1 then
deleteReasonSummary = 'Multiple criteria: '
local isFirst = true
for k,v in pairs(args.entries) do
deleteReasonSummary = v.code and deleteReasonSummary .. (isFirst and or ', ') .. '' .. v.code .. '' or
isFirst = false
end
elseif args.numberOfEntries == 1 then
deleteReasonSummary = args.entries[1].code and deleteReasonSummary .. '' .. args.entries[1].code .. '' or ''
end
if deleteReasonSummary == '' then
deleteReasonSummary = 'Speedy'
end
local adminMessage = mw.html.create('span')
adminMessage:addClass('sysop-show')
adminMessage:wikitext(
message(args.bot and 'checkBot' or 'check',
{args.bot and message('check', {deleteReasonSummary}) or deleteReasonSummary}
)
):done()
mainNotice:wikitext('\n\n' .. tostring(adminMessage))
local lastEditUser = mw.getCurrentFrame():callParserFunction('REVISIONUSER', titleOfPage.fullText)
local editDate = mw.getCurrentFrame():callParserFunction('#time', 'H:i, j F Y', mw.getCurrentFrame():callParserFunction('REVISIONTIMESTAMP', titleOfPage.fullText))
mainNotice:wikitext(' ' .. message('lastEdited', {
'[{{fullurl:' .. titleOfPage.fullText .. '|action=history}} ' .. editDate .. ']',
timeAgo.main({editDate})
}))
mainNotice:wikitext('\n')
mainNotice:allDone()
out = out .. tostring(mainNotice)
-- categorize
out = out .. '' .. (args.nocat and ':' or '') .. 'Category:' .. message('defaultCategory') .. ''
for k,v in pairs(args.entries) do
local categorizeTime = mw.getCurrentFrame():preprocess('{{#time:U|' .. args.date .. ' +' .. (v and v.delayCategorization or 0) .. ' days' .. '}}')
local currentTime = mw.getCurrentFrame():preprocess('{{#time:U|now}}')
if currentTime + 0 >= categorizeTime + 0 then
for l,w in pairs(v and v.categories or {}) do
out = out .. '' .. (args.nocat and ':' or '') .. 'Category:' .. w .. ''
end
end
end
local deletionBoxArgs = {
type = "speedy",
text = mw.getCurrentFrame():preprocess(out),
style = "font-size:95%;word-break:break-word;",
image = message('level' .. args.highestMessage .. 'icon') == '' and 'none' or '40px'
}
local deletionBox = mbox.main('mbox', deletionBoxArgs)
local blankedBox = ''
local hiddenBox = ''
local blanked = ''
local hidden = ''
if args.blank then
blanked = blanked .. message('blanked')
if mw.getCurrentFrame():preprocess('{{REVISIONSIZE}}') + 0 >= 35 then
blanked = blanked .. ' ' .. message('pleaseBlank') .. ''
end
local blankedBoxArgs = {
type = "notice",
text = blanked,
style = "word-break:break-word;"
}
blankedBox = mbox.main('mbox', blankedBoxArgs)
end
if args.hide then
hidden = hidden .. message('hidden')
local hiddenBoxArgs = {
type = "notice",
text = hidden,
style = "word-break:break-word;"
}
hiddenBox = mbox.main('mbox', hiddenBoxArgs)
end
if args.hide and not args.nocat then
hiddenBox = hiddenBox .. '