--                          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 date_names = require('মডিউল:উদ্ধৃতি/রূপরেখা').date_names local lang = mw.getContentLanguage() local enlang = mw.language.new('en') 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, userMatch = vote:match('^(.*)%[%[ব্যবহারকারী:[%s_]*(.-)[%s_]*%]%].-$') 	local talkStart, talkMatch = vote:match('^(.*)%[%[ব্যবহারকারী আলাপ:[%s_]*(.-)[%s_]*%]%].-$') 	local contribStart, contribMatch = vote:match('^(.*)%[%[বিশেষ:অবদান/[%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 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  local function hasCategory(category, catList) 	for _, c in ipairs(catList) do 		if c == category then 			return true 		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 == 'প্রশাসক হওয়ার আবেদন' 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*মনোনয়ন%s*====.-' 		.. '\n====%s*সমর্থন%s*====(.-)' 		.. '\n====%s*বিরোধিতা%s*====(.-)' 		.. '\n====%s*নিরপেক্ষ%s*===(.-)$' 	) 	if not introText then 		introText, supportText, opposeText, neutralText = umatch( 			pageText, 			"^(.-\n[^\n]-%(%d+/%d+/%d+%)[^\n]-)\n.-" 			.. "\nসমর্থন(.-)\nবিরোধিতা(.-)\nনিরপেক্ষ(.-)" 		) 	end    local categories = title.categories  	-- 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     local hours, minutes, days, bnMonth, year = umatch(introText, '([০১২৩৪৫৬৭৮৯][০১২৩৪৫৬৭৮৯]):([০১২৩৪৫৬৭৮৯][০১২৩৪৫৬৭৮৯]) ([০১২৩৪৫৬৭৮৯]+) (.-) ([০১২৩৪৫৬৭৮৯]+) %(ইউটিসি%)')     local monthNumber = date_names['local']['long'][bnMonth]     data.endTime = lang:parseFormattedNumber(year) .. '-' .. monthNumber .. '-' .. lang:parseFormattedNumber(days) .. ' ' .. lang:parseFormattedNumber(hours) .. ':' .. lang:parseFormattedNumber(minutes)     data.user = umatch(introText, '===%s*%[%[[_%s]*ব্যবহারকারী:.-|[_%s]*(.-)[_%s]*%]%][_%s]*===') or umatch(introText, '===%s*([^\n]-)%s*===') 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(enlang:formatDate("U")) 		local success, endTimeU = pcall(enlang.formatDate, enlang, 'U', endTime, true) 		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:getStatus() 		-- Gets the current status of the RfX. Returns either "successful", "unsuccessful", 		-- "discussion", "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 hasCategory('উইকিপিডিয়া প্রশাসক হওয়ার সফল আবেদন', categories) then 			return 'সফল' 		elseif hasCategory('উইকিপিডিয়া প্রশাসক হওয়ার ব্যর্থ আবেদন', categories) then 			return 'ব্যর্থ' 		end 	elseif rfxType == 'rfb' then 		if hasCategory('উইকিপিডিয়া ব্যুরোক্র্যাট হওয়ার সফল আবেদন', categories) then 			return 'সফল' 		elseif hasCategory('উইকিপিডিয়া ব্যুরোক্র্যাট হওয়ার ব্যর্থ আবেদন', categories) then 			return 'ব্যর্থ' 		end 	end  		local secondsLeft = self:getSecondsLeft() 	    if secondsLeft and secondsLeft > 0 then 			return 'খোলা' 		elseif secondsLeft and secondsLeft <= 0 then 			return ' বন্ধের জন্য অপেক্ষমান' 		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 = false, 		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