Модуль:Сезон сериала

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Документация

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

См. также[править код]

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