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 = '{{subst:db|page=' .. titleOfPage.fullText

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', {

'' .. lastEditUser .. '',

'[{{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 .. '

'

end

return deletionBox .. blankedBox .. hiddenBox

end

end

end

p.makeTable = makeInvokeFunc('_makeTable')

function p._makeTable(args)

local usedCodes = {}

local tb = mw.html.create("table")

tb:addClass('wikitable')

local th = tb:tag('tr')

th:tag('th'):wikitext('Code'):done()

th:tag('th'):wikitext('Aliases'):done()

th:tag('th'):wikitext('Criterion'):done()

th:tag('th'):wikitext('Name'):done()

th:tag('th'):wikitext('Description'):done()

for k,v in pairs(config.deletionReasonsSorting) do

local entry = getDeletionEntry(v)

if entry then

if not usedCodes[v] then

local tr = tb:tag('tr')

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('' .. v .. '')):done()

local aliasStr = ''

for _,alias in pairs(entry.aliases) do

aliasStr = aliasStr .. '' .. alias .. ''

usedCodes[alias] = true

end

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess(aliasStr))

tr:tag('td'):wikitext('' .. entry.code .. ''):done()

tr:tag('td'):wikitext(entry.name):done()

tr:tag('td'):wikitext(entry.description):done()

usedCodes[v] = true

end

end

end

for k,entry in pairs(config.deletionCodes) do

if not usedCodes[k] then

local tr = tb:tag('tr')

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('' .. k .. '')):done()

local aliasStr = ''

for _,alias in pairs(entry.aliases) do

aliasStr = aliasStr .. '' .. alias .. ''

usedCodes[alias] = true

end

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess(aliasStr))

tr:tag('td'):wikitext('' .. entry.code .. ''):done()

tr:tag('td'):wikitext(entry.name):done()

tr:tag('td'):wikitext(entry.description):done()

usedCodes[k] = true

end

end

return tostring(tb) .. ''

end

p.makeTableWithExamples = makeInvokeFunc('_makeTableWithExamples')

function p._makeTableWithExamples(args)

local usedCodes = {}

local tb = mw.html.create("table")

tb:addClass('wikitable')

local th = tb:tag('tr')

th:tag('th'):css{position = "sticky", top = 0, left = 0}:wikitext('Codes'):done()

--th:tag('th'):wikitext('Criterion'):done()

th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion message'):done()

th:tag('th'):css{position = "sticky", top = 0}:wikitext('Deletion notice'):done()

for k,v in pairs(config.deletionReasonsSorting) do

local entry = getDeletionEntry(v)

if entry then

if not usedCodes[v] then

local tr = tb:tag('tr')

local aliasStr = ''

for _,alias in pairs(entry.aliases) do

aliasStr = aliasStr .. '
{{db|' .. alias .. '}}'

usedCodes[alias] = true

end

tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame():preprocess('{{db|' .. v .. '}}' .. aliasStr)):done()

--tr:tag('td'):wikitext('' .. entry.code .. ''):done()

tr:tag('td'):wikitext('

' .. mw.getCurrentFrame():preprocess('{{#invoke:Speedy|main|nocat=yes|' .. v .. '}}' .. '
' )):done()

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('

' .. '{{#invoke:Speedy|main|page=Sandbox|notice=yes|' .. v .. '}}' .. '
')):done()

usedCodes[v] = true

end

end

end

for v,entry in pairs(config.deletionCodes) do

if not usedCodes[v] then

local tr = tb:tag('tr')

local aliasStr = ''

for _,alias in pairs(entry.aliases) do

aliasStr = aliasStr .. '
{{db|' .. alias .. '}}'

usedCodes[alias] = true

end

tr:tag('td'):css{position = "sticky", left = 0}:wikitext(mw.getCurrentFrame():preprocess('{{db|' .. v .. '}}\n' .. aliasStr)):done()

--tr:tag('td'):wikitext('' .. entry.code .. ''):done()

tr:tag('td'):wikitext('

' .. mw.getCurrentFrame():preprocess('{{#invoke:Speedy|main|nocat=yes|' .. v .. '}}' .. '
' )):done()

tr:tag('td'):wikitext(mw.getCurrentFrame():preprocess('

' .. '{{#invoke:Speedy|main|page=Sandbox|notice=yes|' .. v .. '}}' .. '
')):done()

usedCodes[v] = true

end

end

return tostring(tb) .. ''

end

return p