Документация

Документацию смотри на странице шаблона {{Сезон сериала}}

См. также

править
require('strict')

local listOfTitlesArticles = require('Модуль:Сезон сериала/БД списки эпизодов')
local wikidata = require('Модуль:Wikidata').formatProperty
local getArgs = require('Модуль:Arguments').getArgs
local yesno = require("Модуль:Yesno")

local format = mw.ustring.format
local match = mw.ustring.match
local find = mw.ustring.find
local gsub = mw.ustring.gsub
local sub = mw.ustring.sub


local p = {}
local cache = {}

--------------------------------------------------------------------------------
--- LOCALS
--------------------------------------------------------------------------------

--- Returns true if param has value (not nil and not empty string)
--- @param param string
--- @return boolean
local function hasValue(param)
	if param and param:find('%S') then
		return true
	end
end

--- Returns true if page exist and not redirect
--- @param param string
--- @return boolean
local function hasPage(page)
	if page and page.exists and page.redirectTarget ~= mw.title.getCurrentTitle() then
		return true
	end
	return false
end

--- Returns a delinked string
--- @param link string
--- @return string
local function getPlainText(text)
	local text = mw.text.trim(text, '%[%]%s'):gsub('<.->.-$', '')

	if text:find('^{{нп')
		or text:find('^%[%[нп')
		or text:find('^{{')
	then
		text = mw.text.trim(text, '{}%[%]:%s')
		text = text:gsub('^[^|]-|', '')
	end

	if text:find('^%[%[:') then
		text = mw.text.trim(text, '{}%s')
		text = text:gsub('^%[%[:[^:]-:', '')
	end

	text = text:gsub('%s*|.*$', '')
			:gsub('%s*{.*$', '')
			:gsub('<.->.-<.->', ' ')
			:gsub('<.->', ' ')
			:gsub('%s+', ' ')

	text = mw.text.trim(text, '%s')
	return text
end

--- Returns a formatted link to the list of episodes article.
--- @param listOfEpisodesArticle string
--- @return string
local function getListOfEpisodesLink(listOfEpisodesArticle)
	local listOfEpisodesPage = mw.title.new(listOfEpisodesArticle, 0)
	if hasPage(listOfEpisodesPage) then
		return format('[[%s|Список серий]]', listOfEpisodesArticle)
	end
end

--- Returns an article link.
--- @param article string The article's title.
--- @param pipedLink string The piped link.
--- @return string
local function getArticleLink(article, pipedLink)
	local articleLink = mw.title.new(article, 0)
	if hasPage(articleLink) then
		if not hasValue(pipedLink) then
			return '[[' .. article .. ']]'
		end
		return '[[' .. article .. '|' .. pipedLink .. ']]'
	end
end

--- Returns the disambiguation without the '(year) TV series,' part.
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getShortDisambiguation(disambiguation)
	local shortDisambiguation = gsub(disambiguation, '^.+,%s*', '')
	return shortDisambiguation
end

--- Returns the current season number from the disambiguation.
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getCurrentSeasonNumberFromDisambiguation(disambiguation)
	local commasText, shortDisambiguation, prefix, number, postfix
	if match(disambiguation, '%d+[—%-]%d+') then
		prefix, number, postfix = match(disambiguation, '^(%D*)(%d+%-?—?%d+)(%D*)$')
	elseif disambiguation:find(', ') then
		commasText = match(disambiguation, '^.+,%s*')
		shortDisambiguation = getShortDisambiguation(disambiguation)
		prefix, number, postfix = match(shortDisambiguation, '^(%D*)(%d+)(%D*)$')
	else
		prefix, number, postfix = match(disambiguation, '^(%D*)(%d+)(%D*)$')
	end
	postfix = postfix or ''
	commasText = commasText or ''
	prefix = commasText .. (prefix or '')
	return number, prefix, postfix
end

