Youtube Timestamp Buttons from Comments

Uses FireBase Helper Function to Call Youtube Data API 3 to get Comments with TimeStamps

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

You will need to install an extension such as Tampermonkey to install this script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Youtube Timestamp Buttons from Comments
// @namespace    http://greasyfork.org
// @version      0.6
// @description  Uses FireBase Helper Function to Call Youtube Data API 3 to get Comments with TimeStamps
// @author       636597
// @include      *://*youtube.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

var button_menu_element = false;
var load_button_element = false;
var next_button_element = false;
var previous_button_element = false;

var time_stamp_data_paragraph_parent_element = false;
var time_stamp_data_paragraph_element = false;
var time_stamp_button_elements = [];

var current_video_id = false;
var current_video_duration = false;
var current_playlist_circular_array = false;
var x1_player = undefined;

var comment_map = {};

// https://stackoverflow.com/a/18359711
function add_custom_css() {
	var custom_css = `
		.tooltip-container:hover .tooltip {
			display: inline;
		}
		.tooltip {
			display: none;  /* NEW */
			background: #C8C8C8;
			margin-left: 28px;
			padding: 10px;
			position: absolute;
			z-index: 1000;
			width:200px;
			height:100px;
		}
		.tooltip-container {
			display: inline;
		}
	`;
	var styleSheet = document.createElement("style")
	styleSheet.type = "text/css"
	styleSheet.innerText = custom_css;
	document.head.appendChild(styleSheet)
}

var window_history_oberver = false;
function load_window_history_observer() {
	CustomLog( "Initializing Window History Poller" );
	window_history_oberver = setInterval( function() {
		if ( !current_video_id ) { return; }
		var url_string = null;
		var now_playing_id = null;
		try {
			url_string = x1_player.getVideoUrl();
			now_playing_id = url_string.split( "v=" )[ 1 ].split( "&" )[ 0 ];
		}
		catch( error ) { return; }
		if ( now_playing_id !== current_video_id ) {
			CustomLog( "Detected New Video ID" );
			CustomLog( "Resetting Time Stamps" );
			current_video_id = now_playing_id;
			setTimeout( function() {
				load_comments_from_firebase_helper( { skip: "yes" , override_id: now_playing_id } );
			} , 3000 );
		}
	} , 1000 );
}

function CustomLog( message ) {
	console.log( "YoutubeTimestampButtons.js === " + message );
}

function CircularArrayIterator( list , starting_index ) {
	if ( !list ) { return false; }
	if ( list.length < 1 ) { return false; }
	const llen = list.length;
	return {
		current_index: starting_index || 0 ,
		list: list ,
		list_length: llen ,
		push: function( item , index ) {
			if ( !item ) { return; }
			if ( index ) {
				this.list.splice( index , 0 , item );
			}
			else {
				this.list.push( item );
			}
			this.list_length += 1;
		} ,
		pop: function( index ) {
			if ( index ) {
				this.list.splice( index , 1 );
			}
			else {
				this.list.pop();
			}
			this.list_length -= 1;
		} ,
		current: function() {
			return this.list[ this.current_index ];
		} ,
		next: function() {
			CustomLog( "next" );
			this.current_index += 1;
			if ( this.current_index > ( this.list_length - 1 ) ) {
				this.current_index = 0;
			}
			return this.list[ this.current_index ];
		} ,
		next_index: function() {
			CustomLog( "next index" );
			this.current_index += 1;
			if ( this.current_index > ( this.list_length - 1 ) ) {
				this.current_index = 0;
			}
			return this.current_index;
		} ,
		previous: function() {
			CustomLog( "previous" );
			this.current_index -= 1;
			if ( this.current_index < 0 ) {
				this.current_index = ( this.list_length - 1 );
			}
			return this.list[ this.current_index ];
		} ,
		previous_index: function() {
			CustomLog( "previous index" );
			this.current_index -= 1;
			if ( this.current_index < 0 ) {
				this.current_index = ( this.list_length - 1 );
			}
			return this.current_index;
		}
	};
}

function removeDuplicates(originalArray, prop) {
	var newArray = [];
	var lookupObject  = {};

	for( var i in originalArray ) {
		lookupObject[originalArray[i][prop]] = originalArray[i];
	}

	for(i in lookupObject) {
		newArray.push(lookupObject[i]);
	}
	return newArray;
}

