Module:Rfx
This is a library for getting information about individual requests for adminship (RfA) and requests for bureaucratship (RfB) pages on the English Wikipedia. It is not meant to be used directly from wiki pages, but rather to be used by other Lua modules.
Creating new objects
First of all, the library must be loaded, like this:
<syntaxhighlight lang="lua"> local rfx = require( 'Module:Rfx' ) </syntaxhighlight>
Once the library is loaded, you can make a new rfx object using rfx.new()
.
(see below).
<syntaxhighlight lang="lua"> local myRfx = rfx.new( pagename ) </syntaxhighlight> The <syntaxhighlight lang="lua"> local exampleRfa = rfx.new( 'Wikipedia:Requests for adminship/Example' ) </syntaxhighlight> If Methods and propertiesOnce you have created a new
Methods must be called with the colon syntax: <syntaxhighlight lang="lua"> local titleObject = exampleRfa:getTitleObject() </syntaxhighlight>
You can compare Expensive functionsThis module makes use of the title:getContent method to fetch RfX page sources. This method will be called for each RfX page being looked up, so each use of |
---------------------------------------------------------------------- -- 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.getContentLanguage() local textSplit = mw.text.split local umatch = mw.ustring.match local 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 end end 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 votes end local function parseVote(vote) -- parses a username from an RfX vote. local userStart, userEnd, userMatch = vote:find('%[%[[%s_]*[uU][sS][eE][rR][%s_]*:[%s_]*(.-)[%s_]*%]%].-$') local talkStart, talkEnd, talkMatch = vote:find('%[%[[%s_]*[uU][sS][eE][rR][%s_]+[tT][aA][lL][kK][%s_]*:[%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 else return string.format( "'''Error parsing signature''': ''%s''", vote ) end username = username:match('^[^|/#]*') return username end local function parseVoters(votes) local voters = {} for i, vote in ipairs(votes) do voters[#voters + 1] = parseVote(vote) end return voters end 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 false end ------------------------------------------ -- 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 == 4 then local rootText = title.rootText if rootText == 'Requests for adminship' then data.type = 'rfa' elseif rootText == 'Requests for bureaucratship' then data.type = 'rfb' else return nil end else return nil end -- 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=====%s*[sS]upport%s*=====(.-)' .. '\n=====%s*[oO]ppose%s*=====(.-)' .. '\n=====%s*[nN]eutral%s*=====(.-)$' ) if not introText then introText, supportText, opposeText, neutralText = umatch( pageText, "^(.-\n'''[^\n]-%(%d+/%d+/%d+%)[^\n]-''')\n.-" .. "\n'''Support'''(.-)\n'''Oppose'''(.-)\n'''Neutral'''(.-)" ) 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, '===%s*%[%[[_%s]*[wW]ikipedia[_%s]*:[_%s]*[rR]equests[_ ]for[_ ]%w+/.-|[_%s]*(.-)[_%s]*%]%][_%s]*===') if not data.user then data.user = umatch(introText, '===%s*([^\n]-)%s*===') 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, {'days', 'hours'}), ' and', ',') end function data:getReport() -- Gets the URI object for Vote History tool checkSelf(self, 'getReport') return mw.uri.new('//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", -- "open", or "pending closure". Returns nil if the status could not be found. checkSelf( self, 'getStatus' ) local rfxType = data.type if rfxType == 'rfa' then if umatch( pageText, '%[%[[%s_]*[cC][aA][tT][eE][gG][oO][rR][yY][%s_]*:[%s_]*[sS]uccessful requests for adminship(.-)[%s_]*%]%]' ) then return 'successful' elseif umatch( pageText, '%[%[[%s_]*[cC][aA][tT][eE][gG][oO][rR][yY][%s_]*:[%s_]*[uU]nsuccessful requests for adminship(.-)[%s_]*%]%]' ) then return 'unsuccessful' end elseif 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 > 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 = { getTitleObject = true, ['type'] = true, getSupportUsers = true, getOpposeUsers = true, getNeutralUsers = true, supports = true, opposes = true, neutrals = true, endTime = true, percent = true, user = true, dupesExist = true, getSecondsLeft = true, getTimeLeft = true, getReport = true, getStatus = true } local function pairsfunc( t, k ) local v repeat k = next( readOnlyFields, k ) if k == nil then return nil end v = t[k] until v ~= nil return k, v end return setmetatable( obj, { __pairs = function ( t ) return pairsfunc, t, nil end, __index = data, __newindex = function( t, key, value ) if readOnlyFields[ key ] then error( 'index "' .. key .. '" is read-only', 2 ) else rawset( t, key, value ) end end, __tostring = function( t ) return t:getTitleObject().prefixedText end } ) end return rfx