--- Returns the type of word used for 'season' in the disambiguation.
--- @param disambiguation string The article's disambiguation.
--- @return string
local function getSeasonType(disambiguation)
	local currentTitleText = mw.title.getCurrentTitle().text
	local seasonTypes = {
		'Мультсериал', 'Телесериал', 'Телепередача', 'Спецвыпуски', 'Телешоу',
		'Шоу', 'Анимационный сериал', 'Аниме-сериал', 'Веб-сериал', 'Мини-сериал',
		'Серии мультсериалов', 'Телевизионный сериал', 'Сюжетная арка', 'Выпуск',
		'Сериал', 'Аниме', 'Серии', 'Сезоны', 'Сезона', 'Сезон', 'Серии с'
	}
	for _, seasonType in pairs(seasonTypes) do
		if match(disambiguation, sub(seasonType, 3)) then
			if find(currentTitleText, '^Список')
				and not find(currentTitleText, '%d+[—%-]%d+')
			then
				if seasonType == 'Сезона' then
					return 'Сезоны с'
				elseif seasonType == 'Серии' then
					return 'Серии с'
				end
			else
				return seasonType
			end
		end
	end

	return 'Сезон'
end

--- Returns the disambiguation from the title.
--- @param title string The article's title.
--- @return string | nil, string | nil
local function getDisambiguation(title)
	local disambiguationLast, disambiguationNext
	local pattern = '%s*%(([^()]+)%)$'

	disambiguationLast = match(title, pattern)
	local title = gsub(title, pattern, '')
	disambiguationNext = match(title, pattern)

	if disambiguationLast ~= '' then
		return disambiguationLast, disambiguationNext
	end
end

--- Returns the show's name from the title.
--- @param title string The article's title.
--- @return string
local function getShowName(title)
	while match(title, '%)%s*$') do
		title = gsub(title, '%s+%b()%s*$', '')
	end
	return title
end

--- Returns 'true' if the given link is valid; nil otherwise.
--- A link is valid in the following cases:
---	-- A season article exists.
---	-- A redirect exists to a season section.
---
--- A link is invalid in the following cases:
---	-- A season article or redirect do not exist.
---	-- A redirect exists, but it is a general redirect and not for any specific season section.
---
--- Note: Return values are not booleans as the returned value is used in template space.
--- @param title string The article's title.
--- @return string | nil
local function isLinkValid(title)
	local article = mw.title.new(title)

	-- Article or redirect do not exist; Not a valid link.
	if not article or not article.exists then
		return nil
	end

	local redirectTarget = article.redirectTarget

	-- Article exists and is not a redirect; Valid link.
	if not redirectTarget then
		return true
	end

	local fullLink = redirectTarget.fullText
	local isSection = fullLink:find("#")

	-- Article is a section redirect; Valid link.
	if isSection then
		return true
	end

	-- Article is a general redirect; Not a valid link.
	return nil
end

