Jump to content

Module:Rotten Tomatoes data

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Notacardoor (talk | contribs) at 00:16, 14 September 2021. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

local Error = require('Module:Error')

local p = {}

local months = {'January', 'February', 'March', 'April', 'May', 'June',
	'July', 'August', 'September', 'October', 'November', 'December'}

local aliasesQ = {
    RottenTomatoes          = "Q105584",
    RottenTomatoesScore     = "Q108403393",
    RottenTomatoesAverage   = "Q108403540",
    Fandango                = "Q5433722",
}

local aliasesP = {
	RottenTomatoesId        = "P1258",
	reviewScore             = "P444",
	reviewScoreBy           = "P447",
	numberOfReviews         = "P7887",
	pointInTime             = "P585",
	determinationMethod     = "P459",
    author                  = "P50",
    publisher               = "P123",
    statedIn                = "P248",
    language                = "P407",
    retrieved               = "P813",
    referenceURL            = "P854",
    archiveURL              = "P1065",
    title                   = "P1476",
    formatterURL            = "P1630",
    archiveDate             = "P2960",
}

-- Helper functions ------------------------------------------------------------
-- 0, nil, empty string, or empty table
local function falsy(x)
	if x == nil or x == '' or x == 0 then
		return true
	end
	return type(x) == 'table' and next(x) == nil
end

-- copied from Module:wd
local function parseDate(dateStr, precision)
    precision = precision or "d"

    local i, j, index, ptr
    local parts = {nil, nil, nil}

    if dateStr == nil then
        return parts[1], parts[2], parts[3]  -- year, month, day
    end

    -- 'T' for snak values, '/' for outputs with '/Julian' attached
    i, j = dateStr:find("[T/]")

    if i then
        dateStr = dateStr:sub(1, i-1)
    end

    local from = 1

    if dateStr:sub(1,1) == "-" then
        -- this is a negative number, look further ahead
        from = 2
    end

    index = 1
    ptr = 1

    i, j = dateStr:find("-", from)

    if i then
        -- year
        parts[index] = tonumber(mw.ustring.gsub(dateStr:sub(ptr, i-1), "^\+(.+)$", "%1"), 10)  -- remove '+' sign (explicitly give base 10 to prevent error)

        if parts[index] == -0 then
            parts[index] = tonumber("0")  -- for some reason, 'parts[index] = 0' may actually store '-0', so parse from string instead
        end

        if precision == "y" then
            -- we're done
            return parts[1], parts[2], parts[3]  -- year, month, day
        end

        index = index + 1
        ptr = i + 1

        i, j = dateStr:find("-", ptr)

        if i then
            -- month
            parts[index] = tonumber(dateStr:sub(ptr, i-1), 10)

            if precision == "m" then
                -- we're done
                return parts[1], parts[2], parts[3]  -- year, month, day
            end

            index = index + 1
            ptr = i + 1
        end
    end

    if dateStr:sub(ptr) ~= "" then
        -- day if we have month, month if we have year, or year
        parts[index] = tonumber(dateStr:sub(ptr), 10)
    end

    return parts[1], parts[2], parts[3]  -- year, month, day
end

-- nil dates precede all other (reasonable) dates since year becomes 1
local function datePrecedesDate(aY, aM, aD, bY, bM, bD)
    aY, aM, aD = aY or 1, aM or 1, aD or 1
    bY, bM, bD = bY or 1, bM or 1, bD or 1
    if aY < bY then return true end
    if aY > bY then return false end
    if aM < bM then return true end
    if aM > bM then return false end
    if aD < bD then return true end
    return false
end

local function format_date(Y, M, D)
	local s = ''
	if Y then
		s = Y
		if M then
			s = months[M] .. ' ' .. s
			if D then
				s = D .. ' ' .. s
			end
		end
	end
	return s
end

--------------------------------------------------------------------------------
-- Returns either QID, true, or ErrorString, false
local function getentityID(frame)
	local entityID = frame.args.qid
	local title = frame.args.title
	if falsy(entityID) then
		if falsy(title) then
			local currentPageentityID = mw.wikibase.getEntityIDForCurrentPage()
			if currentPageentityID then
				return currentPageentityID, true
			end
			return Error.error({'No Wikidata item connected to current page. Need qid or title parameter.'}), false
		else
			if not mw.title.makeTitle(0, title).exists then
				return Error.error({'Article ' .. title .. ' does not exist.'}), false
			end
			entityID = mw.wikibase.getEntityIDForTitle(title)
			if not entityID then
				return Error.error({'Article "' .. title .. '" has no connected Wikidata item.'}), false
			end
			return entityID, true
		end
	end
	--At this point we should have an entityID. Check if valid.
	if not mw.wikibase.isValidEntityID(entityID) then
		return Error.error({'Invalid Q-identifier.'}), false
	end
	if not mw.wikibase.entityExists(entityID) then
		return Error.error({'Wikidata item ' .. entityID .. ' does not exist.'}), false
	end
	return entityID, true
	
end

local function point_in_time(statement)
	local pointintime = statement.qualifiers[aliasesP.pointInTime]
	if not falsy(pointintime) then
		return parseDate(pointintime[1].datavalue.value.time)
	end
	return nil, nil, nil
end

local function access_date(statement)
	if statement.references[1] then
		local accessdate = statement.references[1].snaks[aliasesP.retrieved]
		if not falsy(accessdate) then
			return parseDate(accessdate[1].datavalue.value.time)
		end
	end
	return nil, nil, nil