// function playlist_next() {
// 	CustomLog( "NEXT()" );
// 	if ( !x1_player ) { return; }
// 	var current_time = x1_player.getCurrentTime();
// 	if ( !current_time ) { return; }
// 	current_time = parseInt( current_time );
// 	CustomLog( "CURRENT TIME === " + current_time.toString() );
// 	CustomLog( "CURRENT === [ " + ( current_playlist_circular_array.current_index + 1 ).toString() + " ] of " + current_playlist_circular_array.list_length.toString() + " === " + current_playlist_circular_array.list[ current_playlist_circular_array.current_index ].url_seconds.toString() );
// 	console.log( current_playlist_circular_array.list[ current_playlist_circular_array.current_index ] );

// 	var current_url_seconds = current_playlist_circular_array.list[ current_playlist_circular_array.current_index ].url_seconds;
// 	var current_item = current_playlist_circular_array.list[ current_playlist_circular_array.current_index ];
// 	while ( current_url_seconds <= current_time ) {
// 		CustomLog( "current_url_seconds <= current_time" );
// 		CustomLog( current_url_seconds.toString() + " <= " + current_time.toString() );
// 		CustomLog( "current_index === " + current_playlist_circular_array.current_index.toString() );
// 		CustomLog( "Current Item === " );
// 		console.log( current_item );
// 		CustomLog( "NEXT()" );
// 		current_playlist_circular_array.next_index();
// 		current_item = current_playlist_circular_array.list[ current_playlist_circular_array.current_index ];
// 		current_url_seconds = current_item.url_seconds;
// 		CustomLog( current_url_seconds.toString() + " <= " + current_time.toString() );
// 		CustomLog( "current_index === " + current_playlist_circular_array.current_index.toString() );
// 		CustomLog( "Current Item === " );
// 		console.log( current_item );
// 		if ( current_playlist_circular_array.current_index === 0 ) { break; }
// 	}
// 	//next = current_playlist_circular_array.next();
// 	CustomLog( "NEXT()-FINAL === [ " + ( current_playlist_circular_array.current_index + 1 ).toString() + " ] of " + current_playlist_circular_array.list_length.toString() );
// 	console.log( current_item );

// 	x1_player.loadVideoById( current_video_id , current_item.url_seconds );
// }

// function playlist_previous() {
// 	CustomLog( "PREVIOUS()" );
// 	if ( !x1_player ) { return; }
// 	var current_time = x1_player.getCurrentTime();
// 	if ( !current_time ) { return; }
// 	current_time = parseInt( current_time );
// 	CustomLog( "CURRENT TIME === " + current_time.toString() );
// 	CustomLog( "CURRENT === [ " + ( current_playlist_circular_array.current_index + 1 ).toString() + " ] of " + current_playlist_circular_array.list_length.toString() );
// 	while ( current_time > current_playlist_circular_array.list[ current_playlist_circular_array.current_index ].url_seconds ) {
// 		CustomLog( "current_time > current_index" );
// 		current_playlist_circular_array.previous_index();
// 		CustomLog( "CURRENT === [ " + ( current_playlist_circular_array.current_index + 1 ).toString() + " ] of " + current_playlist_circular_array.list_length.toString() );
// 		if ( current_playlist_circular_array.current_index === current_playlist_circular_array.list_length ) { break; }
// 	}
// 	//next = current_playlist_circular_array.next();
// 	CustomLog( "CURRENT === [ " + ( current_playlist_circular_array.current_index + 1 ).toString() + " ] of " + current_playlist_circular_array.list_length.toString() );

// 	x1_player.loadVideoById( current_video_id , current_playlist_circular_array.list[ current_playlist_circular_array.current_index ].url_seconds );
// }

function playlist_seek( seconds ) {
	CustomLog( "SEEKING TO === " + seconds.toString() );
	x1_player.loadVideoById( current_video_id , seconds );
}