--- Returns a season article title and a piped link.
--- @param title string The article's title.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string, string
local function getArticleTitleAndPipedLink(title, seasonNumberDiff)
	local excludeList = {
		'Американская история ужасов: Дом-убийца', 'Американская история ужасов: Психбольница',
		'Американская история ужасов: Шабаш', 'Американская история ужасов: Фрик-шоу',
		'Американская история ужасов: Отель', 'Американская история ужасов: Роанок',
		'Американская история ужасов: Культ', 'Американская история ужасов: Апокалипсис',
		'Американская история ужасов: 1984', 'Американская история ужасов: Двойной сеанс',
		'Американская история ужасов: Нью-Йорк', 'Американская история ужасов: Нежность'
	}
	local iExcludeList = {
		['Дом-убийца']=1, ['Психбольница']=2, ['Шабаш']=3, ['Фрик-шоу']=4,
		['Отель']=5, ['Роанок']=6, ['Культ']=7, ['Апокалипсис']=8, ['1984']=9,
		['Двойной сеанс']=10, ['Нью-Йорк']=11, ['Нежность']=12
	}

	local showName = getShowName(title)
	local disambiguationLast, disambiguationNext = getDisambiguation(title)

	if seasonNumberDiff == 0 and not hasValue(disambiguationLast) then
		if title:find('мериканская история ужасов') then
			return title, title:match(':%s*([^:]-)%s*$')
		else
			return title, nil
		end
	elseif not hasValue(disambiguationLast) then
		if title:find('мериканская история ужасов') then
			local index = iExcludeList[title:match(':%s*([^:]-)%s*$')]
			local article = excludeList[index + seasonNumberDiff]
			if article then
				return article, article:match(':%s*([^:]-)%s*$')
			else
				return nil, nil
			end
		elseif not showName:find('%s+(%d+)$') then
			return '', nil
		end
	end

	if hasValue(disambiguationNext) then
		disambiguationNext = ' (' .. disambiguationNext .. ')'
	else
		disambiguationNext = ''
	end

	local prefix, seasonType, seasonNumber, postfix
	local shortDisambiguation, showNameModified
	local pipedLink

	if hasValue(disambiguationLast) then
		seasonType = getSeasonType(disambiguationLast)
		seasonNumber, prefix, postfix = getCurrentSeasonNumberFromDisambiguation(disambiguationLast)
		pipedLink = seasonType
	end

	if not hasValue(seasonNumber) then
		showNameModified, seasonNumber = match(showName, '(.+)%s+(%d+)$')
	end

	if tonumber(seasonNumber) then
		seasonNumber = tonumber(seasonNumber) + seasonNumberDiff
	end

	seasonNumber = tostring(seasonNumber)
	if hasValue(seasonNumber) and hasValue(pipedLink) then
		pipedLink = pipedLink .. ' ' .. seasonNumber
	elseif hasValue(seasonNumber) then
		pipedLink = 'Сезон ' .. seasonNumber
	end

	-- Titles such as 'Big Brother 1 (American season)'.
	if hasValue(showNameModified) and hasValue(disambiguationLast) then
		return showNameModified .. ' ' .. seasonNumber .. ' (' .. disambiguationLast .. ')', pipedLink
	-- Titles such as 'Big Brother Brasil 1'.
	elseif hasValue(showNameModified) then
		return showNameModified .. ' ' .. seasonNumber, pipedLink
	else
		return showName .. disambiguationNext .. ' (' .. prefix .. seasonNumber .. postfix .. ')', pipedLink
	end
end

--- Returns the article's title either from args (usually from /testcases) or from the page itself.
--- @param args table The args invoking the module.
--- @return string
local function getTitle(title)
	if not hasValue(title) then
		title = mw.title.getCurrentTitle().text
	end
	return title
end

--- Returns 'true' if the given season link is valid; nil otherwise.
--- @param args table The args invoking the module.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string | nil
local function isSeasonLinkValid(args, seasonNumberDiff)
	if yesno(args.link) == false then
		return nil
	end

	if hasValue(args.link) and args.link:find('%[%[.*%]%]') then
		return true
	elseif hasValue(args.link) and isLinkValid(getPlainText(args.link)) then
		return isLinkValid(getPlainText(args.link))
	end

	local title = getTitle(args.title)
	local articleTitle = getArticleTitleAndPipedLink(title, seasonNumberDiff)
	if hasValue(articleTitle) and articleTitle ~= mw.title.getCurrentTitle().fullText then
		return isLinkValid(articleTitle)
	end
end

--- Returns a season article link.
--- @param args table The args invoking the module.
--- @param seasonNumberDiff number The number difference between the current season and the other season.
--- @return string
local function getSeasonArticleLink(args, seasonNumberDiff)
	if yesno(args.link) == false then
		return nil
	end

	local title, articleTitle, pipedLink

	if hasValue(args.link) and args.link:find('%[%[.*%]%]') then
		return args.link
	elseif hasValue(args.link) and isLinkValid(getPlainText(args.link)) then
		articleTitle, pipedLink = getArticleTitleAndPipedLink(args.link, 0)
	end

	if not hasValue(articleTitle) then
		title = getTitle(args.title)
		articleTitle, pipedLink = getArticleTitleAndPipedLink(title, seasonNumberDiff)
	end

	if hasValue(articleTitle) and articleTitle ~= mw.title.getCurrentTitle().fullText then
		return getArticleLink(articleTitle, pipedLink)
	end
