본문으로 이동
메뉴 여닫기
  • 948 문서
  • 46 사용자
  • 3천 편집
창팝위키
환경 설정 메뉴 여닫기
개인 메뉴 여닫기
로그인하지 않음
편집을 하면 임시 계정이 생성되어 부여됩니다.

이 모듈에 대한 설명문서는 모듈:일간 신창섭 최근 목록/설명문서에서 만들 수 있습니다

local p = {}

local CONFIG = {
	apiuntoSource = "일간신창섭최근목록", -- LocalSettings.php의 $wgApiuntoSources 키
	templateName  = "유튜브 영상 항목",

	-- 시트의 열 순서(A~H)를 그대로 숫자 인덱스로 나열.
	-- values[i][col.url] 식으로 참조하기 위한 상수.
	col = {
		url       = 1, -- A: 원본 URL
		videoId   = 2, -- B: 추출된 영상 ID
		title     = 3, -- C: 제목
		channel   = 4, -- D: 채널명
		channelId = 5, -- E: 채널 ID
		views     = 6, -- F: 조회수
		published = 7, -- G: 게시일(UTC ISO 8601)
		duration  = 8, -- H: 길이(mm:ss, 이미 변환되어 있음)
	},
}

-- ── 날짜/시간 유틸리티 (os 라이브러리 미사용) ──────────────────

local DAYS_IN_MONTH = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

local function isLeapYear(y)
	return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0)
end

local function daysInMonth(y, m)
	if m == 2 and isLeapYear(y) then return 29 end
	return DAYS_IN_MONTH[m]
end

local function parseIso8601Utc(str)
	local y, mo, d, h, mi, s = str:match(
		"^(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)Z$"
	)
	if not y then return nil end
	return {
		year = tonumber(y), month = tonumber(mo), day = tonumber(d),
		hour = tonumber(h), min = tonumber(mi), sec = tonumber(s),
	}
end

local function utcToKst(t)
	local hour = t.hour + 9
	local day, month, year = t.day, t.month, t.year
	if hour >= 24 then
		hour = hour - 24
		day = day + 1
		if day > daysInMonth(year, month) then
			day = 1
			month = month + 1
			if month > 12 then
				month = 1
				year = year + 1
			end
		end
	end
	return { year = year, month = month, day = day, hour = hour, min = t.min }
end

local function formatKstTime(kst)
	local period = "오전"
	local hour12 = kst.hour
	if kst.hour == 0 then
		hour12 = 12
	elseif kst.hour == 12 then
		period = "오후"
		hour12 = 12
	elseif kst.hour > 12 then
		period = "오후"
		hour12 = kst.hour - 12
	end
	return string.format("%s %d:%02d", period, hour12, kst.min)
end

local function formatDateHeading(kst)
	return string.format("%d월 %d일", kst.month, kst.day)
end

local function abbreviateViews(n)
	if not n then return "" end

	local function trimUnit(value, unit)
		local truncated = math.floor(value * 10) / 10
		if truncated == math.floor(truncated) then
			return string.format("%d%s", truncated, unit)
		end
		return string.format("%.1f%s", truncated, unit)
	end

	if n < 1000 then
		return tostring(n)
	elseif n < 10000 then
		return trimUnit(n / 1000, "천")
	elseif n < 100000000 then
		return trimUnit(n / 10000, "만")
	else
		return trimUnit(n / 100000000, "억")
	end
end

-- ============================================================
-- 메인 진입점: {{#invoke:YoutubeList|main}}
-- ============================================================
function p.main(frame)
	local api = mw.ext.Apiunto

	local raw = api.fetch(CONFIG.apiuntoSource, "")
	if not raw or raw == "" or raw == "Could not retrieve API Data" then
		return "Apiunto(" .. CONFIG.apiuntoSource .. ") 응답을 가져오지 못했습니다."
	end

	-- fetch()는 JSON을 파싱하지 않고 원문 문자열을 그대로 주므로, 여기서 직접 디코드해야 한다.
	local ok, response = pcall(mw.text.jsonDecode, raw)
	if not ok or type(response) ~= "table" then
		return "Apiunto(" .. CONFIG.apiuntoSource .. ") 응답이 유효한 JSON이 아닙니다."
	end

	if not response.values then
		return "Apiunto(" .. CONFIG.apiuntoSource .. ") 응답에서 values를 찾지 못했습니다."
	end

	local parts = {}
	local currentDateKey = nil
	local col = CONFIG.col

	for _, row in ipairs(response.values) do
		-- row[n]이 빈 셀 이후는 아예 배열에서 생략될 수 있으므로(구글 시트 API 특성),
		-- 매번 or ""로 안전하게 기본값을 준다.
		local published = row[col.published] or ""
		local utc = parseIso8601Utc(published)

		if utc then -- 업로드 시각이 없는(아직 조회 안 된) 행은 건너뜀
			local kst = utcToKst(utc)
			local dateKey = string.format("%04d-%02d-%02d", kst.year, kst.month, kst.day)

			if dateKey ~= currentDateKey then
				table.insert(parts, [[<p class="mw--card-row" style="
						font-size: var(--font-size-large, 1.125rem);
						font-weight: var(--font-weight-medium, 500);
						margin: 0;
						">]] 
					.. formatDateHeading(kst) .. "</p>")
				
				currentDateKey = dateKey
			end

			local templateArgs = {
				["영상Id"]		= row[col.videoId] or "",
				["제목"]		= row[col.title] or "",
				["채널이름"]	= row[col.channel] or "",
				["채널Id"]		= row[col.channelId] or "",
				["조회수"]		= abbreviateViews(tonumber(row[col.views])),
				["업로드시각"]	= formatKstTime(kst),
				["길이"]		= row[col.duration] or "",
			}

			local rendered = frame:expandTemplate{
				title = CONFIG.templateName,
				args = templateArgs,
			}
			table.insert(parts, rendered)
		end
	end

	return table.concat(parts, "\n")
end

return p