function add_paragraph_with_time_stamp_data() {
	time_stamp_data_paragraph_parent_element = document.querySelector( "ytd-comments#comments" );
	if ( time_stamp_data_paragraph_parent_element ) {
		time_stamp_data_paragraph_parent_element = time_stamp_data_paragraph_parent_element.parentElement;
		var reversed = current_playlist_circular_array.list;
		reversed.reverse();
		for ( var i = 0; i < reversed.length; ++i ) {
			var id = "x1_p_ts_entry_" + i.toString();
			var p_string =`<div class="tooltip-container"><button id="${ id }">${ reversed[ i ].time_string }</button><span class="tooltip">${ comment_map[ reversed[ i ].url_seconds.toString() ] }</span></div>`;
			//CustomLog( p_string );
			var template = document.createElement( 'template' );
			template.innerHTML = p_string;
			var fragment = template.content
			//time_stamp_data_paragraph_parent_element.appendChild( fragment );
			time_stamp_data_paragraph_parent_element.insertBefore( fragment ,  time_stamp_data_paragraph_parent_element.childNodes[ 3 ] );
			var b_elem = document.body.querySelector( "#" + id );
			b_elem.setAttribute( "url_seconds" , current_playlist_circular_array.list[ i ].url_seconds );
			//b_elem.setAttribute( "playlist_index" , reversed[ i ].index );
			b_elem.addEventListener( "click" , function( event ) {
				//current_playlist_circular_array.current_index = parseInt( this.getAttribute( "playlist_index" ) );
				playlist_seek( parseInt( this.getAttribute( "url_seconds" ) ) );
			});
			time_stamp_button_elements.push( b_elem );
		}
	}
	load_button_element.style.visibility = "visible";
	if ( !window_history_oberver ) { load_window_history_observer(); }
}

function add_previous_next_buttons() {
	if ( button_menu_element ) {
		button_menu_element.insertAdjacentHTML( 'beforeend' , '<button id="x1_playlist_previous">Previous</button>' );
		previous_button_element = document.body.querySelector( "#x1_playlist_previous" );
		previous_button_element.addEventListener( "click" , playlist_previous );
		button_menu_element.insertAdjacentHTML( 'beforeend' , '<button id="x1_playlist_next">Next</button>' );
		next_button_element = document.body.querySelector( "#x1_playlist_next" );
		next_button_element.addEventListener( "click" , playlist_next );
	}
}

function load_keyboard_watchers() {
	add_previous_next_buttons();
	add_paragraph_with_time_stamp_data();
	// TODO: Need To figure out Javscript Circular List, COGGERS FailFish
	/*
	document.body.addEventListener( "keydown" , function( event ) {
		if ( event.key === "n" ) {
			playlist_next();
		}
		else if ( event.key === "b" ) {
			playlist_previous();
		}
	});
	*/
}

function get_video_stats() {
  var url_string = x1_player.getVideoUrl();
  current_video_id = url_string.split( "v=" )[ 1 ].split( "&" )[ 0 ];
  current_video_duration = x1_player.getDuration();
  /*
  current_playlist_id = url_string.split( "list=" );
  if ( current_playlist_id ) {
	if ( current_playlist_id.length > 1 ) {
		current_playlist_id = current_playlist_id[ 1 ].split( "&" )[ 0 ];
		current_playlist_index = x1_player.getPlaylistIndex();
		CustomLog( current_playlist_id );
		CustomLog( current_playlist_index );
	}
  }
  */
  CustomLog( current_video_id );
}

function parse_firebase_helper( comments ) {
	CustomLog( "Parsing Recieved Comment Data" );
	var final = [];
	for ( var i = 0; i < comments.length; ++i ) {
		if ( comments[ i ].time_stamp_parts ) {
			if ( comments[ i ].time_stamp_parts.length > 0 ) {
				var text = comments[ i ].text;
				for ( var j = 0; j < comments[ i ].time_stamp_parts.length; ++j ) {
					if ( comments[ i ].time_stamp_parts[ j ].url_seconds ) {
						var url_seconds_string = comments[ i ].time_stamp_parts[ j ].url_seconds.toString();
						if ( !comment_map[ url_seconds_string ] ) {
							comment_map[ url_seconds_string ] = text;
						}
					}
				}
				final.push( ...comments[ i ].time_stamp_parts );
			}
		}
	}
	console.log( comment_map );
	var uniqueArray = removeDuplicates( final , "url_seconds" );
	uniqueArray = uniqueArray.sort( ( a , b ) => {
		b.url_seconds - a.url_seconds
	});
	uniqueArray = uniqueArray.filter( x => x.url_seconds !== null );
	uniqueArray = uniqueArray.filter( x => x.url_seconds !== "null" );
	uniqueArray = uniqueArray.filter( x => x.url_seconds < current_video_duration );
	current_playlist_circular_array = false;
	current_playlist_circular_array = CircularArrayIterator( uniqueArray );
	add_custom_css();
	add_paragraph_with_time_stamp_data();
	console.log( uniqueArray );
}

