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
Line 289: Line 289:
resource:tag('div'):wikitext(args['Has short description'])
resource:tag('div'):wikitext(args['Has short description'])
resource:tag('div')
resource:tag('div')
:tag('small text-muted'):wikitext(externalHost(url))
:tag('small'):assClass('text-muted'):wikitext(externalHost(url))
return html
return html
end
end


return p
return p

Revision as of 00:00, 3 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

	return mw.ustring.gsub(host, '^www%.', '')
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 = {
		['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/Cover',
			order = '1',
			rows = {
				'Image'
			}
		},
		['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',	
			}
		},
		['Resource Info'] = {
			heading = 'Link',
			template = 'Infobox/Resource Link',
			order = '2',
			rows = {
				'Has URL',
				'Has video provider',
				'Has video id',
				'Has language',	
				'Has subtitles',
				'Has subtitles language',
				'Has date',
				'Created by'
			}
		}
	}
	return sectionMap
end

function p.infobox(frame)
	local url          = frame:getParent().args['Has URL']
	local title        = mw.title.getCurrentTitle().text
	local resourceName = title or ''
	
	local scraped = scrapeVideoId(url)
	local service = utils.clean(scraped.service) or ''
	local id = utils.clean(scraped.id) or ''
	
	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

function p.inline(frame)
	local args = frame:getParent().args
	local url =  args['Has URL']
	
	if not isValidUrl(url) then
		return
	end
	
	local content = splitBy(args['Has content type'], ',')
	for i, ct in ipairs(content) do
    	content[i] = makeBadge(ct)
	end
	
	local html = mw.html.create()
	local resource = html:tag('div'):addClass('resource-item')
	resource:tag('div')
		:addClass('badges mb-2 plainlinks')
		:wikitext( 
			string.format('[%s %s]', url, makeBadge('Open', 'primary')) ..
			makeBadge(args['Has resource type']) .. 
			table.concat(content, '')
		)
	resource:tag('div'):wikitext(args['Has short description'])
	resource:tag('div')
		:tag('small'):assClass('text-muted'):wikitext(externalHost(url))
	return html
end	

return p