Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common-votd.js: Difference between revisions

MediaWiki interface page
Joe Beaudoin Jr. (talk | contribs)
 
Joe Beaudoin Jr. (talk | contribs)
Joe Beaudoin Jr. changed the content model of the page MediaWiki:Common-votd.js from "wikitext" to "JavaScript"
 
(2 intermediate revisions by the same user not shown)
(No difference)

Latest revision as of 03:17, 13 April 2026

/* Any JavaScript here will be loaded for all users on every page load. */
/**
 * BattlestarWiki — Video of the Day loader
 * Append to MediaWiki:Common.js (after the main page JS block)
 *
 * Fetches from battlestarpegasus.com MediaCMS API.
 * Selects a video deterministically by date.
 * Supports manual override via Battlestar_Wiki:Video_of_the_Day/YYYY-MM-DD subpage.
 */

( function () {
    'use strict';

    var PEGASUS   = 'https://battlestarpegasus.com';
    var PAGE_SIZE = 50;

    function esc( s ) {
        return String( s || '' )
            .replace( /&/g, '&amp;' ).replace( /</g, '&lt;' )
            .replace( />/g, '&gt;' ).replace( /"/g, '&quot;' );
    }

    function dailySeed() {
        var now = new Date();
        return Math.floor( Date.UTC(
            now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()
        ) / 86400000 );
    }

    function fmtDuration( secs ) {
        secs = Math.round( secs || 0 );
        var m = Math.floor( secs / 60 ), s = secs % 60;
        return m + ':' + ( s < 10 ? '0' : '' ) + s;
    }

    function pegasusGet( path ) {
        return fetch( PEGASUS + path, { headers: { Accept: 'application/json' } } )
            .then( function ( r ) {
                if ( !r.ok ) throw new Error( 'HTTP ' + r.status );
                return r.json();
            } );
    }

    function fetchAllVideos() {
        return pegasusGet( '/api/v1/media/?media_type=video&ordering=add_date&page_size=' + PAGE_SIZE )
            .then( function ( data ) {
                var results = data.results || [];
                if ( data.next ) {
                    return pegasusGet( '/api/v1/media/?media_type=video&ordering=add_date&page_size=' + PAGE_SIZE + '&page=2' )
                        .then( function ( d2 ) { return results.concat( d2.results || [] ); } );
                }
                return results;
            } );
    }

    function fetchVideoDetail( token ) {
        return pegasusGet( '/api/v1/media/' + token );
    }

    function renderVotd( container, media ) {
        var token    = media.friendly_token;
        var watchUrl = media.url || ( PEGASUS + '/view?m=' + token );
        var thumb    = media.thumbnail_url ? PEGASUS + media.thumbnail_url : '';
        var poster   = media.poster_url   ? PEGASUS + media.poster_url   : thumb;

        /* ── Build video URLs from API response ── */
        var hlsSrc = '';
        var mp4Src = '';

        if ( media.hls_info && media.hls_info.master_file ) {
            hlsSrc = PEGASUS + media.hls_info.master_file;
        }
        /* Prefer 720p MP4 fallback, cascade down */
        var enc = media.encodings_info || {};
        var mp4Res = [ '720', '480', '360', '1080', '240' ];
        for ( var i = 0; i < mp4Res.length; i++ ) {
            var r = enc[ mp4Res[i] ];
            if ( r && r.h264 && r.h264.url && r.h264.status === 'success' ) {
                mp4Src = PEGASUS + r.h264.url;
                break;
            }
        }

        /* ── Player HTML: <video> with poster ── */
        var videoId  = 'bsw-votd-video';
        var playerHtml =
            '<video id="' + videoId + '" ' +
            'style="width:100%;aspect-ratio:16/9;display:block;background:#000" ' +
            'controls playsinline preload="none" ' +
            ( poster ? 'poster="' + esc( poster ) + '"' : '' ) + '>' +
            ( mp4Src ? '<source src="' + esc( mp4Src ) + '" type="video/mp4">' : '' ) +
            '</video>';

        /* ── Description sidebar ── */
        var tags = ( media.tags || [] ).slice( 0, 8 ).map( function ( t ) {
            var label = typeof t === 'string' ? t : ( t.title || t.name || '' );
            return label ? '<span class="bsw-votd-tag">' + esc( label ) + '</span>' : '';
        } ).join( '' );

        var stats = [];
        if ( media.duration ) stats.push( '\u23f1 ' + fmtDuration( media.duration ) );
        if ( media.views )    stats.push( '\ud83d\udc41 ' + media.views.toLocaleString() );
        if ( media.size )     stats.push( media.size );

        var desc = media.description || '';

        var infoHtml =
            '<div class="bsw-votd-title">' + esc( media.title || 'Untitled' ) + '</div>' +
            ( stats.length ? '<div class="bsw-votd-stats">' + stats.join( ' &nbsp;\u00b7&nbsp; ' ) + '</div>' : '' ) +
            ( desc ? '<div class="bsw-votd-desc">' + desc + '</div>' : '' ) +
            ( tags ? '<div class="bsw-votd-tags">' + tags + '</div>' : '' ) +
            '<div class="bsw-votd-actions">' +
            '<a class="bsw-votd-watch" href="' + esc( watchUrl ) + '" target="_blank" rel="noopener">Watch on Battlestar Pegasus \u2197</a>' +
            '<a class="bsw-votd-archive" href="/Battlestar_Wiki:Video_of_the_Day">Video archive</a>' +
            '</div>';

        /* Inject into DOM */
        var playerEl = container.querySelector( '.bsw-votd-player' );
        var infoEl   = document.getElementById( 'bsw-votd-info' );

        if ( playerEl ) {
            playerEl.innerHTML = playerHtml;

            /* Wire HLS.js if HLS src available and browser needs it */
            if ( hlsSrc ) {
                var videoEl = document.getElementById( videoId );
                if ( videoEl ) {
                    if ( videoEl.canPlayType( 'application/vnd.apple.mpegurl' ) ) {
                        /* Native HLS (Safari) */
                        videoEl.src = hlsSrc;
                    } else {
                        /* Load hls.js dynamically from cdnjs */
                        var script = document.createElement( 'script' );
                        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.4.12/hls.min.js';
                        script.onload = function () {
                            if ( window.Hls && Hls.isSupported() ) {
                                var hls = new Hls( { startLevel: 3 } ); /* start at 720p */
                                hls.loadSource( hlsSrc );
                                hls.attachMedia( videoEl );
                            }
                        };
                        document.head.appendChild( script );
                    }
                }
            }
        }

        if ( infoEl ) {
            infoEl.innerHTML = infoHtml;
            infoEl.style.display = '';
        }
    }

    function showVotdError( container, msg ) {
        var playerEl = container.querySelector( '.bsw-votd-player' );
        if ( playerEl ) {
            playerEl.innerHTML =
                '<div class="bsw-error" style="padding:1.5rem;text-align:center">' +
                'Could not load video. ' +
                '<a href="' + esc( PEGASUS ) + '" target="_blank" rel="noopener">Visit Battlestar Pegasus \u2197</a>' +
                '</div>';
        }
    }

    mw.hook( 'wikipage.content' ).add( function () {
        var container = document.getElementById( 'bsw-votd-container' );
        if ( !container ) return;

        var dateStr      = container.dataset.date || '';
        var overrideEl   = document.getElementById( 'bsw-votd-override' );
        var override     = overrideEl ? overrideEl.textContent.trim() : '';

        var seed = ( function () {
            if ( dateStr ) {
                var p = dateStr.split( '-' );
                return Math.floor( Date.UTC( +p[0], +p[1] - 1, +p[2] ) / 86400000 );
            }
            return dailySeed();
        }() );

        if ( override ) {
            fetchVideoDetail( override )
                .then( function ( media ) { renderVotd( container, media ); } )
                .catch( function ( e ) { showVotdError( container, e.message ); } );
        } else {
            fetchAllVideos()
                .then( function ( items ) {
                    if ( !items.length ) throw new Error( 'No videos available.' );
                    var pick = items[ seed % items.length ];
                    return fetchVideoDetail( pick.friendly_token );
                } )
                .then( function ( media ) { renderVotd( container, media ); } )
                .catch( function ( e ) { showVotdError( container, e.message ); } );
        }
    } );

}() );