var comment_count = "50";
var fburlb = atob( 'aHR0cHM6Ly91cy1jZW50cmFsMS1oZWxwZXItZTNkODYuY2xvdWRmdW5jdGlvbnMubmV0L3RpbWVzdGFtcHM/aWQ9' );
function load_comments_from_firebase_helper( options ) {
	reset_simple();
	var rebuild = true;
	if ( options ) {
		if ( options.skip ) {
			if ( options.skip === "yes" ) {
				current_video_id = options.override_id;
			}
		}
	}
	x1_player = document.getElementById( "movie_player" );
	if ( !x1_player ) { return; }
	get_video_stats();
	if ( !current_video_id ) { return; }
	load_button_element.style.visibility = "hidden";
	CustomLog( "Fetching Comments from FireBase Helper" );
	// fetch( 'https://us-central1-helper-e3d86.cloudfunctions.net/timestamps?id=' + current_video_id , {
	// 	headers: new Headers({
	// 		'Access-Control-Allow-Origin': '*' ,
	// 		'Access-Control-Allow-Headers':'application/json',
	// 		'Access-Control-Allow-Headers': 'Content-Type, Authorization'
	// 	})
	// })
	fetch( fburlb + current_video_id + "&count=" + comment_count )
	.then( response => response.json() )
	.then( data => {
		console.log( data );
		parse_firebase_helper( data.top_time_stamp_comments );
	})
	.catch(error => console.error(error))
}

function add_load_button() {
	button_menu_element = document.body.querySelector( "ytd-menu-renderer" );
	if ( button_menu_element ) {
		button_menu_element.insertAdjacentHTML( 'beforeend' , '<button id="x1_load_ts">Load Timestamps</button>' );
		load_button_element = document.body.querySelector( "#x1_load_ts" );
		load_button_element.addEventListener( "click" , load_comments_from_firebase_helper );
	}
}

function reset_simple() {
	try { next_button_element.parentNode.removeChild( next_button_element ); } catch( e ){}
	next_button_element = false;
	try { previous_button_element.parentNode.removeChild( previous_button_element ); } catch( e ){}
	previous_button_element = false;
	for ( var i = 0; i < time_stamp_button_elements.length; ++i ) {
		try { time_stamp_button_elements[ i ].parentNode.removeChild( time_stamp_button_elements[ i ] ); } catch( e ){}
	}
	//time_stamp_data_paragraph_parent_element = false;
	//time_stamp_data_paragraph_element = false;
	time_stamp_button_elements = [];
	current_video_id = false;
	current_video_duration = false;
	// current_playlist_id = false;
	// current_playlist_index = 0;
	// current_index = 0;
	// current_playlist = [];
	current_playlist_circular_array = undefined;
	x1_player = undefined;
	comment_map = false;
	comment_map = {};
}

function reset_all() {
	button_menu_element = false;
	try { load_button_element.parentNode.removeChild( load_button_element ); } catch( e ){}
	load_button_element = false;
	try { next_button_element.parentNode.removeChild( next_button_element ); } catch( e ){}
	next_button_element = false;
	try { previous_button_element.parentNode.removeChild( previous_button_element ); } catch( e ){}
	previous_button_element = false;
	try { time_stamp_data_paragraph_parent_element.parentNode.removeChild( time_stamp_data_paragraph_parent_element ); } catch( e ){}
	time_stamp_data_paragraph_parent_element = false;
	time_stamp_data_paragraph_element = false;
	time_stamp_button_elements = [];
	current_video_id = false;
	current_video_duration = false;
	//current_playlist_id = false;
	//current_playlist_index = 0;
	//current_index = 0;
	//current_playlist = [];
	current_playlist_circular_array = undefined;
	x1_player = undefined;
	comment_map = false;
	comment_map = {};
}

function init() {
	reset_all();
	CustomLog( "LOADING" );
	setTimeout(function(){
		add_load_button();
		//CustomLog( "LOADED" );
	} , 3000 );
}

(function() {
	window.addEventListener ( "load", init );
})();