diff --git a/javascript/commons/Countdown.js b/javascript/commons/Countdown.js
index d23c15fc3c..e7a2ff57c4 100644
--- a/javascript/commons/Countdown.js
+++ b/javascript/commons/Countdown.js
@@ -49,7 +49,8 @@ liquipedia.countdown = {
liquipedia.countdown.setupCountdownsIfSwitchToggleExists();
- // Only run when the window is actually in the front, not in background tabs (on browsers that support it)
+ // Only run when the window is actually in the front,
+ // not in background tabs (on browsers that support it)
mw.loader.using( 'mediawiki.visibleTimeout' ).then( ( require ) => {
liquipedia.countdown.timeoutFunctions = require( 'mediawiki.visibleTimeout' );
liquipedia.countdown.runCountdown();
@@ -96,7 +97,6 @@ liquipedia.countdown = {
);
},
setCountdownString: function ( timerObjectNode ) {
- const streamsarr = [];
const countdownElem = timerObjectNode.querySelector( '.timer-object-countdown' );
let datestr = '', live = 'LIVE';
@@ -143,75 +143,12 @@ liquipedia.countdown = {
} else {
datestr = ''; // DATE ERROR!
}
- if ( timerObjectNode.dataset.streamTwitch ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamTwitch2 ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamYoutube ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamAfreeca ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamAfreecatv ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamBilibili ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamBooyah ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamCc163 ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamDailymotion ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamDouyu ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamFacebook ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamHuomao ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamHuya ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamKick ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamLoco ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamMildom ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamNimo ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamTrovo ) {
- streamsarr.push( '' );
- }
- if ( timerObjectNode.dataset.streamTl ) {
- streamsarr.push( '' );
- }
let html = '' + datestr + '';
- if ( datestr.length > 0 && streamsarr.length > 0 ) {
+ if ( datestr.length > 0 && timerObjectNode.dataset.hasstreams === 'true' ) {
html += ' - ';
}
- if ( timerObjectNode.dataset.finished !== 'finished' ) {
- html += streamsarr.join( ' ' );
- }
countdownElem.innerHTML = html;
},
- getStreamName: function ( url ) {
- return url.replace( /\s/g, '_' );
- },
timeZoneAbbr: new Map( [
[ 'Acre Time', 'ACT' ],
[ 'Afghanistan Time', 'AFT' ],
diff --git a/standard/countdown.lua b/standard/countdown.lua
new file mode 100644
index 0000000000..bf4210c2f3
--- /dev/null
+++ b/standard/countdown.lua
@@ -0,0 +1,76 @@
+---
+-- @Liquipedia
+-- wiki=commons
+-- page=Module:Countdown
+--
+-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
+--
+
+local Arguments = require('Module:Arguments')
+local DateExt = require('Module:Date/Ext')
+local Logic = require('Module:Logic')
+local Lua = require('Module:Lua')
+
+local StreamLinks = Lua.import('Module:Links/Stream')
+
+local Countdown = {}
+
+---@param frame Frame
+---@return string
+function Countdown.create(frame)
+ return Countdown._create(Arguments.getArgs(frame))
+end
+
+---@param args table
+---@return string
+function Countdown._create(args)
+ if Logic.isEmpty(args.date) and not args.timestamp then
+ return ''
+ end
+
+ local wrapper = mw.html.create('span')
+ :addClass('timer-object')
+
+ if Logic.readBool(args.rawcountdown) then
+ wrapper:addClass('timer-object-countdown-only')
+ end
+ if Logic.readBool(args.rawdatetime) then
+ wrapper:addClass('timer-object-datetime-only')
+ end
+
+ -- Timestamp
+ local timestamp = args.timestamp or DateExt.readTimestampOrNil(args.date) or 'error'
+ wrapper:attr('data-timestamp', timestamp)
+
+ local streams
+ if Logic.readBool(args.finished) then
+ wrapper:attr('data-finished', 'finished')
+ elseif not Logic.readBool(args.nostreams) then
+ streams = StreamLinks.buildDisplays(StreamLinks.filterStreams(args))
+ end
+ if streams then
+ streams = table.concat(streams, ' ')
+ wrapper:attr('data-hasstreams', 'true')
+ end
+
+
+ if args.text then
+ wrapper:attr('data-countdown-end-text', args.text)
+ end
+ if args.separator then
+ wrapper:attr('data-separator', args.separator)
+ end
+
+ wrapper:wikitext(args.date)
+
+ if Logic.isEmpty(streams) then
+ return tostring(wrapper)
+ end
+
+ return tostring(mw.html.create()
+ :node(wrapper)
+ :wikitext(streams)
+ )
+end
+
+return Countdown
diff --git a/standard/links_stream.lua b/standard/links_stream.lua
index 78463a6f89..98f19a41a8 100644
--- a/standard/links_stream.lua
+++ b/standard/links_stream.lua
@@ -11,20 +11,21 @@ Module containing utility functions for streaming platforms.
]]
local StreamLinks = {}
+local Array = require('Module:Array')
local Class = require('Module:Class')
local Logic = require('Module:Logic')
+local Page = require('Module:Page')
local String = require('Module:StringUtils')
+local Table = require('Module:Table')
local Variables = require('Module:Variables')
---[[
-List of streaming platforms supported in Module:Countdown. This is a subset of
-the list in Module:Links
-]]
+local TLNET_STREAM = 'stream'
+
+--List of streaming platforms supported in Module:Countdown.
StreamLinks.countdownPlatformNames = {
'afreeca',
- 'afreecatv',
'bilibili',
- 'cc163',
+ 'cc',
'dailymotion',
'douyu',
'facebook',
@@ -34,30 +35,26 @@ StreamLinks.countdownPlatformNames = {
'loco',
'mildom',
'nimo',
- 'stream',
+ TLNET_STREAM,
'tl',
'trovo',
'twitch',
- 'twitch2',
'youtube',
}
---[[
-Lookup table of allowed inputs that use a plattform with a different name
-]]
-StreamLinks.streamPlatformLookupNames = {
- twitch2 = 'twitch',
-}
+---@param key string
+---@return boolean
+function StreamLinks.isStream(key)
+ return Array.any(StreamLinks.countdownPlatformNames, function(platform)
+ return String.startsWith(key, platform)
+ end)
+end
---Extracts the streaming platform args from an argument table for use in Module:Countdown.
---@param args {[string]: string}
---@return table
function StreamLinks.readCountdownStreams(args)
- local stream = {}
- for _, platformName in ipairs(StreamLinks.countdownPlatformNames) do
- stream[platformName] = args[platformName]
- end
- return stream
+ return Table.filterByKey(args, StreamLinks.isStream)
end
---Resolves the value of a stream given the platform
@@ -83,45 +80,130 @@ function StreamLinks.processStreams(forwardedInputArgs)
forwardedInputArgs.stream = nil
end
- for _, platformName in pairs(StreamLinks.countdownPlatformNames) do
- local streamValue = Logic.emptyOr(
- streams[platformName],
- forwardedInputArgs[platformName],
- Variables.varDefault(platformName)
- )
+ streams = Table.merge(
+ Table.filterByKey(forwardedInputArgs, StreamLinks.isStream),
+ Table.filterByKey(streams, StreamLinks.isStream)
+ )
- if String.isNotEmpty(streamValue) then
- -- stream has no platform
- if platformName ~= 'stream' then
- local lookUpPlatform = StreamLinks.streamPlatformLookupNames[platformName] or platformName
+ local processedStreams = {}
+ Array.forEach(StreamLinks.countdownPlatformNames, function(platformName)
+ Table.mergeInto(processedStreams, StreamLinks._processStreamsOfPlatform(streams, platformName))
+ end)
+
+ return processedStreams
+end
- streamValue = StreamLinks.resolve(lookUpPlatform, streamValue)
+---@param streamValues {[string]: string}
+---@param platformName string
+---@return {[string]: string}
+function StreamLinks._processStreamsOfPlatform(streamValues, platformName)
+ local platformStreams = {}
+ local legacyStreams = {}
+ local enCounter = 0
+
+ for key, streamValue in Table.iter.spairs(streamValues) do
+ if platformName ~= TLNET_STREAM then
+ streamValue = StreamLinks.resolve(platformName, streamValue)
+ end
+
+ -- legacy key
+ if key:match('^' .. platformName .. '%d*$') then
+ table.insert(legacyStreams, streamValue)
+ elseif key:match('^' .. platformName .. '_%a+_%d+') then
+ local streamKey = StreamLinks.StreamKey(key)
+ if streamKey.languageCode == 'en' then
+ enCounter = enCounter + 1
end
+ platformStreams[streamKey:toString()] = streamValue
+ end
+ end
- local key = StreamLinks.StreamKey(platformName):toString()
- streams[key] = streamValue
- streams[platformName] = streamValue -- Legacy
+ for _, streamValue in ipairs(legacyStreams) do
+ if not Table.includes(platformStreams, streamValue) then
+ enCounter = enCounter + 1
+ local streamKey = StreamLinks.StreamKey(platformName, 'en', enCounter):toString()
+ platformStreams[streamKey] = streamValue
+ platformStreams[platformName] = streamValue -- Legacy
end
end
+ if Logic.isEmpty(platformStreams) then
+ platformStreams = {[platformName] = Variables.varDefault(platformName)}
+ end
+
+ return platformStreams
+end
+
+---@param platform string
+---@param streamValue string
+---@return string?
+function StreamLinks.displaySingle(platform, streamValue)
+ local icon = ''
+ if platform == TLNET_STREAM then
+ return Page.makeExternalLink(icon, 'https://tl.net/video/streams/' .. streamValue)
+ end
+
+ local streamLink = StreamLinks.resolve(platform, streamValue)
+ if not streamLink then return nil end
+
+ return Page.makeInternalLink({}, icon, 'Special:Stream/' .. platform .. '/' .. streamValue)
+end
+
+---@param streams {string: string[]}
+---@return string[]?
+function StreamLinks.buildDisplays(streams)
+ local displays = {}
+ Array.forEach(StreamLinks.countdownPlatformNames, function(platform)
+ Array.forEach(streams[platform] or {}, function(streamValue)
+ table.insert(displays, StreamLinks.displaySingle(platform, streamValue))
+ end)
+ end)
+ return Table.isNotEmpty(displays) and displays or nil
+end
+
+---Filter non-english streams if english streams exists
+---@param streamsInput {string: string}
+---@return {string: string[]}
+function StreamLinks.filterStreams(streamsInput)
+ local hasEnglishStream = Table.any(streamsInput, function(key)
+ return key:match('_en_') or Table.includes(StreamLinks.countdownPlatformNames, key)
+ end)
+
+ local streams = {}
+ for rawHost, stream in Table.iter.spairs(streamsInput) do
+ if #(mw.text.split(rawHost, '_', true)) == 3 then
+ local streamKey = StreamLinks.StreamKey(rawHost)
+ local platform = streamKey.platform
+ if not streams[platform] then
+ streams[platform] = {}
+ end
+ table.insert(streams[platform], (not hasEnglishStream or streamKey.languageCode == 'en') and stream or nil)
+ end
+ end
+
+ Array.forEach(StreamLinks.countdownPlatformNames, function(platformName)
+ local stream = streamsInput[platformName]
+ if type(streams[platformName]) == 'table' or String.isEmpty(stream) then return end
+ streams[platformName] = {stream, streamsInput[platformName .. 2]}
+ end)
+
return streams
end
--- StreamKey Class
-- Contains the triplet that makes up a stream key
-- [platform, languageCode, index]
----@class StreamKey
+---@class StreamKey: BaseClass
---@operator call(...): StreamKey
---@field platform string
---@field languageCode string
---@field index integer
----@field is_a function
-StreamLinks.StreamKey = Class.new(
+local StreamKey = Class.new(
function (self, ...)
self:new(...)
end
)
-local StreamKey = StreamLinks.StreamKey
+StreamLinks.StreamKey = StreamKey
---@param tbl string
---@param languageCode string
@@ -146,7 +228,9 @@ function StreamKey:new(tbl, languageCode, index)
languageCode = 'en'
-- Input is a StreamKey in string format
elseif #components == 3 then
- platform, languageCode, index = unpack(components)
+ local stringIndex
+ platform, languageCode, stringIndex = unpack(components)
+ index = tonumber(stringIndex) --[[@as integer]]
end
end
@@ -158,30 +242,29 @@ function StreamKey:new(tbl, languageCode, index)
end
---@param input string
----@return string?, integer?
+---@return string, integer
function StreamKey:_fromLegacy(input)
for _, platform in pairs(StreamLinks.countdownPlatformNames) do
- -- The intersection of values in countdownPlatformNames and keys in streamPlatformLookupNames
- -- are not valid platforms. E.g. "twitch2" is not a valid platform.
- if not StreamLinks.streamPlatformLookupNames[platform] then
- -- Check if this platform matches the input
- if string.find(input, platform .. '%d-$') then
- local index = 1
- -- If the input is longer than the platform, there's an index at the end
- -- Eg. In "twitch2", the 2 would the index.
- if #input > #platform then
- index = tonumber(input:sub(#platform + 1)) --[[@as integer]]
- end
- return platform, index
+ if string.find(input, platform .. '%d-$') then
+ local index = 1
+ -- If the input is longer than the platform, there's an index at the end
+ -- Eg. In "twitch2", the 2 would be the index.
+ if #input > #platform then
+ index = tonumber(input:sub(#platform + 1)) or index
+ assert(index, '"' .. input .. '" is not a supported stream key')
end
+ return platform, index
end
end
+ error('"' .. input .. '" is not a supported stream key')
end
+---@return string
function StreamKey:toString()
return self.platform .. '_' .. self.languageCode .. '_' .. self.index
end
+---@return string
function StreamKey:toLegacy()
-- Return twitch instead of twitch1
if self.index == 1 then
@@ -190,6 +273,7 @@ function StreamKey:toLegacy()
return self.platform .. self.index
end
+---@return boolean
function StreamKey:_isValid()
assert(Logic.isNotEmpty(self.platform), 'StreamKey: Platform is required')
assert(Logic.isNotEmpty(self.languageCode), 'StreamKey: Language Code is required')
diff --git a/stylesheets/commons/Icons.less b/stylesheets/commons/Icons.less
index e058a74dd5..fc0641f872 100644
--- a/stylesheets/commons/Icons.less
+++ b/stylesheets/commons/Icons.less
@@ -118,6 +118,7 @@ Note: When adding a new icon, please add to
.icon-make-image( mixer, "//liquipedia.net/commons/images/8/85/InfoboxIcon_Mixer.png" );
.icon-make-image( music, "//liquipedia.net/commons/images/3/37/InfoboxIcon_Music.png" );
.icon-make-image( niconico, "//liquipedia.net/commons/images/b/bf/InfoboxIcon_Niconico.png" );
+ .icon-make-image( nimo, "//liquipedia.net/commons/images/f/f7/InfoboxIcon_NimoTV.png" );
.icon-make-image( nimotv, "//liquipedia.net/commons/images/f/f7/InfoboxIcon_NimoTV.png" );
.icon-make-image( nwc3l, "//liquipedia.net/commons/images/1/1c/InfoboxIcon_NWC3L.png" );
.icon-make-image( octane, "//liquipedia.net/commons/images/d/da/InfoboxIcon_Octane.png" );