Module:Ep/Info


You can invoke this module from a template to get back episode information from a string representing an episode code or name in Module:Ep/Array. To get a formatted string similar to {{Ep}}, invoke Module:Ep instead. This module will return blank values if the episode code is missing from Module:Ep/Array, even if it's validly constructed.

In general, this module should not need to be updated. For more detailed directions about updating data modules for new shows or seasons, please see Help:Maintaining data modules.

Invoking from template[edit source]

Episode information[edit source]

This module can return information about an episode, including the episode code, title, pagename, episode number, thumbnail link, or transcript link. Here it is invoked for the latest episode of Campaign 3:

Usage Gives
{{#invoke:Ep/Info|ep|C3 latest}} 3x91
{{#invoke:Ep/Info|ep|C3 latest|title=1}} Campaign 3 Episode 91
{{#invoke:Ep/Info|ep|C3 latest|pagename=1}} Campaign 3 Episode 91
{{#invoke:Ep/Info|ep|C3 latest|prodCode=1}} C3E91
{{#invoke:Ep/Info|ep|C3 latest|epnum=true}} 91
{{#invoke:Ep/Info|ep|C3 latest|thumbnail=true}} Bells Hells logo color.png
{{#invoke:Ep/Info|ep|C3 latest|transcript=true}} Transcript:Campaign 3 Episode 91

Here it is for a case where the pagename and episode title differ:

Usage Gives
{{#invoke:Ep/Info|ep|3x25}} 3x25
{{#invoke:Ep/Info|ep|3x25|title=1}} A Taste of Tal'Dorei
{{#invoke:Ep/Info|ep|3x25|pagename=1}} A Taste of Tal'Dorei (episode)


Module:Ep combines all three of these values into a standardized format:

Campaign and season information[edit source]

This utilizes Module:Ep/Decoder. When appropriate, some seasons are named instead of numbered. Note that true, 1, and any other string is equivalent after the equals sign.

Usage Gives
{{#invoke:Ep/Info|ep|LVM2x04|campaign=true}} LVM
{{#invoke:Ep/Info|ep|LVM2x04|campaignname=true}} The Legend of Vox Machina
{{#invoke:Ep/Info|ep|LVM2x04|season=true}} 2
{{#invoke:Ep/Info|ep|LVM2x04|seasonname=true}} Season 2
Usage Gives
{{#invoke:Ep/Info|ep|E3x02|campaign=true}} E
{{#invoke:Ep/Info|ep|E3x02|campaignname=true}} Exandria Unlimited
{{#invoke:Ep/Info|ep|E3x02|season=true}} 3
{{#invoke:Ep/Info|ep|E3x02|seasonname=true}} Calamity

Arc information[edit source]

This utilizes Module:Ep/Decoder to produce information about Arcs in longer campaigns.

Usage Gives
{{#invoke:Ep/Info|ep|2x105|arcName=true}} Family Ties
{{#invoke:Ep/Info|ep|2x105|arcNum=true}} 5
{{#invoke:Ep/Info|ep|2x105|arcCategory=true}} Category:Family Ties arc
{{#invoke:Ep/Info|ep|2x105|arcCharacterCategory=true}} Category:Characters in the Family Ties arc
{{#invoke:Ep/Info|ep|2x105|arcPage=true}} Campaign 2 Arc 5: Family Ties

Calling from another module[edit source]

You can also get the episode number, title, pagename, campaign, and/or season in another module. Note that in this case, the module expects epCode to be a string, not a frame. Within a function, that might look something like:


local epInfo = require('Module:Ep/Info')
local epName = epInfo.title(epCode)
local pageName = epInfo.pagename(epCode)
local epNum = epInfo.epNum(epCode)
local campaign = epInfo.campaign(epCode)
local campaignName = epInfo.campaignName(epCode)
local season = epInfo.season(epCode)
local seasonName = epInfo.seasonName(epCode)


-- TO DO: update this so other modules don't need to call Module:Ep/Matcher directly
-- TO DO: consider changing so the individual functions can be called directly when invoked (instead of using param)

local p = {}

-- For templates to use this module. First unnamed arg is the value to be interpreted.
function p.ep(frame)
	local ep = frame.args[1]
	local title = frame.args['title'] or nil
	local pagename = frame.args['pagename'] or nil
	local epNum = frame.args['epnum'] or nil
	local epCodeFormatted = frame.args['epCodeFormatted'] or nil
	local campaign = frame.args['campaign'] or nil
	local campaignName = frame.args['campaignname'] or nil
	local season = frame.args['season'] or nil
	local seasonName = frame.args['seasonname'] or nil
	local isCampaign = frame.args['iscampaign'] or nil
	local prodCode = frame.args['prodCode'] or nil
	local arcName = frame.args['arcName'] or nil
	local arcNum = frame.args['arcNum'] or nil
	local arcCategory = frame.args['arcCategory'] or nil
	local arcCharacterCategory = frame.args['arcCharacterCategory'] or nil
	local arcPage = frame.args['arcPage'] or nil
	local thumbnail = frame.args['thumbnail'] or nil
	local transcript = frame.args['transcript'] or nil
	local sortkey = frame.args['sortkey'] or nil
	local sortBy = frame.args['sortby'] or 'airdate'
	local epMatcher = require('Module:Ep/Matcher')
	local epCode = epMatcher.matchCode(ep)
	local data = ''
	
	if not isempty(title) then
		data = p.title(epCode)
	elseif not isempty(pagename) then
		data = p.pagename(epCode)
	elseif not isempty(epNum) then
		data = p.epNum(epCode)
	elseif not isempty(epCodeFormatted) then
		data = p.epCodeFormatted(epCode)
	elseif not isempty(campaign) then
		data = p.campaign(epCode)
	elseif not isempty(campaignName) then
		data = p.campaignName(epCode)
	elseif not isempty(season) then
		data = p.season(epCode)
	elseif not isempty(seasonName) then
		data = p.seasonName(epCode)
	elseif not isempty(arcName) then
		data = p.arcName(epCode)
	elseif not isempty(prodCode) then
		data = p.prodCode(epCode)
	elseif not isempty(isCampaign) then
		data = p.isCampaign(epCode)
	elseif not isempty(arcNum) then
		data = p.arcNum(epCode)
	elseif not isempty(arcPage) then
		data = p.arcPage(epCode)
	elseif not isempty(arcCategory) then
		data = p.arcCategory(epCode)
	elseif not isempty(arcCharacterCategory) then
		data = p.arcCharacterCategory(epCode)
	elseif not isempty(sortkey) then
		data = p.calculateSortkey(epCode, sortBy)
	elseif not isempty(thumbnail) then
		data = p.thumbnail(epCode)
	elseif not isempty(transcript) then
		data = p.transcript(epCode)
	else
		data = epCode
	end
	return data
end

-- Functions for querying Module:Ep/Array to get title and/or pagename for output
function p.epInfo(epCode)
	local result

	local epInfos = mw.loadData('Module:Ep/Array')
	
	if epInfos[epCode] == nil then
		result = ""
	else
		result = epInfos[epCode]
	end

	return result

end

p.title = function(epCode)
	return p.epInfo(epCode)['title']
end

p.pagename = function(epCode)
	if p.epInfo(epCode)['pagename'] then
		pagename = p.epInfo(epCode)['pagename']
	else
		pagename = p.title(epCode)
	end
	return pagename
end

-- functions to parse epCode into campaign, season and epnumber, using EPISODE_DECODER

p.parseCode = function(epCode)
	if type(epCode) ~= "string" then
		epCode = epCode.args[1]
		end
	i, j = string.find(epCode,"x")
	prefix, epNum = '', ''
	if i then
		prefix = string.sub(epCode,1,i-1)
		epNum = string.sub(epCode,j+1)
		end
	return prefix, epNum
end

p.epNum = function(epCode)
	-- TM exception; find epNum in pagename
	if p.campaign(epCode) == 'TM' then
		local pagename = p.pagename(epCode)
		epNum = string.match(pagename, "%d+")
	else
		local prefix, epNum = p.parseCode(epCode)
	end
	-- remove leading zeroes
	if epNum then
		epNum = string.format("%d", epNum)
	end
	return epNum
end

-- Parse out shows and (optionally) seasons. Prevents TM from being parsed as having seasons.
p.campSeason = function(epCode)
	local campaign
	local season
	local decoder = mw.loadData('Module:Ep/Decoder')
	
	local prefix, epNum = p.parseCode(epCode)
	
	if prefix == 'TMOS' then
		campaign = 'TM'
	elseif (not tonumber(prefix) and prefix:match("^(.*%D)(%d+)$")) then
		campaign, season = prefix:match("^(.*%D)(%d+)$")
	elseif decoder[prefix] then
		campaign = prefix
	else
		campaign = 'Unknown Campaign'
	end
	
	-- Talks Machina season exception
	if campaign == 'TM' then
		season = ''
	end
	
	return campaign, season
end


p.prefix = function(epCode)
	prefix, epNum = p.parseCode(epCode)
	return prefix
end

p.campaign = function(epCode)
	campaign, season = p.campSeason(epCode)
	return campaign
end

p.campaignName = function(epCode, noItal)
	if not noItal then noItal = false
	end
	
	local campaign = p.campaign(epCode)
	
	if not campaign then
		return "Unknown Campaign"
	end
	
	local decoder = mw.loadData('Module:Ep/Decoder')
	if not decoder[campaign] then return "Unknown Campaign" end
	local campaignName = decoder[campaign]['title']
	
	if decoder[campaign]['italics'] == true and not noItal and campaignName then
		campaignName = "''" .. campaignName .. "''"
	end
	return campaignName
end

p.season = function(epCode)
	campaign, season = p.campSeason(epCode)
	return season
end

-- displays in the infobox "Episode" field before the episode number
p.seasonName = function(epCode)
	local decoder = mw.loadData('Module:Ep/Decoder')
	local campaign, season = p.campSeason(epCode)
	local valid_seasons
	local pagename
	local nameForDisplay
	local italics

	if campaign and decoder[campaign] then
		valid_seasons = decoder[campaign]['seasons'] or nil
	end

	-- if there's no season or the campaign doesn't have seasons, blank
	if isempty(season) or isempty(valid_seasons) then return ''
		
	-- is season valid? if not, blank
	elseif not searchKeys(season, valid_seasons) then return ''
	
	-- get season info from decoder
	else
		nameForDisplay = valid_seasons[season]['name'] or ''
		italics = valid_seasons[season]['italics'] or false
		end

	-- Default display is e.g. "Season 1", other build with season data from decoder
	if isempty(nameForDisplay) then
		nameForDisplay = 'Season ' .. season
	end
	return nameForDisplay
end

-- determine if episode is from a main campaign
p.isCampaign = function(epCode)
    local prefix, epNum = p.parseCode(epCode)
    return tonumber(prefix) and tonumber(epNum)
end

-- format epCode as C[Campaign]E[Episode]
p.prodCode = function(epCode)
    if p.isCampaign(epCode) then
        local prefix, epNum = p.parseCode(epCode)
        return 'C' .. prefix .. 'E' .. string.format("%02d", epNum)
    else
        return nil -- for non-campaign episodes
    end
end

p.arcInfo = function(epCode, arcIndex)
	local campaign = p.campaign(epCode)
	local epNum = tonumber(p.epNum(epCode))
	local decoder = mw.loadData('Module:Ep/Decoder')
	local arc = {}
	
	if decoder[campaign] then
		local arcs = decoder[campaign].arcs
		
		if not isempty(arcs) then
			if not arcIndex then
				for index, arcData in ipairs(arcs) do
					if epNum >= arcData.startEpisode and epNum <= arcData.endEpisode then
						arc = arcData
						break
					end
				end
			else
				local selectedArc = arcs[arcIndex]
				if selectedArc then
					arc = selectedArc
				end
			end
		end
	end
	return arc
end

p.arcNum = function(epCode)
	local arc = p.arcInfo(epCode)
	return arc.arcNum
end

p.arcName = function(epCode)
	local arc = p.arcInfo(epCode)
	return arc.title
end

p.arcPage = function(epCode)
	local campaign = p.campaign(epCode)
	local arc = p.arcInfo(epCode)
	local category = 'Campaign  ' .. campaign .. ' Arc ' .. arc.arcNum .. ': ' .. arc.title
	return category
end

p.arcCategory = function(epCode)
	local arc = p.arcInfo(epCode)
	return 'Category:' .. arc.title .. ' arc'
end

p.arcCharacterCategory = function(epCode)
	local arc = p.arcInfo(epCode)
	local arcName = removeLeadingThe(arc.title)
	return 'Category:Characters in the ' .. arcName .. ' arc'
end

-- get default naming for link to thumbnail
p.thumbnail = function(epCode)
	local thumbnailFile = epCode .. ' Episode Thumb.jpg'
	local campaign = p.campaign(epCode)

	-- Check if thumbnailFile exists, if not, fall back to default
	local title = mw.title.new('File:' .. thumbnailFile)
	if not title or not title.exists then
		if campaign == '3' then
			thumbnailFile = 'Bells Hells logo color.png'
		elseif campaign == 'CO' then
			thumbnailFile = 'Candela Obscura Logo.png'
		end
	end
	return thumbnailFile
end

p.transcript = function(epCode)
	local pagename = p.pagename(epCode)
	local transcript_link = 'Transcript:' .. pagename .. ''
	return transcript_link
end

-- for formatting the episode code
p.epCodeFormatted = function(epCode)
	local campaign = p.campaign(epCode)
	local decoder = mw.loadData('Module:Ep/Decoder')
	local small = mw.html.create('small')
	local formatKey
	local formatted
	
	if campaign ~= 'Unknown Campaign' and decoder[campaign] then
		formatKey = decoder[campaign]['codeFormat']
	end
	
	if formatKey then
		formatted = formatKey:gsub('{campaign}', campaign)
		if p.season(epCode) then
			formatted = formatted:gsub('{season}', p.season(epCode))
		end
		formatted = formatted:gsub('{epNum}', string.format("%02d", p.epNum(epCode)))
	else
		formatted = epCode
	end
	small:wikitext("(" .. formatted .. ")")
	formatted = tostring(small)
	return formatted
end

-- Function to calculate the sortkey for an episode code
p.calculateSortkey = function(epCode, sortBy)
	local epNum = p.epNum(epCode)
	local campaign, season = p.campSeason(epCode)
	if not season then season = "0" end

	-- map of campaign codes to sort values
	local campaignTransform = {
		["1"] = "01",
		["2"] = "02",
		["3"] = "03",
		["OS"] = "05",
		["CO"] = "06",
		["4SD"] = "07",
		["E"] = "08",
		["M"] = "09",
		["TM"] = "10",
		["U"] = "11",
		["Midst"] = "12",
		}

	-- transform campaign code
	if campaignTransform[campaign] then
		campaign = campaignTransform[campaign]
	end

	-- Retrieve the airdate for the episode code from Module:AirdateOrder/Array
	-- Distill airdate to YYYYMMDD
	local airdateOrder = require('Module:AirdateOrder')
	local airdate
	if airdateOrder.airdate(epCode) then
		airdate = airdateOrder.airdate(epCode):gsub("(%d%d%d%d)-(%d%d)-(%d%d).*", "%1%2%3")
	else
		-- Add episode code to maintenance category
        mw.log(tostring(epCode) .. " does not have an airdate in the airdate array.")
        airdate = 'ZZZZZZZZ'
	end
	-- Calculate the sortkey based on the specified sorting method
	local sortkey = campaign .. season .. string.format("%04d", epNum)
	if sortBy == "airdate" then
		mw.log(tostring(epCode) .. ' Airdate yes')
		sortkey = airdate .. sortkey
	else mw.log(tostring(epCode) .. " Airdate no")
	end
	return sortkey
end

-- function to remove a leading 'The' at the beginning of a string
function removeLeadingThe(s)
	local result, _ = string.gsub(s, '^The%s*', '', 1)
	return result
end

--function to determine if a string is empty
function isempty(s)
   return s == nil or s == ''
end


return p