PC Gaming Shelter
An archive dedicated to preserving PC Gaming history and more

Module:Resource: Difference between revisions

From PC Gaming Shelter
No edit summary
No edit summary
 
(80 intermediate revisions by the same user not shown)
Line 2: Line 2:


local p = {}
local p = {}
local function splitBy(str, sep)
local t = {}
for part in str:gmatch("[^" .. sep .. "]+") do
table.insert(t, mw.text.trim(part))
end
return t
end
local function isValidUrl(url)
return utils.clean(url) and mw.ustring.match(url, '^https?://[^%s]+$') ~= nil
end
local function externalHost(url)
url = utils.clean(url)
if not url then
return nil
end
local host = mw.ustring.match(url, '^https?://([^/%?#]+)')
if not host then
return nil
end
local cleanedHost = mw.ustring.gsub(host, '^www%.', '')
return cleanedHost
end
local function urlDecode(value)
value = utils.clean(value)
if not value then
return nil
end
value = string.gsub(value, '+', ' ')
value = string.gsub(value, '%%(%x%x)', function(hex)
return string.char(tonumber(hex, 16))
end)
return value
end
local function queryParam(url, name)
url = utils.clean(url)
if not url then
return nil
end
local query = string.match(url, '%?([^#]+)')
if not query then
return nil
end
for key, value in string.gmatch(query, '([^&=]+)=([^&]*)') do
if urlDecode(key) == name then
return urlDecode(value)
end
end
return nil
end
local function pathMatch(url, pattern)
url = utils.clean(url)
if not url then
return nil
end
local path = string.match(url, '^https?://[^/]+([^%?#]*)') or ''
local value = string.match(path, pattern)
return urlDecode(value)
end


local function scrapeVideoId(url)
local function scrapeVideoId(url)
url = clean(url)
url = utils.clean(url)
if not isValidUrl(url) then
if not isValidUrl(url) then
return nil
return nil
Line 103: Line 175:


