Module:Ep ref

local p = {} -- function to create and format the reference function p.epRef(frame)

-- receiving parameters local ep = frame.args["ep"] or "" local part = frame.args["part"] or nil local intro = frame.args["intro"] or nil local more = frame.args["more"] or nil local vod = frame.args["vod"] or nil local startTime = frame.args[1] or nil local endTime = frame.args[2] or nil local cite = frame.args["cite"] or nil local noShow = frame.args["noshow"] or nil local twitch = frame.args["twitch"] or nil -- remove pipes in ep   if isempty(ep) then ep = ep:gsub('|', '|') end

-- convert the input into the standardized epCode local epMatcher = require('Module:Ep/Matcher') local epCode = epMatcher.matchCode(ep) local title = mw.title.getCurrentTitle local namespace = title.namespace local subpageText = title.subpageText local errorMsg local category -- appropriate error message if code doesn't exist; otherwise, get the full episode string if (epCode == '0x00' and isempty(vod)) or isempty(ep) then local args = frame:getParent.args local vodCheck = false for key, value in pairs(args) do       	if tostring(key):find('http') then vodCheck = true break end end if startTime:find('http') or endTime:find('http') then vodCheck = true end if vodCheck then errorMsg = "Custom episode missing 'vod=' label for url " category = getErrorCategory("Category:Ep refs with unlabeled custom vods", namespace, subpageText) else errorMsg = "Undefined Episode " category = getErrorCategory("Category:Ep refs missing episode parameter", namespace, subpageText) end return errorMsg .. category elseif not isempty(vod) and isempty(startTime) then -- if custom video with no startTime, link video directly epValue = '[' .. vod .. ' ' .. ep .. ']'   elseif not isempty(vod) then -- ignore epCode when it's a custom video link epValue = ep   else epValue = p.epForRef(epCode, noShow) end

-- get YouTube URL from Module:Ep/YTURLSwitcher/URLs local ytURL = mw.loadData('Module:Ep/YTURLSwitcher/URLs') local url if not isempty(vod) then url = vod elseif not isempty(part) then url = ytURL[epCode .. part] else url = ytURL[epCode] or ytURL["default"] end -- use Twitch timecode to generate YouTube timecode local ytConvertedTime local twitchWarning local twitchURL local assigned -- convert twitch timecode to YouTube if can if not isempty(twitch) and isempty(startTime) and not isempty(url) then ytConvertedTime = p.getOffsetYTTime(epCode, twitch) if ytConvertedTime then startTime = ytConvertedTime -- needs to be eventually converted to YouTube timestamp twitchWarning = 'Timestamp approximated from Twitch.' .. getErrorCategory("Category:Episode references using Twitch timestamps", namespace, subpageText) end end -- otherwise, twitch timecode is startTime if not isempty(twitch) and isempty(startTime) then -- placeholder because VOD isn't out yet startTime = twitch local epTwitch = require('Module:Ep/Twitch') twitchURL = epTwitch.turl(epCode) twitchWarning = '(subscription required).' if not isempty(twitchURL) then url = twitchURL assigned = true -- even if YT URL exists, it's about Twitch else url = nil end end if assigned == false then startTime = twitch end

--add timestamp to youtube URL local timedURL local longURL = false

if not isempty(url) then longURL = string.find(url, "?") end -- create linked startTime, if available if not isempty(startTime) then startTime = p.formatTimestamp(startTime) if not isempty(url) then timedURL = p.buildLinkedTimestamp(url, startTime) end end if not isempty(endTime) then endTime = p.formatTimestamp(endTime) end if not isempty(timedURL) then startTime = timedURL end local refText = epValue

