Greasy Fork is available in English.

Youtube Timestamp Buttons from Comments

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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 );
})();