local function propertiesPlan()
local function propertiesPlan()
-- ['Form field name'] = { 'Semantic property', 'delimiter' }
-- ['Form field name'] = { 'Semantic property', 'delimiter' }
local propMap = {
local propMap = {
['Image']                  = {'Image', 'file'},
['Has URL']                = {'Has URL' },
['Has URL']                = {'Has URL' },
['Has short description']  = {'Has short description' },
['Has short description']  = {'Has short description' },
['Related game']          = {'Related game', ',' },
['Related game']          = {'Related game' },
['Has resource type']      = {'Has resource type' },
['Has resource type']      = {'Has resource type' },
['Has content type']      = {'Has content type', ',' },
['Has content type']      = {'Has content type', ',' },
Line 120: Line 193:
end
end


local function isValidUrl(url)
local function infoboxSectionPlan()
return clean(url) and mw.ustring.match(url, '^https?://[^%s]+$') ~= nil
local sectionMap = {
['Cover image'] = {
heading = '',
template = 'Infobox/Resource Cover',
order = '1',
rows = {
'Image',
'Related game',
'Has resource type',
'Has short description'
}
},
['Resource Meta'] = {
heading = 'Resource Info',
template = 'Infobox/Resource Info',
order = '2',
rows = {
'Has short description',
'Related game',
'Has resource type',
'Has content type',
'Has spoilers',
'Has date',
'Created by',
'Has language',
'Has subtitles',
'Has subtitles language',
}
}
}
return sectionMap
end
end


local function externalHost(url)
function p.infobox(frame)
url = clean(url)
local template    = frame:getParent().args
if not url then
local url         = template['Has URL']
return nil
local title        = mw.title.getCurrentTitle().text
end
local resourceName = title or ''


local host = mw.ustring.match(url, '^https?://([^/%?#]+)')
local scraped = url
if not host then
local service, id = '', ''
return nil
if url and template['Has resource type'] == 'Video' then
end
scraped = scrapeVideoId(url)
 
service = utils.clean(scraped.service)
return mw.ustring.gsub(host, '^www%.', '')
id = utils.clean(scraped.id)
end
 
local function embed(scraped)
if not scraped then
return
end
 
local service = clean(scraped.service)
local id = clean(scraped.id)
if not service or not id then
return
end
end
 
mw.smw.set({
mw.smw.set({
['Has video provider'] = service,
['Has video provider'] = service,
['Has video id'] = id
['Has video id']       = id,
['Has resource name']  = resourceName
})
})
local category  = 'Resources'
local properties = propertiesPlan()
local sections  = infoboxSectionPlan()
local infobox    = utils.createInfobox(frame, category, properties, sections)
return infobox
end
end


local function clean(value)
local function makeBadge(val, style)
if value == nil then
if not val and val ~= '' then
return nil
return
end
end
local style = style or 'secondary'


value = mw.text.trim(tostring(value))
local html = mw.html.create()
return value ~= '' and value or nil
html:tag('span'):addClass('mr-2 badge badge-' .. style):wikitext(val)
return tostring(html)
end
end


local function isTruthy(value)
local function pageExists(page)
value = clean(value)
if not value then
local page = mw.smw.ask {
return false
'[[' .. page .. ']][[Modification date::+]]',
end
} or {}
local check = #page > 0


value = mw.ustring.lower(value)
return check
return value == '1' or value == 'true' or value == 'yes' or value == 'y' or value == 'on'
end
end


local function splitValues(value)
function p.inline(frame)
value = clean(value)
local args        = frame:getParent().args
if not value then
local url        = args['Has URL']
return {}
local content    = args['Has content type'] or ''
local subtitles  = args['Has subtitles'] or ''
local sublang    = args['Has subtitles language'] or ''
local spoilers    = args['Has spoilers'] or ''
local restype    = args['Has resource type'] or ''
local lang        = args['Has language'] or ''
local game        = args['Related game'] or ''
    local description = args['Has short description'] or nil
   
if not isValidUrl(url) then
return
end
end


local values = {}
if content ~= '' then
for part in string.gmatch(value, '([^,]+)') do
content = splitBy(content, ',')
local item = clean(part)
for i, ct in ipairs(content) do
if item then
content[i] = makeBadge(ct)
table.insert(values, item)
end
end
end
end
 
if #values == 0 then
local subBadges = ''
table.insert(values, value)
if subtitles == "Yes" and sublang ~= '' then
end
sublang = splitBy(sublang, ',')
 
for i, st in ipairs(sublang) do
return values
sublang[i] = makeBadge(st)
end
 
local function firstValue(value)
local values = splitValues(value)
return values[1]
end
 
 
 
local function cssList(values, linked)
if not values or #values == 0 then
return nil
end
 
local items = {}
for _, value in ipairs(values) do
if linked then
table.insert(items, string.format('[[%s]]', value))
else
table.insert(items, value)
end
end
subBadges = "<span class='nowrap'>" .. makeBadge('CC', ' border border-secondary') .. table.concat(sublang, '') .. "</span>"
end
end
 
return table.concat(items, ', ')
local spoilerBadge = ''
end
if spoilers == "Yes" then
 
spoilerBadge = makeBadge('SPOILERS', 'danger')
local function addProperty(props, name, value, delimiter)
value = clean(value)
if not value then
return
end
end
 
if delimiter then
local resBadge = ''
props[name] = splitValues(value)
if restype ~= '' then
else
resBadge = makeBadge(restype)
props[name] = value
end
end
end
 
local contentBadge = ''
local function setProperties(args)
if content ~= '' and type(content) == 'table' then
local props = {}
contentBadge = table.concat(content, '')
 
addProperty(props, 'Has URL', args['Has URL'])
addProperty(props, 'Has short description', args['Has short description'])
addProperty(props, 'Related game', args['Related game'], ',')
addProperty(props, 'Has resource type', args['Has resource type'], ',')
addProperty(props, 'Has content type', args['Has content type'], ',')
addProperty(props, 'Has date', args['Has date'])
addProperty(props, 'Created by', args['Created by'])
addProperty(props, 'Has language', args['Has language'], ',')
addProperty(props, 'Has subtitles language', args['Has subtitles language'], ',')
 
if isTruthy(args['Has spoilers']) then
props['Has spoilers'] = 'Yes'
end
end
 
if isTruthy(args['Has subtitles']) then
local langBadge = ''
props['Has subtitles'] = 'Yes'
if lang ~= '' then
langBadge = makeBadge(lang, 'light')
end
end
 
if next(props) then
local gameBadge = ''
mw.smw.set(props)
if game and pageExists(game) == true then
gameBadge = string.format('[[%s|%s]]', game, makeBadge('Game', ' border border-secondary'))
end
end
end
 
local html = mw.html.create()
local function makeBadge(parent, text, badgeClass)
local resource = html:tag('div'):addClass('resource-item')
if not clean(text) then
resource:tag('div')
return
:addClass('badges d-flex mb-2 plainlinks')
:wikitext(
string.format('[%s %s]', url, makeBadge('► Open', 'primary')) ..
string.format('<span>%s%s%s%s</span>'
, resBadge
, contentBadge
, spoilerBadge
, gameBadge
) ..
string.format('<span class="ml-sm-auto">%s%s</span>'
, langBadge
, subBadges
)
)
if description then
resource:tag('div'):wikitext(description)
end
end
 
resource:tag('div')
parent
:tag('small'):addClass('text-muted'):wikitext(externalHost(url))
:tag('span')
:addClass('badge')
return html
:addClass(badgeClass or 'badge-secondary')
:addClass('mr-1 mb-1')
:wikitext(text)
end
 
local function addRow(parent, label, value)
value = clean(value)
if not value then
return
end
 
local row = parent:tag('div'):addClass('infobox-section d-flex flex-column flex-sm-row border-0 rounded-0 px-3 py-1')
row:tag('div'):addClass('infobox-label'):wikitext(label)
row:tag('div'):addClass('infobox-value'):wikitext(value)
end
 
local function renderUrl(args)
local url = clean(args['Has URL'])
if not isValidUrl(url) then
return nil
end
 
local label = externalHost(url) or url
return string.format('[%s %s]', url, label)
end
 
local function renderResource(frame, args)
local url = clean(args['Has URL'])
local title = mw.title.getCurrentTitle().text
local host = externalHost(url)
local resourceType = firstValue(args['Has resource type'])
 
local root = mw.html.create()
local card = root
:tag('div')
:addClass('infobox d-flex flex-column card shadow-none rounded-0 bg-transparent mb-4 float-sm-right')
 
local header = card
:tag('div')
:addClass('infobox-block border-bottom pb-2 mb-2')
 
local titleLine = header
:tag('div')
:addClass('d-flex flex-column flex-sm-row justify-content-between align-items-sm-start px-3 pt-3')
 
local titleBox = titleLine:tag('div')
if host then
titleBox:tag('div'):addClass('text-muted small mb-2'):wikitext(host)
end
 
local badges = titleLine:tag('div'):addClass('text-sm-right mt-2 mt-sm-0')
makeBadge(badges, resourceType, 'badge-info')
makeBadge(badges, contentType, 'badge-secondary')
 
if isTruthy(args['Has spoilers']) then
makeBadge(badges, 'Spoilers', 'badge-warning')
end
 
if isTruthy(args['Has subtitles']) then
makeBadge(badges, 'Subtitles', 'badge-success')
end
 
if isValidUrl(url) then
header
:tag('div')
:addClass('px-3 pb-3')
:tag('span')
:addClass('btn btn-success font-weight-bold')
:wikitext(renderUrl(args))
else
header
:tag('div')
:addClass('alert alert-warning mx-3 mb-3')
:wikitext('No valid URL has been provided.')
end
 
local details = card:tag('div'):addClass('infobox-block')
 
addRow(details, 'Game', cssList(splitValues(args['Related game']), true))
addRow(details, 'Media type', cssList(splitValues(args['Has resource type']), true))
addRow(details, 'Content type', cssList(splitValues(args['Has content type']), true))
addRow(details, 'Date', clean(args['Has date']))
addRow(details, 'Created by', clean(args['Created by']))
addRow(details, 'Language', cssList(splitValues(args['Has language']), true))
 
if isTruthy(args['Has subtitles']) then
addRow(details, 'Subtitles', cssList(splitValues(args['Has subtitles language']), true) or 'Yes')
end
 
return root
:wikitext('[[Category:Resources]]')
end
 
function p.main(frame)
local parent = frame:getParent()
local args = parent and parent.args or frame.args
 
setProperties(args)
return tostring(renderResource(frame, args))
end
end


return p
return p

Latest revision as of 08:59, 10 July 2026

Documentation for this module may be created at Module:Resource/doc

local utils = require( 'Module:Utils' )

local p = {}

local function splitBy(str, sep)
	local t = {}
	for part in str:gmatch("[^" .. sep .. "]+") do
		table.insert(t, mw.text.trim(part))
	end
	return t
end

local function isValidUrl(url)
	return utils.clean(url) and mw.ustring.match(url, '^https?://[^%s]+$') ~= nil
end

local function externalHost(url)
	url = utils.clean(url)
	if not url then
		return nil
	end

	local host = mw.ustring.match(url, '^https?://([^/%?#]+)')
	if not host then
		return nil
	end
	
	local cleanedHost = mw.ustring.gsub(host, '^www%.', '')
	return cleanedHost
end

local function urlDecode(value)
	value = utils.clean(value)
	if not value then
		return nil
	end

	value = string.gsub(value, '+', ' ')
	value = string.gsub(value, '%%(%x%x)', function(hex)
		return string.char(tonumber(hex, 16))
	end)

	return value
end

local function queryParam(url, name)
	url = utils.clean(url)
	if not url then
		return nil
	end

	local query = string.match(url, '%?([^#]+)')
	if not query then
		return nil
	end

	for key, value in string.gmatch(query, '([^&=]+)=([^&]*)') do
		if urlDecode(key) == name then
			return urlDecode(value)
		end
	end

	return nil
end

local function pathMatch(url, pattern)
	url = utils.clean(url)
	if not url then
		return nil
	end

	local path = string.match(url, '^https?://[^/]+([^%?#]*)') or ''
	local value = string.match(path, pattern)
	return urlDecode(value)
end

local function scrapeVideoId(url)
	url = utils.clean(url)
	if not isValidUrl(url) then
		return nil
	end

	local host = mw.ustring.lower(externalHost(url) or '')
	local id

	if host == 'archive.org' then
		id = pathMatch(url, '^/details/([^/]+)') or pathMatch(url, '^/embed/([^/]+)')
		return id and { service = 'archiveorg', id = id } or nil
	end

	if host == 'player.bilibili.com' then
		id = queryParam(url, 'bvid') or queryParam(url, 'aid')
		return id and { service = 'bilibili', id = id } or nil
	end

	if host == 'bilibili.com' or host == 'm.bilibili.com' then
		id = pathMatch(url, '^/video/([^/]+)')
		return id and { service = 'bilibili', id = id } or nil
	end

	if host == 'play-tv.kakao.com' then
		id = pathMatch(url, '^/embed/player/cliplink/([^/]+)')
		return id and { service = 'kakaotv', id = id } or nil
	end

	if host == 'tv.kakao.com' then
		id = pathMatch(url, '^/channel/[^/]+/cliplink/([^/]+)') or pathMatch(url, '^/v/([^/]+)')
		return id and { service = 'kakaotv', id = id } or nil
	end

	if host == 'tv.naver.com' then
		id = pathMatch(url, '^/embed/([^/]+)') or pathMatch(url, '^/v/([^/]+)')
		return id and { service = 'navertv', id = id } or nil
	end

	if host == 'embed.nicovideo.jp' or host == 'nicovideo.jp' or host == 'www.nicovideo.jp' then
		id = pathMatch(url, '^/watch/([^/]+)')
		return id and { service = 'niconico', id = id } or nil
	end

	if host == 'soundcloud.com' then
		return { service = 'soundcloud', id = url }
	end

	if host == 'open.spotify.com' then
		local spotifyType = pathMatch(url, '^/(album)/[^/]+') or pathMatch(url, '^/(artist)/[^/]+') or pathMatch(url, '^/(track)/[^/]+')
		id = pathMatch(url, '^/[^/]+/([^/]+)')
		if spotifyType and id then
			return { service = 'spotify' .. spotifyType, id = id }
		end
	end

	if host == 'twitch.tv' or host == 'www.twitch.tv' then
		id = pathMatch(url, '^/videos/([^/]+)')
		if id then
			return { service = 'twitchvod', id = id }
		end

		id = pathMatch(url, '^/[^/]+/clip/([^/]+)')
		if id then
			return { service = 'twitchclip', id = id }
		end

		id = pathMatch(url, '^/([^/]+)')
		return id and { service = 'twitch', id = id } or nil
	end

	if host == 'clips.twitch.tv' then
		id = pathMatch(url, '^/([^/]+)')
		return id and { service = 'twitchclip', id = id } or nil
	end

	if host == 'vimeo.com' or host == 'www.vimeo.com' or host == 'player.vimeo.com' then
		id = pathMatch(url, '^/video/([^/]+)') or pathMatch(url, '^/([^/]+)')
		return id and { service = 'vimeo', id = id } or nil
	end

	if host == 'youtube.com' or host == 'www.youtube.com' or host == 'm.youtube.com' or host == 'music.youtube.com' then
		id = queryParam(url, 'v') or pathMatch(url, '^/embed/([^/]+)') or pathMatch(url, '^/shorts/([^/]+)') or pathMatch(url, '^/live/([^/]+)')
		if id then
			return { service = 'youtube', id = id }
		end

		id = queryParam(url, 'list')
		return id and { service = 'youtubeplaylist', id = id } or nil
	end

	if host == 'youtu.be' then
		id = pathMatch(url, '^/([^/]+)')
		return id and { service = 'youtube', id = id } or nil
	end

	return nil
end

local function propertiesPlan()
-- ['Form field name'] = { 'Semantic property', 'delimiter' }
	local propMap = {
		['Image']                  = {'Image', 'file'},
		['Has URL']                = {'Has URL' },
		['Has short description']  = {'Has short description' },
		['Related game']           = {'Related game' },
		['Has resource type']      = {'Has resource type' },
		['Has content type']       = {'Has content type', ',' },
		['Has date']               = {'Has date' },
		['Created by']             = {'Created by' },
		['Has language']           = {'Has language', ',' },
		['Has spoilers']           = {'Has spoilers', },
		['Has subtitles']          = {'Has subtitles' },
		['Has subtitles language'] = {'Has subtitles language', ',' }
	}
	return propMap
end

local function infoboxSectionPlan()
	local sectionMap = {
		['Cover image'] = {
			heading = '',
			template = 'Infobox/Resource Cover',
			order = '1',
			rows = {
				'Image',
				'Related game',
				'Has resource type',
				'Has short description'
			}
		},
		['Resource Meta'] = {
			heading = 'Resource Info',
			template = 'Infobox/Resource Info',
			order = '2',
			rows = {
				'Has short description',
				'Related game',
				'Has resource type',
				'Has content type',
				'Has spoilers',
				'Has date',
				'Created by',
				'Has language',
				'Has subtitles',
				'Has subtitles language',
			}
		}
	}
	return sectionMap
end

function p.infobox(frame)
	local template     = frame:getParent().args
	local url          = template['Has URL']
	local title        = mw.title.getCurrentTitle().text
	local resourceName = title or ''

	local scraped = url
	local service, id = '', ''
	if url and template['Has resource type'] == 'Video' then
		scraped = scrapeVideoId(url)
		service = utils.clean(scraped.service)
		id = utils.clean(scraped.id)
	end
	
	mw.smw.set({
		['Has video provider'] = service,
		['Has video id']       = id,
		['Has resource name']  = resourceName
	})

	local category   = 'Resources'
	local properties = propertiesPlan()
	local sections   = infoboxSectionPlan()
	local infobox    = utils.createInfobox(frame, category, properties, sections)
	return infobox
end

local function makeBadge(val, style)
	if not val and val ~= '' then
		return
	end
	local style = style or 'secondary'

	local html = mw.html.create()
	html:tag('span'):addClass('mr-2 badge badge-' .. style):wikitext(val)
	return tostring(html)
end

local function pageExists(page)
	
	local page = mw.smw.ask {
		'[[' .. page .. ']][[Modification date::+]]',
	} or {}
	local check = #page > 0

	return check
end

function p.inline(frame)
	local args        = frame:getParent().args
	local url         = args['Has URL']
	local content     = args['Has content type'] or ''
	local subtitles   = args['Has subtitles'] or ''
	local sublang     = args['Has subtitles language'] or ''
	local spoilers    = args['Has spoilers'] or ''
	local restype     = args['Has resource type'] or ''
	local lang        = args['Has language'] or ''
	local game        = args['Related game'] or ''
    local description = args['Has short description'] or nil
    
	if not isValidUrl(url) then
		return
	end

	if content ~= '' then
		content = splitBy(content, ',')
		for i, ct in ipairs(content) do
			content[i] = makeBadge(ct)
		end
	end
	
	local subBadges = ''
	if subtitles == "Yes" and sublang ~= '' then
		sublang = splitBy(sublang, ',')
		for i, st in ipairs(sublang) do
			sublang[i] = makeBadge(st)
		end
		subBadges = "<span class='nowrap'>" .. makeBadge('CC', ' border border-secondary') .. table.concat(sublang, '') .. "</span>"
	end
	
	local spoilerBadge = ''
	if spoilers == "Yes" then
		spoilerBadge = makeBadge('SPOILERS', 'danger')
	end
	
	local resBadge = ''
	if restype ~= '' then
		resBadge = makeBadge(restype)
	end
		
	local contentBadge = ''
	if content ~= '' and type(content) == 'table' then
		contentBadge = table.concat(content, '')
	end
	
	local langBadge = ''
	if lang ~= '' then
		langBadge = makeBadge(lang, 'light')
	end
	
	local gameBadge = ''
	if game and pageExists(game) == true then
		gameBadge = string.format('[[%s|%s]]', game, makeBadge('Game', ' border border-secondary'))
	end
	
	local html = mw.html.create()
	local resource = html:tag('div'):addClass('resource-item')
	resource:tag('div')
	:addClass('badges d-flex mb-2 plainlinks')
	:wikitext(
		string.format('[%s %s]', url, makeBadge('► Open', 'primary')) ..
		string.format('<span>%s%s%s%s</span>'
			, resBadge
			, contentBadge
			, spoilerBadge
			, gameBadge
		) ..
		string.format('<span class="ml-sm-auto">%s%s</span>'
			, langBadge
			, subBadges
		)
	)
	if description then
		resource:tag('div'):wikitext(description)
	end
	resource:tag('div')
	:tag('small'):addClass('text-muted'):wikitext(externalHost(url))
	
	return html
end

return p