if not isempty(part) then refText = refText .. ", part " .. part end if not isempty(part) and not isempty(startTime) then refText = refText .. "," end if not isempty(startTime) and not isempty(endTime) then refText = refText .. " from " .. startTime .. " through " .. endTime .. "."   elseif not isempty(startTime) and url == twitchURL then refText = refText .. " at " .. startTime .. " on Twitch" elseif not isempty(startTime) then refText = refText .. " at " .. startTime .. "."   else refText = refText .. "." end -- +error msg and cat if timestring has invalid timecodes errorMsg = 'Invalid Timecode ' category = getErrorCategory('Category:Ep refs with invalid timecode calls', namespace, subpageText) refText = string.gsub(		refText,		'invalid timecode',		errorMsg .. category)

-- citation format local supCite = mw.html.create('sup') supCite :wikitext("[citation needed]") local citeCategory if not isempty(cite) then citeCategory = getErrorCategory("Category:Articles needing citations/" .. epCode, namespace, subpageText) end if not isempty(intro) then refText = intro .. " " .. refText end if not isempty(cite) then refText = refText .. tostring(supCite) .. citeCategory end if not isempty(more) then refText = refText .. " " .. more end -- add Twitch warning, if Twitch timecode included if not isempty(twitchWarning) then refText = refText .. ' ' .. twitchWarning end return refText

end function p.epForRef(epCode, noShow) -- get the title and pagename for that epCode local epInfo = require('Module:Ep/Info') local epName = epInfo.title(epCode) local pageName = epInfo.pagename(epCode) -- if there is no epCode code, then just return the episode name without any further formatting if isempty(epCode) then return epName end -- (epCode) local small = mw.html.create('small') small :wikitext("(" .. epCode .. ")")   if epCode == '0x00' or isempty(epName) then local pageName = 'Unknown episode' local epName = pageName end local epValue if pageName:find("Talks Machina Special") and not isempty(noShow) then epName = split(pageName, ': ')[2] elseif pageName:find("Talks Machina") and not isempty(noShow) then epName = pageName:gsub('Talks Machina ', '#') elseif pageName == epName or not isempty(noShow) then epValue = pageName end if pageName ~= epName and not epValue then epValue = pageName .. '|' .. epName end if epValue then epValue = '"' .. epValue .. '"' else epValue = '' end

local fullEpValue = epValue .. " " .. tostring(small) if not isempty(hide) then return epValue else return fullEpValue end

end

-- function to split a string at a specified character function split(s, delimiter) result = {}; for match in (s..delimiter):gmatch("(.-)"..delimiter) do       table.insert(result, match); end return result; end --function to determine if a string is empty function isempty(s) return s == nil or s == '' end

-- to parse a string with single or multiple single-character delimiters -- thx https://stackoverflow.com/a/40180465/7417081 function multisplit(s, sep) local fields = {} local sep = sep or " " local pattern = string.format("([^%s]+)", sep) string.gsub(s, pattern, function(c) fields[#fields + 1] = c end) return fields end

-- for including error categories to only certain namespaces function getErrorCategory(category, namespace, subpageText) -- in article namespace, add page to article maintenance category if namespace == 0 then category = "" .. category .. "" -- in a testing space, print out the error on the page itself elseif subpageText == 'test' or subpageText == 'testcases' then category = ' would be in ' .. category .. ' ' -- if it's not a test page, don't add to category else category = '' end return category end

-- convert either (HH:)MM:SS or (HHh)MMmSSs to seconds function p.timestampToSeconds(s, delimiters) if s == 'invalid timecode' or isempty(s) then return s		end local totalSeconds local t = multisplit(s, delimiters) for _, x in ipairs(t) do   	if not tonumber(x) then return "invalid timecode" end end if #t == 3 then totalSeconds = 3600*t[1] + 60*t[2] + t[3] elseif #t == 2 then totalSeconds = 60*t[1] + t[2] elseif #t == 1 then totalSeconds = t[1] end return totalSeconds end

