------------------------------------------------------------------------ Module:Rfx ---- This is a library for retrieving information about requests ---- for adminship and requests for bureaucratship on the English ---- Wikipedia. Please see the module documentation for instructions. ------------------------------------------------------------------------
local libraryUtil = require('libraryUtil')local lang = mw.getContentLanguagelocal textSplit = mw.text.splitlocal umatch = mw.ustring.matchlocal newTitle = mw.title.new
local rfx =
---------------------------------------- Helper functions ----------------------------------------
local function getTitleObject(title) local success, titleObject = pcall(newTitle, title) if success and titleObject then return titleObject else return nil endend
local function parseVoteBoundaries(section) -- Returns an array containing the raw wikitext of RfX votes in a given section. section = section:match('^.-\n#(.*)$') -- Strip non-votes from the start. if not section then return end section = section:match('^(.-)\n[^#]') or section -- Discard subsequent numbered lists. local comments = textSplit(section, '\n#') local votes = for i, comment in ipairs(comments) do if comment:find('^[^#*;:].*%S') then votes[#votes + 1] = comment end end return votesend
local function parseVote(vote) -- parses a username from an RfX vote. local userStart, userMatch = vote:match('^(.*)%[%[[%s_]*:?[%s_]*[uU][sS][eE][rR][%s_]*:[%s_]*(.-)[%s_]*%]%].-$') local talkStart, talkMatch = vote:match('^(.*)%[%[[%s_]*:?[%s_]*[uU][sS][eE][rR][%s_]+[tT][aA][lL][kK][%s_]*:[%s_]*(.-)[%s_]*%]%].-$') local contribStart, contribMatch = vote:match('^(.*)%[%[[%s_]*:?[%s_]*[sS][pP][eE][cC][iI][aA][lL][%s_]*:[%s_]*[cC][oO][nN][tT][rR][iI][bB][uU][tT][iI][oO][nN][sS]/[%s_]*(.-)[%s_]*%]%].-$') local username if userStart and talkStart then if #userStart > #talkStart then username = userMatch else username = talkMatch end elseif userStart then username = userMatch elseif talkStart then username = talkMatch elseif contribStart then username = contribMatch else return string.format("Error parsing signature: %s", vote) end username = username:match('^[^|/#]*') return usernameend
local function parseVoters(votes) local voters = for i, vote in ipairs(votes) do voters[#voters + 1] = parseVote(vote) end return votersend
local function dupesExist(...) local exists = local tables = for i, usernames in ipairs(tables) do for j, username in ipairs(usernames) do username = lang:ucfirst(username) if exists[username] then return true else exists[username] = true end end end return falseend
-------------------------------------------- Define the constructor function --------------------------------------------
function rfx.new(title) local obj = local data = local checkSelf = libraryUtil.makeCheckSelfFunction('Module:Rfx', 'rfx', obj, 'rfx object') -- Get the title object and check to see whether we are a subpage of WP:RFA or WP:RFB. title = getTitleObject(title) if not title then return nil end function data:getTitleObject checkSelf(self, 'getTitleObject') return title end if title.namespace
'Requests for adminship' then data.type = 'rfa' elseif rootText
-- Get the page content and divide it into sections. local pageText = title:getContent if not pageText then return nil end local introText, supportText, opposeText, neutralText = umatch(pageText, '^(.-)\n
.-' .. '\n
(.-)' .. '\n
(.-)' .. '\n
(.-)$' ) if not introText then introText, supportText, opposeText, neutralText = umatch(pageText, "^(.-\n[^\n]-%(%d+/%d+/%d+%)[^\n]-)\n.-" .. "\nSupport(.-)\nOppose(.-)\nNeutral(.-)" ) end
-- Get vote counts. local supportVotes, opposeVotes, neutralVotes if supportText and opposeText and neutralText then supportVotes = parseVoteBoundaries(supportText) opposeVotes = parseVoteBoundaries(opposeText) neutralVotes = parseVoteBoundaries(neutralText) end local supports, opposes, neutrals if supportVotes and opposeVotes and neutralVotes then supports = #supportVotes data.supports = supports opposes = #opposeVotes data.opposes = opposes neutrals = #neutralVotes data.neutrals = neutrals end
-- Voter methods and dupe check.
function data:getSupportUsers checkSelf(self, 'getSupportUsers') if supportVotes then return parseVoters(supportVotes) else return nil end end
function data:getOpposeUsers checkSelf(self, 'getOpposeUsers') if opposeVotes then return parseVoters(opposeVotes) else return nil end end
function data:getNeutralUsers checkSelf(self, 'getNeutralUsers') if neutralVotes then return parseVoters(neutralVotes) else return nil end end
function data:dupesExist checkSelf(self, 'dupesExist') local supportUsers = self:getSupportUsers local opposeUsers = self:getOpposeUsers local neutralUsers = self:getNeutralUsers if not (supportUsers and opposeUsers and neutralUsers) then return nil end return dupesExist(supportUsers, opposeUsers, neutralUsers) end
if supports and opposes then local total = supports + opposes if total <= 0 then data.percent = 0 else data.percent = math.floor((supports / total * 100) + 0.5) end end if introText then data.endTime = umatch(introText, '(%d%d:%d%d, %d+ %w+ %d+) %(UTC%)') data.user = umatch(introText, '
') if not data.user then data.user = umatch(introText, '
') end end -- Methods for seconds left and time left. function data:getSecondsLeft checkSelf(self, 'getSecondsLeft') local endTime = self.endTime if not endTime then return nil end local now = tonumber(lang:formatDate("U")) local success, endTimeU = pcall(lang.formatDate, lang, 'U', endTime) if not success then return nil end endTimeU = tonumber(endTimeU) if not endTimeU then return nil end local secondsLeft = endTimeU - now if secondsLeft <= 0 then return 0 else return secondsLeft end end
function data:getTimeLeft checkSelf(self, 'getTimeLeft') local secondsLeft = self:getSecondsLeft if not secondsLeft then return nil end return mw.ustring.gsub(lang:formatDuration(secondsLeft,), ' and', ',') end function data:getReport -- Gets the URI object for Vote History tool checkSelf(self, 'getReport') return mw.uri.new('https://apersonbot.toolforge.org/vote-history?page=' .. mw.uri.encode(title.prefixedText)) end function data:getStatus -- Gets the current status of the RfX. Returns either "successful", "unsuccessful", -- "questions", "open", or "pending closure". Returns nil if the status could not be found. checkSelf(self, 'getStatus') local rfxType = data.type if rfxType
'rfb' then if umatch(pageText, '%[%[[%s_]*[cC][aA][tT][eE][gG][oO][rR][yY][%s_]*:[%s_]*[sS]uccessful requests for bureaucratship(.-)[%s_]*%]%]' ) then return 'successful' elseif umatch(pageText, '%[%[[%s_]*[cC][aA][tT][eE][gG][oO][rR][yY][%s_]*:[%s_]*[uU]nsuccessful requests for bureaucratship(.-)[%s_]*%]%]' ) then return 'unsuccessful' end end local secondsLeft = self:getSecondsLeft if secondsLeft and secondsLeft > 432000 then -- Per 2023 Phase I Proposal 3b: the voting period is for the final 5 days return 'discussion' elseif secondsLeft and secondsLeft > 0 then return 'open' elseif secondsLeft and secondsLeft <= 0 then return 'pending closure' else return nil end end -- Specify which fields are read-only, and prepare the metatable. local readOnlyFields = local function pairsfunc(t, k) local v repeat k = next(readOnlyFields, k) if k
return setmetatable(obj,)end
return rfx