Module:VideoGallery
From Dune Awakening DB
Documentation for this module may be created at Module:VideoGallery/doc
-- Module:VideoGallery
-- Handles video gallery display with database integration
local p = {}
-- Database connection function
local function getDB()
return mw.ext.LuaSQLite.open('duneawakening')
end
-- Convert seconds to MM:SS format
local function formatDuration(seconds)
if not seconds or seconds == 0 then
return ""
end
local minutes = math.floor(seconds / 60)
local secs = seconds % 60
return string.format("%d:%02d", minutes, secs)
end
-- Get video thumbnail URL
local function getVideoThumbnail(youtubeId)
-- YouTube thumbnail URL pattern
return string.format("https://img.youtube.com/vi/%s/mqdefault.jpg", youtubeId)
end
-- Parse video notes from markdown-style text
local function parseVideoNotes(notes)
if not notes or notes == "" then
return "<p>No notes available for this video.</p>"
end
-- Convert markdown-style formatting
-- Headers: ### becomes <h3>
notes = notes:gsub("###%s*([^\n]+)", "<h3>%1</h3>")
-- Lists: lines starting with - become <li>
notes = notes:gsub("\n%-%s*([^\n]+)", "\n<li>%1</li>")
-- Wrap consecutive <li> items in <ul>
notes = notes:gsub("(<li>.-</li>)\n(<li>)", "%1\n<ul>\n%2")
notes = notes:gsub("(</li>)\n([^<])", "%1\n</ul>\n%2")
-- Line breaks
notes = notes:gsub("\n\n", "</p><p>")
notes = "<p>" .. notes .. "</p>"
return notes
end
-- Main function to render videos for a category
function p.renderVideos(frame)
local category = frame.args.category or "featured"
local db = getDB()
-- Define tag mappings for each category
local categoryTags = {
featured = "Featured",
leveling = "Leveling",
crafting = "Crafting",
building = "Building",
pve = "PVE",
pvp = "PVP"
}
local tagName = categoryTags[category] or "Featured"
-- Query to get videos by tag and group by purpose
local query = [[
SELECT DISTINCT
v.video_id,
v.youtube_id,
v.title,
v.channel_title,
v.duration_sec,
v.purpose,
v.description,
v.video_notes,
v.published_at
FROM videos v
JOIN video_tags vt ON v.video_id = vt.video_id
JOIN tags t ON vt.tag_id = t.tag_id
WHERE t.tag_name = ?
ORDER BY v.purpose, v.published_at DESC
]]
local stmt = db:prepare(query)
stmt:bind_values(tagName)
local videos = {}
local purposes = {}
local purposeOrder = {}
-- Group videos by purpose
for row in stmt:rows() do
local purpose = row.purpose or "General"
if not videos[purpose] then
videos[purpose] = {}
table.insert(purposeOrder, purpose)
end
table.insert(videos[purpose], {
video_id = row.video_id,
youtube_id = row.youtube_id,
title = row.title,
channel_title = row.channel_title,
duration = formatDuration(row.duration_sec),
duration_sec = row.duration_sec,
description = row.description,
video_notes = row.video_notes,
published_at = row.published_at
})
end
stmt:finalize()
-- Build HTML output
local output = {}
-- Special ordering for certain categories
local purposeOrderMap = {
leveling = {
"Leveling 1-20",
"Leveling 21-40",
"Leveling 41-60",
"Leveling Tips",
"General"
},
crafting = {
"Basic Crafting",
"Advanced Crafting",
"Crafting Stations",
"Crafting Tips",
"General"
},
building = {
"Base Building",
"Advanced Structures",
"Defense Building",
"Building Tips",
"General"
}
}
-- Use custom order if available
if purposeOrderMap[category] then
purposeOrder = purposeOrderMap[category]
end
-- Render each purpose section
for _, purpose in ipairs(purposeOrder) do
if videos[purpose] then
table.insert(output, string.format([[
<div class="video-section">
<div class="video-section-header">
<h3 class="section-title">%s</h3>
</div>
<div class="video-grid">]], purpose))
-- Render video cards
for _, video in ipairs(videos[purpose]) do
local thumbnail = getVideoThumbnail(video.youtube_id)
table.insert(output, string.format([[
<div class="video-card" data-video-id="%s" data-youtube-id="%s">
<div class="video-thumbnail" style="background-image: url('%s');">
<span class="video-duration">%s</span>
</div>
<div class="video-info">
<div class="video-title">%s</div>
<div class="video-channel">%s</div>
</div>
</div>]],
video.video_id,
video.youtube_id,
thumbnail,
video.duration,
mw.text.encode(video.title),
mw.text.encode(video.channel_title)
))
end
table.insert(output, [[
</div>
</div>]])
end
end
db:close()
if #output == 0 then
return '<div class="video-section"><p style="text-align: center; color: #E3BB7A;">No videos found for this category.</p></div>'
end
return table.concat(output, "\n")
end
-- Function to get all video data as JSON for JavaScript
function p.getVideoData(frame)
local db = getDB()
local query = [[
SELECT
v.video_id,
v.youtube_id,
v.title,
v.channel_title,
v.author,
v.duration_sec,
v.purpose,
v.description,
v.video_notes,
v.published_at,
GROUP_CONCAT(t.tag_name) as tags
FROM videos v
LEFT JOIN video_tags vt ON v.video_id = vt.video_id
LEFT JOIN tags t ON vt.tag_id = t.tag_id
GROUP BY v.video_id
ORDER BY v.published_at DESC
]]
local stmt = db:prepare(query)
local videoData = {}
for row in stmt:rows() do
local tags = {}
if row.tags then
for tag in string.gmatch(row.tags, "[^,]+") do
table.insert(tags, mw.text.trim(tag))
end
end
videoData[tostring(row.video_id)] = {
video_id = row.video_id,
youtube_id = row.youtube_id,
title = row.title,
channel_title = row.channel_title,
author = row.author,
duration = formatDuration(row.duration_sec),
duration_sec = row.duration_sec,
purpose = row.purpose,
description = row.description,
video_notes = parseVideoNotes(row.video_notes),
published_at = row.published_at,
tags = tags
}
end
stmt:finalize()
db:close()
return mw.text.jsonEncode(videoData)
end
-- Function to render a single video player (called via AJAX)
function p.renderVideoPlayer(frame)
local videoId = frame.args.video_id or frame.args[1]
if not videoId then
return '<div class="error">No video ID provided</div>'
end
local db = getDB()
local query = [[
SELECT
v.*,
GROUP_CONCAT(t.tag_name) as tags
FROM videos v
LEFT JOIN video_tags vt ON v.video_id = vt.video_id
LEFT JOIN tags t ON vt.tag_id = t.tag_id
WHERE v.video_id = ?
GROUP BY v.video_id
]]
local stmt = db:prepare(query)
stmt:bind_values(videoId)
local video = nil
for row in stmt:rows() do
video = row
break
end
stmt:finalize()
db:close()
if not video then
return '<div class="error">Video not found</div>'
end
-- Parse published date
local publishedDate = video.published_at:match("(%d%d%d%d%-%d%d%-%d%d)")
-- Build player HTML
local output = string.format([[
<div class="video-embed-container">
<iframe src="https://www.youtube.com/embed/%s?rel=0&modestbranding=1"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</div>
<div class="video-details-header">
<h2 class="video-main-title">%s</h2>
<div class="video-meta">
<span class="video-author">👤 %s</span>
<span class="video-date">📅 %s</span>
</div>
</div>
<div class="video-notes-section">
<div class="notes-header">Video Notes</div>
<div class="notes-content">
%s
</div>
</div>]],
video.youtube_id,
mw.text.encode(video.title),
mw.text.encode(video.author or video.channel_title),
publishedDate,
parseVideoNotes(video.video_notes)
)
return output
end
return p