-- convert seconds to either MM:SS or HH:MM:SS (depending on length) function p.secondsToTimestamp(seconds) local formatted if tonumber(seconds) and tonumber(seconds) >= 3600 then formatted = string.format("%d:%02d:%02d",           math.floor(seconds / 3600) % 60,            math.floor(seconds / 60) % 60,            math.floor(seconds) % 60) elseif tonumber(seconds) then formatted = string.format("%d:%02d",       math.floor(seconds / 60) % 60,        math.floor(seconds) % 60) end return formatted end

-- zeropad a timestamp, or convert seconds to timestamp function p.formatTimestamp(timeValue) local seconds local timestamp if timeValue and timeValue:find('[hms:]') then seconds = p.timestampToSeconds(timeValue, 'hms:') elseif timeValue and tonumber(timeValue) then seconds = timeValue else timestamp = 'invalid timecode' end if timestamp ~= 'invalid timecode' then timestamp = p.secondsToTimestamp(seconds) end return timestamp end

-- create suffix for appending to YT URL function p.timestampToYtSuffix(timeValue) local suffix local timestamp = p.formatTimestamp(timeValue) if isempty(timestamp) then return "invalid timecode" end timeSnippet = multisplit(timestamp, 'hms:') for _, x in ipairs(timeSnippet) do   	if not tonumber(x) then return "invalid timecode" end end if #timeSnippet == 3 then suffix = string.format("%dh%02dm%02ds", timeSnippet[1], timeSnippet[2], timeSnippet[3]) elseif #timeSnippet == 2 then suffix = string.format("%dm%02ds", timeSnippet[1], timeSnippet[2]) end suffix = 't=' .. suffix return suffix end

-- combine YT url and url-formatted timestamp function buildYouTubeLink(videoLink, timestamp) local fullLink local suffix = p.timestampToYtSuffix(timestamp) if suffix == 'invalid timecode' then return suffix end if videoLink:find('youtube.com/[%w_-]+') then fullLink = videoLink .. '&' .. suffix elseif videoLink:find('youtu.be/[%w_-]+') or videoLink:find('twitch.tv')then fullLink = videoLink .. '?' .. suffix end return fullLink end

function p.buildLinkedTimestamp(videoLink, timestamp) local url local linkedTimestamp timestamp = p.formatTimestamp(timestamp) url = buildYouTubeLink(videoLink, timestamp) if url == 'invalid timecode' then linkedTimestamp = 'invalid timecode' else linkedTimestamp = '[' .. url .. ' ' .. timestamp .. "] "	end return linkedTimestamp end

-- for generating placeholder timecodes from Twitch function p.getOffsetYTTime(epCode, twitchStartTime) local epTwitch = require('Module:Ep/Twitch') local ytSeconds local ytTimecode

local seconds = p.timestampToSeconds(twitchStartTime, "hms:") local offset = epTwitch.offset(epCode) if not isempty(offset) then offsetSeconds = p.timestampToSeconds(offset, "hms:") ytSeconds = seconds - offsetSeconds ytTimecode = p.secondsToTimestamp(ytSeconds) else ytTimecode = nil end return ytTimecode end

p.anchor = function(frame) local ep = frame.args["ep"] or "" local part = frame.args["part"] or "" local intro = frame.args["intro"] or "" local more = frame.args["more"] or "" local vod = frame.args["vod"] or "" local noShow = frame.args["noshow"] or nil local startTime = frame.args[1] or "" local endTime = frame.args[2] or "" local cite = frame.args["cite"] or nil local twitch = frame.args["twitch"] or nil --don't match if cite field is filled out if not isempty(cite) then return nil end

-- convert the input into the standardized epCode local epMatcher = require('Module:Ep/Matcher') local epCode = epMatcher.matchCode(ep) if not isempty(startTime) then startTime = p.timestampToSeconds(startTime, "hms:") end if not isempty(endTime) then endTime = p.timestampToSeconds(endTime, "hms:") end

anchor = ep .. part .. intro .. more .. vod .. noShow .. twitch .. startTime .. endTime -- alphanumeric only anchor = anchor:gsub('%W', '') return anchor end

return p