Module:Staff

-- local p = {}

local getArgs = require('Module:Arguments').getArgs local loader = require('Module:Loader')

local string, table, yesno, libUtil = loader.require('String', 'Table', 'Yesno', 'LibraryUtil') local checkType = libUtil.checkType

local roles, aliases, members = loader.loadData('Staff/Data', 'Staff/Aliases', 'Staff/Members')

local lang = mw.language.getContentLanguage

-- This is for generating staff strings for AF:41 -- Staff name not listed here will be the name itself -- All staff and retired staff are handled in the list local abuseFilterRegexSpecialCases = { Thundercraft5 = 'Thundercraft\\d?', Pa3ckP7 = 'Pa\\d?ckP\\d', }

local function getdaysinmonth(m, y)	local t = { 31, 28 + (((y % 4 == 0 and y % 100 ~= 0) or y % 400 == 0) and 1 or 0), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } return t[m] end

-- Template: StaffRole function p.staffRole(frame) local args = getArgs(frame) local name = args[1] local alt = args[2] local plural = yesno(args['plural'] or args['plur'] or args['p'], false) local shorten = yesno(args['short'] or args['shr'] or args['s'], false) local linkAlt = args['link_alt'] or args['link'] or args['linkalt'] or args['la'] or args['l'] local altText = args['alt'] or args['a'] local success, response = pcall(p._staffRole, name, alt, plural, shorten, linkAlt, altText) return success and response or string.error(response) end

function p._staffRole(name, alt, plural, shorten, linkAlt, altText) if type(name) == 'table' then alt = name[2] or name.alt or name.alttext or name.altText plural = name[3] or name.plural shorten = name[4] or name.short or name.shorten linkAlt = name[5] or name.linkAlt or name.link name = name[1] or name.name end if name ==  or name == nil then return  end checkType('_staffRole', 1, name, 'string') checkType('_staffRole', 2, alt, 'string', true) checkType('_staffRole', 3, plural, 'boolean', true) checkType('_staffRole', 4, shorten, 'boolean', true) checkType('_staffRole', 5, linkAlt, 'string', true) checkType('_staffRole', 6, altText, 'string', true) if name:lower:match('(\'?s)$') or plural then plural = true name = name:gsub('(\'?s)$', '') else plural = false end local titleId = aliases[name:gsub('_', ' '):lower] local data = roles[titleId] if not titleId then error(string.format('Invalid staff rank name %s', name), 2) end local function hideAlways(s) return ' ' .. s .. ' '	end local text = alt or		(shorten and plural) and data.shortPlural or		(shorten) and data.short or		(plural) and data.plural or		data.title local format_string = linkAlt and ( linkAlt:match('https?%:%/%/%S*') and '%s[%s %s]' or '%s%s' ) or		'%s%s' local output_text = format_string:format(		hideAlways(data.order),		linkAlt or data.link,		string.wrapHtml(altText or text, ' ', { class = data.class })	) return output_text end