end

-- Декоратор для передачи аргуметнов в функцию
local function makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame)
		return p[funcName](args)
	end
end

--------------------------------------------------------------------------------
--- MAIN CLASS
--------------------------------------------------------------------------------

-- Проверка Викидинных пока не реализована
-- Предыдущий сезон ### wikidata|P155
-- Следующий сезон ### wikidata|P156
-- ОБРАЗЕЦ: wikidata({args={{property='P1811', value='Список эпизодов телесериала «Во все тяжкие»', from='Q126004'}})

--- Returns 'true' if the season link for the next season is valid; nil otherwise.
--- @param args table The args invoking the module.
--- @return string | nil
p.isNextSeasonLinkValid = makeInvokeFunc('_isNextSeasonLinkValid')
function p._isNextSeasonLinkValid(args)
	return isSeasonLinkValid(args, 1)
end

--- Returns 'true' if the season link for the previous season is valid; nil otherwise.
--- @param args table The args invoking the module.
--- @return string | nil
p.isPrevSeasonLinkValid = makeInvokeFunc('_isPrevSeasonLinkValid')
function p._isPrevSeasonLinkValid(args)
	return isSeasonLinkValid(args, -1)
end

--- Returns the next season article title.
--- @param args table The args invoking the module.
--- @return string
p.getNextSeasonArticle = makeInvokeFunc('_getNextSeasonArticle')
function p._getNextSeasonArticle(args)
	return getSeasonArticleLink(args, 1)
end

--- Returns the previous season article title.
--- @param args table The args invoking the module.
--- @return string
p.getPrevSeasonArticle = makeInvokeFunc('_getPrevSeasonArticle')
function p._getPrevSeasonArticle(args)
	return getSeasonArticleLink(args, -1)
end

--- @param args table The args invoking the module.
--- @return string
p.getInfoboxHeader = makeInvokeFunc('_getInfoboxHeader')
function p._getInfoboxHeader(args)
	local title = getTitle(args.title)
	local header = getShowName(title)
	return header
end

--- Returns the relevant text for the sub-header field.
--- @param args table The args invoking the module.
--- @return string | nil
p.getInfoboxSubHeader = makeInvokeFunc('_getInfoboxSubHeader')
function p._getInfoboxSubHeader(args)
	local title = getTitle(args.title)
	local disambiguation = getDisambiguation(title)
	local seasonNumber = args.season_number or args.series_number
	local seasonType, shortDisambiguation

	if not hasValue(disambiguation) and not hasValue(seasonNumber) then
		return nil
	end

	if not hasValue(seasonNumber) then
		shortDisambiguation = getShortDisambiguation(disambiguation)
		seasonNumber = getCurrentSeasonNumberFromDisambiguation(shortDisambiguation)
	end

	if hasValue(disambiguation) then
		seasonType = getSeasonType(disambiguation)
	end

	if not hasValue(seasonType) and hasValue(seasonNumber) then
		return 'Сезон ' .. seasonNumber
	elseif hasValue(seasonNumber) then
		return seasonType .. ' ' .. seasonNumber
	end
end

--- Returns a formatted link to the list of episodes article.
---
--- The returned link is in the style of:
--- [List of <series name> <disambiguation, if present> episodes <range, if present>|List of episodes]
---
--- The link will only return if the page exists.
--- @param args table The args invoking the module.
--- @return string | nil
p.getListOfEpisodes = makeInvokeFunc('_getListOfEpisodes')
function p._getListOfEpisodes(args)
	--[=[== Схема-приоритет поиска рабочей ссылки типа [[Список эпизодов телесериала «Название»]] ==
	args.link -> wikidata -> database -> zeronamespace -> nil
	]=]

	if find(mw.title.getCurrentTitle().text, '^Список')
		or yesno(args.link) == false
	then
		return nil
	end

	-- Проверка args.link
	if hasValue(args.link) and args.link:find('%[%[.*%]%]') then
		return getListOfEpisodesLink(getPlainText(args.link))
	elseif hasValue(args.link) and isLinkValid(getPlainText(args.link)) then
		return getListOfEpisodesLink(args.link)
	end

	-- list of episodes (P1811)
	-- ОБРАЗЕЦ: wikidata({args={{property='P1811', value='Список эпизодов телесериала «Во все тяжкие»', from='Q126004'}})
	-- Проверка Викиданных
	--[=[ ДОРАБОТАТЬ!!!!
	local linkFromWikidata = wikidata({args={property='P1811', value='', from=args.from or '', title=args.title, link=args.link}})
	if hasValue(linkFromWikidata) then
		local wikidataLinkText = getPlainText(linkFromWikidata)
		local wikidataLink = getListOfEpisodesLink(wikidataLinkText)
		if hasValue(wikidataLink) then
			return wikidataLink
		end
	end
	]=]

	-- Остальные проверки
	local title = getTitle(args.title)
	local showName = getShowName(title)
	if not hasValue(showName) then return nil end

	-- Проверка Базы данных
	local showName2, shortShowName, shortShowName2 = '~', '~', '~'
	if showName:find('[«"]') then
		showName2 = showName:gsub('«', '„'):gsub('»', '“')
		shortShowName = match(showName, '[«"](.-)["»]')
	end

	if showName:find('[:.]') then
		shortShowName2 = showName:gsub('[:.].-$', '')
	end

	local findedName1, findedName2, findedName3, findedName4
	local pattern, articleText, databaseLink

	for _, searchName in ipairs({shortShowName2, showName2, shortShowName, showName}) do
		pattern = format('{{([^{«]+[« ]%s[ »][^»}]*)}}', searchName)
		articleText = match(listOfTitlesArticles, pattern)
		if hasValue(articleText) then
			databaseLink = getListOfEpisodesLink(articleText)
			if hasValue(databaseLink) then
				return databaseLink
			end
		end
	end

	-- Проверка основного пространства имён википедии
	local listOfEpisodesArticle, listOfEpisodesLink
	local listOfShowNames = {showName, showName2, shortShowName, shortShowName2}

	--- ??? работает ли кэш ???
	for _, showTitle in pairs(listOfShowNames) do
		if cache[showTitle] then
			return cache[showTitle]
		end
	end

	local listOfAllTypes = {
	'серий', 'эпизодов', 'выпусков',
	'серий телесериала', 'эпизодов телесериала', 'серий телесериала', 'эпизодов сериала',
	'серий мультсериала', 'эпизодов мультсериала', 'серий аниме', 'эпизодов аниме',
	'эпизодов телепередачи', 'эпизодов шоу', 'выпусков телепередачи', 'выпусков телешоу'
	-- не используются:
	-- 'выпусков шоу', 'серий сериала', 'серий телепередачи', 'серий телешоу', 'серий шоу',
	-- 'эпизодов анимационного сериала', 'эпизодов аниме-сериала', 'эпизодов веб-сериала', 'эпизодов мини-сериала'
	}

	for _, insertThis in pairs(listOfAllTypes) do
		for _, showTitle in pairs(listOfShowNames) do
			listOfEpisodesArticle = format('Список %s «%s»', insertThis, showTitle)
			listOfEpisodesLink = getListOfEpisodesLink(listOfEpisodesArticle)
			if listOfEpisodesLink then
				--- ??? работает ли кэш ???
				cache[showTitle] = listOfEpisodesLink
				return listOfEpisodesLink
			end
			listOfEpisodesArticle = format('Список %s %s', insertThis, showTitle)
			listOfEpisodesLink = getListOfEpisodesLink(listOfEpisodesArticle)
			if listOfEpisodesLink then
				--- ??? работает ли кэш ???
				cache[showTitle] = listOfEpisodesLink
				return listOfEpisodesLink
			end
		end
	end
end

return p