end

local function date_from_statement(statement)
	local Y, M, D = point_in_time(statement)
	if Y then
		return Y, M, D
	end
	Y, M, D = access_date(statement)
	if Y then
		return Y, M, D
	end
	if statement.rank == 'preferred' then
		return 1, 1, 3
	elseif statement.rank == 'normal' then
		return 1, 1, 2
	else
		return 1, 1, 1
	end
end

local function reviewedby_RT(statement)
	local x = statement.qualifiers[aliasesP.reviewScoreBy]
	return not falsy(x) and x[1].datavalue.value.id == aliasesQ.RottenTomatoes
end

-- statement should be a review score (P444) statement by Rotten Tomatoes
local function score_type(statement)
	local y = statement.mainsnak.datavalue.value
	local x = statement.qualifiers[aliasesP.determinationMethod]
	if falsy(x) then
		return nil
	end
	x = x[1].datavalue.value.id

	if x == aliasesQ.RottenTomatoesScore then
		return 'percent'
	elseif x == aliasesQ.RottenTomatoesAverage then
		return 'average'
	elseif string.match(y, '^0%%$') or string.match(y, '^[1-9][0-9]%%$') or string.match(y, '^100%%$') then
		return 'percent'
	elseif string.match(y, '^0 percent$') or string.match(y, '^[1-9][0-9] percent$') or string.match(y, '^100 percent$') then
		return 'percent'
	elseif string.match(y, '^%d/10$') or string.match(y, '^%d.%d%d?/10$') then
		return 'average'
	elseif string.match(y, '^%d out of 10$') or string.match(y, '^%d.%d%d? out of 10$') then
		return 'average'
	end
	return nil
end

local function most_recent_score_statement(entityID, scoretype)
	scoretype = scoretype or 'percent'
	local score_statements = mw.wikibase.getAllStatements(entityID, aliasesP.reviewScore)
	local newest, nY, nM, nD
	for i, v in ipairs(score_statements) do
		local Y, M, D = date_from_statement(v)
		if v.rank ~= 'deprecated' and v.mainsnak.snaktype == 'value'
				and reviewedby_RT(v) and score_type(v)==scoretype
				and not datePrecedesDate(Y, M, D, nY, nM, nD) then
			nY, nM, nD = Y, M, D
			newest = v
		end
	end
	return newest
end

local function get_score(entityID, scoretype)
	scoretype = scoretype or 'percent'
	local x = most_recent_score_statement(entityID, scoretype)
	if falsy(x) then
		return nil
	end
	return x.mainsnak.datavalue.value
end

local function get_count(entityID)
	local x = most_recent_score_statement(entityID)
	if falsy(x) then
		return nil
	end
	local y = x.qualifiers[aliasesP.numberOfReviews]
	if falsy(y) then
		return nil
	end
	return string.match(y[1].datavalue.value.amount, '%d+') -- dont get sign
end

local function get_rtid(entityID)
	local x = mw.wikibase.getBestStatements(entityID, aliasesP.RottenTomatoesId)
	if falsy(x) then return nil end
	return x[1].mainsnak.datavalue.value
end

local function get_url(entityID)
	local rtid = get_rtid(entityID)
	if falsy(rtid) then return nil end
	local x = mw.wikibase.getBestStatements(aliasesP.RottenTomatoesId, aliasesP.formatterURL)
	return (string.gsub(x[1].mainsnak.datavalue.value, '$1', rtid))
end

local function get_date(entityID, part)
	local z = most_recent_score_statement(entityID)
	if falsy(z) then
		return nil
	end
	local Y, M, D = date_from_statement(z)
	if     part == 'year' then
		return Y
	elseif part == 'month' then
		return months[M]
	elseif part == 'day' then
		return D
	else
		return format_date(Y, M, D)
	end
end

local function get_access_date(entityID)
	local z = most_recent_score_statement(entityID)
	if falsy(z) then
		return nil
	end
	local Y, M, D = access_date(z)
	if not Y then
		Y, M, D = point_in_time(z)
	end
	return format_date(Y, M, D)
end

function p.test(frame)
	mw.log('starting')
	local entityID, is_good = getentityID(frame)
	if not is_good then
		return entityID -- which is the error message in this case
	end
	local command = mw.text.trim(frame.args[1])
	if falsy(command) then
		return Error.error({'Missing command.'})
	end
	local retval
	if command == 'score' then
		retval = get_score(entityID, 'percent')
	elseif command == 'average' then
		retval = get_score(entityID, 'average')
	elseif command == 'count' then
		retval = get_count(entityID)
	elseif command == 'rtid' then
		retval = get_rtid(entityID)
	elseif command == 'url' then
		retval = get_url(entityID)
	elseif command == 'date' then
		retval = get_date(entityID)
	elseif command == 'year' then
		retval = get_date(entityID, command)
	elseif command == 'month' then
		retval = get_date(entityID, command)
	elseif command == 'day' then
		retval = get_date(entityID, command)
	elseif command == 'access-date' then
		retval = get_access_date(entityID)
	else
		return Error.error({'Invalid command.'})
	end
	if falsy(retval) then
		return Error.error({'Data for ' .. command .. ' unavailable.'})
	end
	return retval
end

function p.test2(frame)
end

return p