-- Template:StaffMsgbox function p.staffMsgBox(frame) local args = getArgs(frame) local function createList(ranks) table.sort(ranks, function(a, b)			return roles[aliases[a:lower]].order > roles[aliases[b:lower]].order 			end) local ret = {} for i, v in ipairs(ranks) do			table.push(ret,				(#ranks > 2 and i > 1) and ', ' or ,				(#ranks == i and i > 1) and 'and ' or ,				v:match('^([aeiou]+)') and 'an ' or 'a ', 				p.staffRole{ v },				#ranks == 2 and ' ' or ''			) end return ret end local ranks = {} if args.user then local user = members[args.user] if not user then error(('User "%s" does not exist on record'):format(args.user)) end if user.rank then ranks = string.split(user.rank, '%|') end else for k, v in pairs(args) do			if tonumber(k) or tostring(k):match('^rank[_ ]?%d*$') then table.push(ranks, v)			end end end local text = (#ranks > 0) and ('This user is %s of the wiki. The wiki is not affiliated with Hypixel. %s'):format(		table.concat(createList(ranks), ''),		string.wrapHtml{			text = 'This user is \'\'\'not\'\'\' part of the Hypixel staff and has \'\'\'no\'\'\' power in-game.',			tag = 'span',			attrs = { style = 'color: red;' }		}	) or ('\'\'\'This former staff is retired.\'\'\' They were once active on this wiki, but are no longer so. %s'):format(		string.wrapHtml{			text = 'Please do not message them about matters of the wiki unless necessary.',			tag = 'span',			attrs = { style = 'color: red;' }		}	) return text end

-- Project:Staff/user function p.staffLink(frame) local args = getArgs(frame) local staff = args.staff or args.name or args[1] local rank = args.rank or args.r	local showlinks = args.showlinks or args.showlink return p._staffLink(name, rank, { showlinks = showlinks, note = args.notes or args.note or args[2] }) end

function p._staffLink(name, rank, opts) local member = members[name] if not member then string.wrapHtml(('Staff Link: No staff named %s.'):format(name), 'span', { class = 'error' }) end opts = opts or {} rank = rank or 'RB' if (aliases[rank:lower] or rank:lower) == 'bot' then return ('%s'):format(name, name) end local disp = p._staffRole(rank, name, nil, nil, ('User:%s'):format(name)) local bull = '\'\'\'&bull;\'\'\'' local links = (' \'\'\'(%s %s %s %s %s)\'\'\''):format(		p._staffRole(rank, 'wall', nil, nil, ('Message_Wall:%s'):format(name)), bull,		p._staffRole(rank, 'CC Wall', nil, nil, ('w:c:Message_Wall:%s'):format(name)), bull,		p._staffRole(rank, 'contribs', nil, nil, ('Special:Contributions/%s'):format(name))	) local showlinks = opts.showlinks or opts.showlink note = opts.notes or opts.note note = note and (' (%s)'):format(note) or '' return ('%s%s%s'):format(disp, yesno(showlinks, true) and string.wrapHtml(links, 'small') or '', note) end

-- Project:Staff/data function p.staffLookup(frame) local args = getArgs(frame) local lookup = args[1] local mode = args[2] return p._staffLookup(lookup, mode) end

function p._staffLookup(lookup, mode) local function countWith(t, callbackfn) local ret = {} for i, v in pairs(t) do			if callbackfn(v, i, t) then table.push(ret, v)			end end return table.length(ret) end mode = mode or 'list' if lookup:match('^former') then local period = lookup:match('^former (%w+)$') if not period then return string.wrapHtml(('Staff Lookup: Format not valid. Try "former current" or "former past".'):format(lookup), 'span', { class = 'error' }) end return p._formerLookup(period, mode) end if lookup:match('^total') then local target = lookup:match('^total (.+)$') if target == 'staff' then return countWith(members, function (m)				return not not m.rank			end) elseif target == 'former past' then return countWith(members, function (m)				return not m.rank and m.period == 'past'			end) elseif target == 'former current' then return countWith(members, function (m)				return not m.rank and m.period ~= 'past'			end) else return string.wrapHtml(('Staff Lookup: Format not valid. Try "staff", total former current", or "total former past".'):format(lookup), 'span', { class = 'error' })		end	end	role = roles[aliases[lookup:lower] or lookup:lower]	if not role then return string.wrapHtml(('Staff Lookup: Role "%s" not valid.'):format(lookup), 'span', { class = 'error' }) end	local code = role.code	local a, count = {}, 0	for staff, staffdata in pairs(members) do		if staffdata.rank then			if (code == 'BOT' and staffdata.bot) then				a[staff] = staffdata				count = count + table.length(mw.text.split(staffdata.bot, '%|'))			elseif staffdata.rank:match(code) then				a[staff] = staffdata				count = count + 1			end		end	end	local sortedpairs = table.sortedPairs(a, function(one, two) return one:lower < two:lower end)	if mode == 'num' or mode == 'count' then		return count	elseif mode == 'list' then		local t = {}		if code == 'BOT' then			table.push(t, '{| class="article-table"\n!Bot\n!Operator') for owner, ownerdata in sortedpairs do				local temp = {} -- bots by one owner can be separated by the pipe character (|) for bot in mw.text.gsplit(ownerdata.bot, '%|') do					table.push(temp, p._staffLink(bot, 'bot')) end if ownerdata.rank then -- find highest rank of that user, default Rollback local order, highestrank = 0, 'RB' for r in mw.text.gsplit(ownerdata.rank, '%|') do						local rd = roles[aliases[lookup:lower] or lookup:lower] if rd then if rd.order > order then order, highestrank = rd.order, r							end end end table.push(t, ('|-\n| %s || %s'):format( table.concat(temp, ', '), p._staffLink(owner, highestrank) ))				else table.push(t, ('|-\n| %s || %s'):format( table.concat(temp, ', '), owner, owner ))				end end table.push(t, '|}') else for staff, staffdata in sortedpairs do				if not staffdata.rank then string.wrapHtml(('Staff Lookup: Staff %s does not have the required key "rank".'):format(staff), 'span', { class = 'error' }) end table.push(t, ('* %s'):format( p._staffLink(staff, code, { note = staffdata.activity }) ))			end end return mw.getCurrentFrame:preprocess((table.concat(t, '\n'))) else return string.wrapHtml(('Staff Lookup: Mode "%s" not valid.'):format(mode or 'nil'), 'span', { class = 'error' }) end end

function p._formerLookup(period, mode) mode = mode or 'list' local a = {} for staff, staffdata in pairs(members) do		local p = staffdata.period or 'current' if staffdata.resignation and p == period then if not staffdata.former then string.wrapHtml(('Staff Lookup: Staff %s does not have the required key "former".'):format(staff), 'span', { class = 'error' }) end -- verify date local y, m, d = staffdata.resignation:match('^(%d+)%-(%d+)%-(%d+)$') y, m, d = tonumber(y), tonumber(m), tonumber(d) if (not y) or (not m) or (not d) or (y > 2099) or (y < 2019) or (not getdaysinmonth(m, y)) or (d < 1) or (d > getdaysinmonth(m, y)) then return string.wrapHtml(('Staff Lookup: Resignation date "%s" for %s not valid.'):format(staffdata.resignation, staff), 'span', { class = 'error' }) end a[#a + 1] = table.merge(table.deepCopy(staffdata, true), { name = staff, y = y, m = m, d = d }) end end table.sort(a, function(one, two)		-- sort by resignation date; if equal, sort by name		if one.y == two.y then			if one.m == two.m then				if one.d == two.d then					return one.name:lower < two.name:lower				else					return one.d > two.d				end			else				return one.m > two.m			end		else			return one.y > two.y		end	end) if mode == 'num' or mode == 'count' then return table.length(a) elseif mode == 'list' then local t = {} for _, staffdata in ipairs(a) do			local u = {} for r in mw.text.gsplit(staffdata.former, '%|') do				if roles[aliases[r:lower] or r:lower] then table.push(u, p._staffRole(r)) else string.wrapHtml(('Staff Lookup: Role "%s" for %s not valid.'):format(r, nm), 'span', { class = 'error' }) end end table.push(t, ('* %s (Former %s) Resigned %s'):format( staffdata.name, staffdata.name, mw.text.listToText(u), lang:formatDate('j F Y', (staffdata.resignation)) ))		end return mw.getCurrentFrame:preprocess((table.concat(t, '\n'))) else return string.wrapHtml(('Staff Lookup: Mode "%s" not valid.'):format(mode or 'nil'), 'span', { class = 'error' }) end end

function p.abuseFilterStaffString -- local members = table.deepCopy(members, true) local t = table.keys(members) -- special cases for name, replacement in pairs(abuseFilterRegexSpecialCases) do		local i = table.findIndex(t, name) if i then t[i] = replacement end end return table.concat(t, "|") end

return p