|
|
| Line 1: |
Line 1: |
| -- Module:VideoGallery | | -- Module:VideoGallery (no LuaSQLite required) |
| -- Handles video gallery display with database integration
| | local p = {} |
| | local json = mw.text.jsonEncode |
| | local ed = mw.ext.externaldata |
|
| |
|
| local p = {}
| | ----------------------------------------------------------------- |
| | | -- collectRows() |
| -- Database connection function | | -- Returns: (rowsList , id→row table) |
| local function getDB()
| | ----------------------------------------------------------------- |
| return mw.ext.LuaSQLite.open('duneawakening')
| | local function collectRows() |
| end
| | local rows = ed.getExternalData() |
| | | local byId = {} |
| -- Convert seconds to MM:SS format | | for _, r in ipairs( rows ) do |
| local function formatDuration(seconds) | | byId[ r.id ] = r |
| 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 | | end |
| | | return rows, byId |
| -- 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 | | end |
|
| |
|
| -- Main function to render videos for a category | | ----------------------------------------------------------------- |
| function p.renderVideos(frame)
| | -- renderVideos( frame ) |
| local category = frame.args.category or "featured"
| | -- Renders the cards for one category (passed as |category=...) |
| local db = getDB()
| | ----------------------------------------------------------------- |
|
| | function p.renderVideos( frame ) |
| -- Define tag mappings for each category
| | local category = ( frame.args.category or '' ):lower() |
| local categoryTags = {
| | local rows = ed.getExternalData() |
| 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
| | local out = { '<div class="video-grid">' } |
| function p.getVideoData(frame)
| | for _, v in ipairs( rows ) do |
| local db = getDB() | | if ( v.category or '' ):lower() == category then |
| | | out[#out+1] = string.format( |
| local query = [[
| | '<div class="video-card" data-video-id="%s">' .. |
| SELECT
| | '<div class="video-thumbnail" style="background-image:url(%s)"></div>' .. |
| v.video_id,
| | '<div class="video-info"><span class="video-title">%s</span></div>' .. |
| v.youtube_id,
| | '</div>', |
| v.title,
| | v.id, |
| v.channel_title,
| | mw.uri.encode( v.thumbnail_url or '', 'PATH' ), |
| v.author,
| | mw.text.nowiki( v.title or '' ) |
| 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 | | 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 | | end |
| | | out[#out+1] = '</div>' |
| stmt:finalize()
| | return table.concat( out ) |
| db:close()
| |
|
| |
| return mw.text.jsonEncode(videoData) | |
| end | | end |
|
| |
|
| -- Function to render a single video player (called via AJAX) | | ----------------------------------------------------------------- |
| function p.renderVideoPlayer(frame)
| | -- getVideoData() |
| local videoId = frame.args.video_id or frame.args[1]
| | -- Emits one big JSON blob keyed by id → row (for JS) |
|
| | ----------------------------------------------------------------- |
| if not videoId then
| | function p.getVideoData() |
| return '<div class="error">No video ID provided</div>'
| | local _, byId = collectRows() |
| end
| | return json( byId ) |
|
| |
| 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 | | end |
|
| |
|
| return p | | return p |
Documentation for this module may be created at Module:VideoGallery/doc
-- Module:VideoGallery (no LuaSQLite required)
local p = {}
local json = mw.text.jsonEncode
local ed = mw.ext.externaldata
-----------------------------------------------------------------
-- collectRows()
-- Returns: (rowsList , id→row table)
-----------------------------------------------------------------
local function collectRows()
local rows = ed.getExternalData()
local byId = {}
for _, r in ipairs( rows ) do
byId[ r.id ] = r
end
return rows, byId
end
-----------------------------------------------------------------
-- renderVideos( frame )
-- Renders the cards for one category (passed as |category=...)
-----------------------------------------------------------------
function p.renderVideos( frame )
local category = ( frame.args.category or '' ):lower()
local rows = ed.getExternalData()
local out = { '<div class="video-grid">' }
for _, v in ipairs( rows ) do
if ( v.category or '' ):lower() == category then
out[#out+1] = string.format(
'<div class="video-card" data-video-id="%s">' ..
'<div class="video-thumbnail" style="background-image:url(%s)"></div>' ..
'<div class="video-info"><span class="video-title">%s</span></div>' ..
'</div>',
v.id,
mw.uri.encode( v.thumbnail_url or '', 'PATH' ),
mw.text.nowiki( v.title or '' )
)
end
end
out[#out+1] = '</div>'
return table.concat( out )
end
-----------------------------------------------------------------
-- getVideoData()
-- Emits one big JSON blob keyed by id → row (for JS)
-----------------------------------------------------------------
function p.getVideoData()
local _, byId = collectRows()
return json( byId )
end
return p