Skip to content

Instantly share code, notes, and snippets.

@ollybritton
Created October 21, 2020 21:31
Show Gist options
  • Save ollybritton/3826013e3738fd69e05087fe223d3928 to your computer and use it in GitHub Desktop.
Save ollybritton/3826013e3738fd69e05087fe223d3928 to your computer and use it in GitHub Desktop.
var injectContentScript = function () {
var verbose = false;
// var defaultServerOptions = [];
// for (var i = 110; i <= 169; i++) { defaultServerOptions.push('s' + i)}
var version = null;
var defaultServerOptions = [
's110', 's111', 's112', 's113', 's114', 's115', 's116', 's117', 's118', 's119',
's120', 's121', 's122', 's123', 's125', 's126', 's127', 's128', 's129',
's130', 's131', 's132', 's133', 's134', 's135', 's136', 's137', 's138', 's139',
's140', 's141', 's142', 's143', 's144', 's145', 's146', 's147', 's148', 's149',
's150', 's151', 's152', 's153', 's154', 's155', 's156', 's157', 's158', 's159',
's160', 's161', 's162', 's163', 's164', 's165', 's166', 's167', 's168', 's169'
];
// hardcoded server config for auto scaling in case cloud config isn't available
var serversConfig = {
"load":true, "undefined":true, "wpseth":true, "sundefined":true, "s1":true, "s2":true, "s3":true, "s4":true, "s5":true, "s6":true, "s7":true, "s8":true, "s9":true,
"s10":true, "s11":true, "s12":true, "s13":true, "s14":true, "s15":true, "s16":true, "s17":true, "s18":true, "s19":true,
"s20":true, "s21":true, "s22":true, "s23":true, "s24":true, "s25":true, "s26":true, "s27":true, "s28":true, "s29":true,
"s30":true, "s31":true, "s32":true, "s33":true, "s34":true, "s35":true, "s36":true, "s37":true, "s38":true, "s39":true,
"s40":true, "s41":true, "s42":true, "s43":true, "s44":true, "s45":true, "s46":true, "s47":true, "s48":true, "s49":true,
"s50":true, "s51":true, "s52":true, "s53":true, "s54":true, "s55":true, "s56":true, "s57":true, "s58":true, "s59":true,
"s60":true, "s61":true, "s62":true, "s63":true, "s64":true, "s65":true, "s66":true, "s67":true, "s68":true, "s69":true,
"s70":true, "s71":true, "s72":true, "s73":true, "s74":true, "s75":true, "s76":true, "s77":true, "s78":true, "s79":true,
"s80":true, "s81":true, "s82":true, "s83":true, "s84":true, "s85":true, "s86":true, "s87":true, "s88":true, "s89":true,
"s90":true, "s91":true, "s92":true, "s93":true, "s94":true, "s95":true, "s96":true, "s97":true, "s98":true, "s99":true,
"s100":true, "s101":true, "s102":true, "s103":true, "s104":true, "s105":true, "s106":true, "s107":true, "s108":true, "s109":true,
"s110":true, "s111":true, "s112":true, "s113":true, "s114":true, "s115":true, "s116":true, "s117":true, "s118":true, "s119":true,
"s120":true, "s121":true, "s122":true, "s123":true, "s124":true, "s125":true, "s126":true, "s127":true, "s128":true, "s129":true,
"s130":true, "s131":true, "s132":true, "s133":true, "s134":true, "s135":true, "s136":true, "s137":true, "s138":true, "s139":true,
"s140":true, "s141":true, "s142":true, "s143":true, "s144":true, "s145":true, "s146":true, "s147":true, "s148":true, "s149":true,
"s150":true, "s151":true, "s152":true, "s153":true, "s154":true, "s155":true, "s156":true, "s157":true, "s158":true, "s159":true,
"s160":true, "s161":true, "s162":true, "s163":true, "s164":true, "s165":true, "s166":true, "s167":true, "s168":true, "s169":true,
"s170":true, "s171":true, "s172":true, "s173":true, "s174":true, "s175":true, "s176":true, "s177":true, "s178":true, "s179":true,
"s180":true, "s181":true, "s182":true, "s183":true, "s184":true, "s185":true, "s186":true, "s187":true, "s188":true, "s189":true,
"s190":true, "s191":true, "s192":true, "s193":true, "s194":true, "s195":true, "s196":true, "s197":true, "s198":true, "s199":true,
"s200":true, "s201":true, "s202":true, "s203":true, "s204":true, "s205":true, "s206":true, "s207":true, "s208":true, "s209":true,
"s210":true, "s211":true, "s212":true, "s213":true, "s214":true, "s215":true, "s216":true, "s217":true, "s218":true, "s219":true,
"s220":true, "s221":true, "s222":true, "s223":true, "s224":true, "s225":true, "s226":true, "s227":true, "s228":true, "s229":true,
"s230":true, "s231":true, "s232":true, "s233":true, "s234":true, "s235":true, "s236":true, "s237":true, "s238":true, "s239":true,
"s240":true, "s241":true, "s242":true, "s243":true, "s244":true, "s245":true, "s246":true, "s247":true, "s248":true, "s249":true,
"s250":true, "s251":true, "s252":true, "s253":true, "s254":true, "s255":true, "s256":true, "s257":true, "s258":true, "s259":true,
"s260":true, "s261":true, "s262":true, "s263":true, "s264":true, "s265":true, "s266":true, "s267":true, "s268":true, "s269":true,
"s270":true, "s271":true, "s272":true, "s273":true, "s274":true, "s275":true, "s276":true, "s277":true, "s278":true, "s279":true,
"s280":true, "s281":true, "s282":true, "s283":true, "s284":true, "s285":true, "s286":true, "s287":true, "s288":true, "s289":true,
"s290":true, "s291":true, "s292":true, "s293":true, "s294":true, "s295":true, "s296":true, "s297":true, "s298":true, "s299":true,
"s300":true, "s301":true, "s302":true, "s303":true, "s304":true, "s305":true, "s306":true, "s307":true, "s308":true, "s309":true,
"s310":true, "s311":true, "s312":true, "s313":true, "s314":true, "s315":true, "s316":true, "s317":true, "s318":true, "s319":true,
"s320":true, "s321":true, "s322":true, "s323":true, "s324":true, "s325":true, "s326":true, "s327":true, "s328":true, "s329":true,
"s330":true, "s331":true, "s332":true, "s333":true, "s334":true, "s335":true, "s336":true, "s337":true, "s338":true, "s339":true,
"s340":true, "s341":true, "s342":true, "s343":true, "s344":true, "s345":true, "s346":true, "s347":true, "s348":true, "s349":true,
"s350":true, "s351":true, "s352":true, "s353":true, "s354":true, "s355":true, "s356":true, "s357":true, "s358":true, "s359":true,
"s360":true, "s361":true, "s362":true, "s363":true, "s364":true, "s365":true, "s366":true, "s367":true, "s368":true, "s369":true,
"s370":true, "s371":true, "s372":true, "s373":true, "s374":true, "s375":true, "s376":true, "s377":true, "s378":true, "s379":true,
"s380":true, "s381":true, "s382":true, "s383":true, "s384":true, "s385":true, "s386":true, "s387":true, "s388":true, "s389":true,
"s390":true, "s391":true, "s392":true, "s393":true, "s394":true, "s395":true, "s396":true, "s397":true, "s398":true, "s399":true,
"s400":true, "s401":true, "s402":true, "s403":true, "s404":true, "s405":true, "s406":true, "s407":true, "s408":true, "s409":true,
"s410":true, "s411":true, "s412":true, "s413":true, "s414":true, "s415":true, "s416":true, "s417":true, "s418":true, "s419":true,
"s420":true, "s421":true, "s422":true, "s423":true, "s424":true, "s425":true, "s426":true, "s427":true, "s428":true, "s429":true,
"s430":true, "s431":true, "s432":true, "s433":true, "s434":true, "s435":true, "s436":true, "s437":true, "s438":true, "s439":true,
"s440":true, "s441":true, "s442":true, "s443":true, "s444":true, "s445":true, "s446":true, "s447":true, "s448":true, "s449":true,
"s450":true, "s451":true, "s452":true, "s453":true, "s454":true, "s455":true, "s456":true, "s457":true, "s458":true, "s459":true,
"s460":true, "s461":true, "s462":true, "s463":true, "s464":true, "s465":true, "s466":true, "s467":true, "s468":true, "s469":true,
"s470":true, "s471":true, "s472":true, "s473":true, "s474":true, "s475":true, "s476":true, "s477":true, "s478":true, "s479":true,
"s480":true, "s481":true, "s482":true, "s483":true, "s484":true, "s485":true, "s486":true, "s487":true, "s488":true, "s489":true,
"s490":true, "s491":true, "s492":true, "s493":true, "s494":true, "s495":true, "s496":true, "s497":true, "s498":true, "s499":true,
"s500":true, "s501":true, "s502":true, "s503":true, "s504":true, "s505":true, "s506":true, "s507":true, "s508":true, "s509":true,
"s510":true, "s511":true, "s512":true, "s513":true, "s514":true, "s515":true, "s516":true, "s517":true, "s518":true, "s519":true,
"s520":true, "s521":true, "s522":true, "s523":true, "s524":true, "s525":true, "s526":true, "s527":true, "s528":true, "s529":true,
"s530":true, "s531":true, "s532":true, "s533":true, "s534":true, "s535":true, "s536":true, "s537":true, "s538":true, "s539":true,
"s540":true, "s541":true, "s542":true, "s543":true, "s544":true, "s545":true, "s546":true, "s547":true, "s548":true, "s549":true,
"s550":true, "s551":true, "s552":true, "s553":true, "s554":true, "s555":true, "s556":true, "s557":true, "s558":true, "s559":true,
"s560":true, "s561":true, "s562":true, "s563":true, "s564":true, "s565":true, "s566":true, "s567":true, "s568":true, "s569":true,
"s570":true, "s571":true, "s572":true, "s573":true, "s574":true, "s575":true, "s576":true, "s577":true, "s578":true, "s579":true,
"s580":true, "s581":true, "s582":true, "s583":true, "s584":true, "s585":true, "s586":true, "s587":true, "s588":true, "s589":true,
"s590":true, "s591":true, "s592":true, "s593":true, "s594":true, "s595":true, "s596":true, "s597":true, "s598":true, "s599":true,
"s600":true, "s601":true, "s602":true, "s603":true, "s604":true, "s605":true, "s606":true, "s607":true, "s608":true, "s609":true,
"s610":true, "s611":true, "s612":true, "s613":true, "s614":true, "s615":true, "s616":true, "s617":true, "s618":true, "s619":true,
"s620":true, "s621":true, "s622":true, "s623":true, "s624":true, "s625":true, "s626":true, "s627":true, "s628":true, "s629":true,
"s630":true, "s631":true, "s632":true, "s633":true, "s634":true, "s635":true, "s636":true, "s637":true, "s638":true, "s639":true,
"s640":true, "s641":true, "s642":true, "s643":true, "s644":true, "s645":true, "s646":true, "s647":true, "s648":true, "s649":true,
"s650":true, "s651":true, "s652":true, "s653":true, "s654":true, "s655":true, "s656":true, "s657":true, "s658":true, "s659":true,
"s660":true, "s661":true, "s662":true, "s663":true, "s664":true, "s665":true, "s666":true, "s667":true, "s668":true, "s669":true,
"s670":true, "s671":true, "s672":true, "s673":true, "s674":true, "s675":true, "s676":true, "s677":true, "s678":true, "s679":true,
"s680":true, "s681":true, "s682":true, "s683":true, "s684":true, "s685":true, "s686":true, "s687":true, "s688":true, "s689":true,
"s690":true, "s691":true, "s692":true, "s693":true, "s694":true, "s695":true, "s696":true, "s697":true, "s698":true, "s699":true,
"s700":true, "s701":true, "s702":true, "s703":true, "s704":true, "s705":true, "s706":true, "s707":true, "s708":true, "s709":true,
"s710":true, "s711":true, "s712":true, "s713":true, "s714":true, "s715":true, "s716":true, "s717":true, "s718":true, "s719":true,
"s720":true, "s721":true, "s722":true, "s723":true, "s724":true, "s725":true, "s726":true, "s727":true, "s728":true, "s729":true,
"s730":true, "s731":true, "s732":true, "s733":true, "s734":true, "s735":true, "s736":true, "s737":true, "s738":true, "s739":true,
"s740":true, "s741":true, "s742":true, "s743":true, "s744":true, "s745":true, "s746":true, "s747":true, "s748":true, "s749":true,
"s750":true, "s751":true, "s752":true, "s753":true, "s754":true, "s755":true, "s756":true, "s757":true, "s758":true, "s759":true,
"s760":true, "s761":true, "s762":true, "s763":true, "s764":true, "s765":true, "s766":true, "s767":true, "s768":true, "s769":true,
"s770":true, "s771":true, "s772":true, "s773":true, "s774":true, "s775":true, "s776":true, "s777":true, "s778":true, "s779":true,
"s780":true, "s781":true, "s782":true, "s783":true, "s784":true, "s785":true, "s786":true, "s787":true, "s788":true, "s789":true,
"s790":true, "s791":true, "s792":true, "s793":true, "s794":true, "s795":true, "s796":true, "s797":true, "s798":true, "s799":true
};
// log interaction events with permanent userId
var permId;
chrome.storage.local.get(['userId'], function(data) {
if(data.userId) {
permId = data.userId;
}
if(permIdFix) {
permId = userId;
}
});
//////////////////////////////////////////////////////////////////////////
// Custom Alert //
//////////////////////////////////////////////////////////////////////////
const closeImage = chrome.extension.getURL('img/x-circle.svg');
const redirectUrl = 'https://www.tele.pe';
var getCustomMessageWithButton = function(options) {
return `
<div id="alert-dialog-wrapper">
<div id="alert-dialog-container">
<div id="alert-title-wrapper">
<div class="alert-title">
<p id="alert-title-txt" class="extension-title">
${options.title}
</p>
<button id="alert-x-btn">
<img src="${closeImage}" alt="close" />
</button>
</div>
<div class="extension-border-bot">
</div>
</div>
<div id="alert-description">
<p id="alert-content-txt" class="extension-txt">
${options.content}
</p>
<button id="alert-return-btn" class="extension-btn">${options.buttonTitle}</button>
</div>
</div>
</div>
`;
}
var ownerOnlyNextEpisodeModal = {
title: 'Teleparty | Disconnected from party',
content: `Only the owner of this party can change the episode. Click the button below to be redirected to the party, then click on the red Tp icon to rejoin.`,
buttonTitle: `Return to Party`
}
var testParticipationModal = {
title: 'Teleparty | Test Participation',
content: 'You are using an experimental Netflix video player which Teleparty does not support.\n\nPlease click the button below, disable your test participation and return to the party to continue using Teleparty.',
buttonTitle: `Disable test participation`
}
var longPartyModal = {
title: 'Teleparty | Disconnected from party',
content: `Long parties only work for consecutive episodes for now. Please share a new Teleparty to continue watching together.`,
buttonTitle: `Return to Party`
}
var longPartyRedirectModal = {
title: 'Teleparty | Disconnected from party',
content: `Looks like someone changed the video. Long parties only work for consecutive episodes for now. Please share a new Teleparty to continue watching together.`,
buttonTitle: `Go to new video`
}
var failedNextEpisodeModal = {
title: 'Teleparty | Disconnected from party',
content: `It looks like someone changed the video and we weren't able to connect you. Click the button below to be redirected to the party, then click on the red Tp icon to rejoin.`,
buttonTitle: `Return to Party`
}
var lostConnectionModal = {
title: 'Teleparty | Disconnected from party',
content: `It looks like you lost connection to the server. Click the button below to be redirected to the party, then click on the red Tp icon to rejoin.`,
buttonTitle: `Return to Party`
}
var showButtonMessage = function(options, redirectUrl) {
const modalTemplate = getCustomMessageWithButton(options);
document.body.insertAdjacentHTML("afterbegin", modalTemplate);
jQuery('#alert-x-btn').click(() => {
document.querySelector('#alert-dialog-wrapper').remove();
})
jQuery('#alert-return-btn').click(() => {
document.querySelector('#alert-dialog-wrapper').remove();
window.location.href = redirectUrl;
})
}
var showMessage = function(options) {
const modalTemplate = getCustomMessage(options);
document.body.insertAdjacentHTML("afterbegin", modalTemplate);
jQuery('#alert-close').click(() => {
document.querySelector('#alert-dialog-wrapper').remove();
})
}
//////////////////////////////////////////////////////////////////////////
// Helpers //
//////////////////////////////////////////////////////////////////////////
var logMessage = function(message, logTime = false) {
if (verbose && logTime) {
const time = new Date();
const timeStatus = time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + ":" + time.getMilliseconds();
console.log(`${timeStatus} : ${message}`);
}else if(verbose) {
console.log(message);
}
}
// returns an action which delays for some time
var delay = function(milliseconds) {
return function(result) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(result);
}, milliseconds);
});
};
};
// returns an action which waits until the condition thunk returns true,
// rejecting if maxDelay time is exceeded
var delayUntil = function(condition, maxDelay) {
return function(result) {
var delayStep = 250;
var startTime = (new Date()).getTime();
var checkForCondition = function() {
if (condition()) {
return Promise.resolve(result);
}
if (maxDelay !== null && (new Date()).getTime() - startTime > maxDelay) {
return Promise.reject(Error('delayUntil timed out'));
}
return delay(delayStep)().then(checkForCondition);
};
return checkForCondition();
};
};
// add value to the end of array, and remove items from the beginning
// such that the length does not exceed limit
var shove = function(array, value, limit) {
array.push(value);
if (array.length > limit) {
array.splice(0, array.length - limit);
}
};
// compute the mean of an array of numbers
var mean = function(array) {
return array.reduce(function(a, b) { return a + b; }) / array.length;
};
// compute the median of an array of numbers
var median = function(array) {
return array.concat().sort()[Math.floor(array.length / 2)];
};
// swallow any errors from an action
// and log them to the console
// returns a function that takes in a previous promise result arg that is passed down to swallowed action
var swallow = function(action) {
return function(result) {
return action(result).catch(function(e) {
console.error(e);
});
};
};
var escapeStr = function(str) {
return str.replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
// promise.ensure(fn) method
// note that this method will not swallow errors
Promise.prototype.ensure = function(fn) {
return this.then(fn, function(e) {
fn();
throw e;
});
};
// inject a script onto the Netflix window DOM outside of CRX sandbox
// with full access to window context
var injectScript = function(script) {
var s = document.createElement('script');
s.textContent = script;
(document.head||document.documentElement).appendChild(s);
s.remove();
}
var getPartyUrl = function() {
return redirectUrl + '/netflix/' + encodeURIComponent(sessionId) + '?s=' + encodeURIComponent(serverId);
}
//////////////////////////////////////////////////////////////////////////
// Netflix API //
//////////////////////////////////////////////////////////////////////////
// how many simulated UI events are currently going on
// don't respond to UI events unless this is 0, otherwise
// we will mistake simulated actions for real ones
var uiEventsHappening = 0;
// video duration in milliseconds
var lastDuration = 60 * 60 * 1000;
var getDuration = function() {
var video = jQuery("video");
if (video.length > 0) {
lastDuration = Math.floor((video[0].duration) * 1000);
}
return lastDuration;
};
// 'playing', 'paused', 'loading', or 'idle'
// jQuery(".legacy-controls-styles.legacy.inactive")
// jQuery(".legacy-controls-styles.legacy.dimmed")
// jQuery(".legacy-controls-styles.legacy.active")
// jQuery(".nf-big-play-pause.play").length
// jQuery(".nf-big-play-pause.pause").length
// jQuery(".player-loading").length
// jQuery(".nf-big-play-pause").length
// jQuery(".nf-big-play-pause-secondary-play").length
// document.querySelector(".nf-big-play-pause").click()
// document.querySelector(".ltr-fntwn3").className.includes("passive")
// player-loading
var getState = function() {
if (jQuery(".legacy-controls-styles.legacy.dimmed").length > 0) {
return 'idle';
}
if (jQuery(".AkiraPlayerSpinner--container").length > 0) {
return 'loading';
}
if (jQuery('.button-nfplayerPause').length > 0) {
return 'playing';
} else {
return 'paused';
}
};
// current playback position in milliseconds
var getPlaybackPosition = function() {
if(jQuery("video")[0]) return Math.floor(jQuery("video")[0].currentTime * 1000);
return null;
};
// current playback position in milliseconds
var getRemainingTime = function() {
if(jQuery("video")[0]) return Math.floor( (jQuery("video")[0].duration - jQuery("video")[0].currentTime) * 1000);
return null;
};
// current playback position in milliseconds
var getRemainingTimeText = function() {
if(jQuery(".time-remaining__time")[0]) {
var remainingTimeText = jQuery('.time-remaining__time')[0].textContent;
if(remainingTimeText !== '0:00') {
return remainingTimeText;
}
}
return null;
};
// wake up from idle mode
var wakeUp = function() {
uiEventsHappening += 1;
var idleDisplay = jQuery(".legacy-controls-styles.legacy.dimmed")
var eventOptions = {
'bubbles': true,
'button': 0,
'currentTarget': idleDisplay[0]
};
idleDisplay[0].dispatchEvent(new MouseEvent('mouseover', eventOptions));
return delayUntil(function() {
return getState() !== 'idle';
}, 2500)().ensure(function() {
uiEventsHappening -= 1;
});
};
// show the playback controls (hides controls after 2.5 secs)
var showControls = function() {
uiEventsHappening += 1;
var uiError = false;
var scrubber;
try {
var sessionIdString = sessionId ? sessionId : 'null';
scrubber = jQuery('.text-control');
if (scrubber) {
var eventOptions = {
'bubbles': true,
'button': 0,
'currentTarget': scrubber[0]
};
scrubber[0].dispatchEvent(new MouseEvent('mousemove', eventOptions));
}else {
throw new Error();
}
} catch(e) {
console.log('controls not available');
uiEventsHappening -= 1;
uiError = true;
}
return delayUntil(function() {
return scrubber && scrubber.is(':visible');
}, 1000)().ensure(function() {
if(!uiError) uiEventsHappening -= 1;
});
};
// hide the playback controls
var hideControls = function() {
uiEventsHappening += 1;
var player = jQuery('.VideoContainer');
var mouseX = 100; // relative to the document
var mouseY = 100; // relative to the document
var eventOptions = {
'bubbles': true,
'button': 0,
'screenX': mouseX - jQuery(window).scrollLeft(),
'screenY': mouseY - jQuery(window).scrollTop(),
'clientX': mouseX - jQuery(window).scrollLeft(),
'clientY': mouseY - jQuery(window).scrollTop(),
'offsetX': mouseX - player.offset().left,
'offsetY': mouseY - player.offset().top,
'pageX': mouseX,
'pageY': mouseY,
'currentTarget': player[0]
};
player[0].dispatchEvent(new MouseEvent('mousemove', eventOptions));
return delay(1)().ensure(function() {
uiEventsHappening -= 1;
});
};
// pause (hide controls after using)
var pause = function() {
uiEventsHappening += 1;
jQuery('.button-nfplayerPause').click();
return delayUntil(function() {
return getState() === 'paused';
}, 1000)().then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
// play
var play = function() {
uiEventsHappening += 1;
jQuery('.button-nfplayerPlay').click();
return delayUntil(function() {
return getState() === 'playing';
}, 2500)().then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
// freeze playback for some time and then play
var freeze = function(milliseconds) {
return function() {
uiEventsHappening += 1;
jQuery('.button-nfplayerPause').click();
return delay(milliseconds)().then(function() {
jQuery('.button-nfplayerPlay').click();
}).then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
};
// freeze playback until the condition thunk returns true and then play
// rejecting if maxDelay time is exceeded
// TODO: take a look at error handling
var freezeUntil = function(condition, maxDelay) {
return function() {
uiEventsHappening += 1;
jQuery('.button-nfplayerPause').click();
return delayUntil(condition, maxDelay)().then(function() {
jQuery('.button-nfplayerPlay').click();
}).then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
};
// whether others are buffering
var othersAreBuffering = false;
// wait until othersAreBuffering is false;
var waitForBuffering = function() {
console.log('called wait function');
return function() {
uiEventsHappening += 1;
jQuery('.button-nfplayerPause').click();
return delayUntil(function() {
return !othersAreBuffering;
}, 5000)().then(function() {
jQuery('.button-nfplayerPlay').click();
}).then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
};
// jump to a specific time in the video
var seekErrorRecent = [];
var seekErrorMean = 0;
const seekScript = `window.seekScriptLoaded=!0;var getVideoPlayer=function(){var e=window.netflix.appContext.state.playerApp.getAPI().videoPlayer,t=e.getAllPlayerSessionIds()[0];return e.getVideoPlayerBySessionId(t)},seekInteraction=function(e){if(e.source==window){if(e.data.type&&"SEEK"==e.data.type)getVideoPlayer().seek(e.data.time);e.data.type&&"teardown"==e.data.type&&(window.removeEventListener("message",seekInteraction,!1),window.seekScriptLoaded=!1)}};window.addEventListener("message",seekInteraction,!1);`;
injectScript(seekScript);
var seek = function(milliseconds) {
return function() {
logMessage('Seek called with window post Message', true);
uiEventsHappening += 1;
var newPlaybackPosition;
var alreadyUpdated = false;
// send seek event to window w time
window.postMessage({ type: "SEEK", time: milliseconds}, "*");
// delay 250ms to get seek api and start seeking
return delay(250)().then(delayUntil(function() {
// broadcast start of buffering
if(!alreadyUpdated) {
alreadyUpdated = true;
socket.emit('buffering', { buffering: true }, function() {});
}
newPlaybackPosition = getPlaybackPosition();
return getState() !== 'loading';
}, 10000)).catch(function() {
// broadcast end of buffering (timeout)
socket.emit('buffering', { buffering: false }, function() {});
logMessage('Simulated seek timed out. Sending buffer stop to server', true)
}).then(function() {
// broadcast end of buffering (finished loading)
socket.emit('buffering', { buffering: false }, function() {});
var newSeekError = Math.min(Math.max(newPlaybackPosition - milliseconds, -10000), 10000);
shove(seekErrorRecent, newSeekError, 5);
seekErrorMean = mean(seekErrorRecent);
}).then(hideControls).ensure(function() {
uiEventsHappening -= 1;
});
};
};
//////////////////////////////////////////////////////////////////////////
// Socket //
//////////////////////////////////////////////////////////////////////////
// connection to the server
var getURLParameter = function(url, key, queryIndex) {
var searchString = '?' + url.split('?')[queryIndex];
if (searchString === undefined) {
return null;
}
console.log("search String: " + searchString);
var escapedKey = key.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
console.log("escaped Key: " + escapedKey);
var regex = new RegExp('[?|&]' + escapedKey + '=' + '([^&]*)(&|$)');
var match = regex.exec(searchString);
if (match === null) {
return null;
}
return decodeURIComponent(match[1]);
};
var socket;
var url = window.location.href;
var herokuSocket = false;
logMessage('content script url: ' + url);
var sessionIdFromUrl = getURLParameter(url, 'npSessionId', 1);
var npServerIdFromUrl = getURLParameter(url, 'npServerId', 1);
logMessage(npServerIdFromUrl);
logMessage(sessionIdFromUrl);
// var defaultServer = 'wpseth';
var defaultServer = defaultServerOptions[Math.floor(Math.random() * defaultServerOptions.length)];
console.log('default server: ' + defaultServer);
var serverId = npServerIdFromUrl ? npServerIdFromUrl : defaultServer;
var servers = serversConfig;
// Using ws transport vs default preflight XHR requests fixes CORS cross-domain Chrome issues
// Read more at http://maxprog.net.pl/node-js/socket-io-and-cross-domain-communication/
// https://stackoverflow.com/questions/28238628/socket-io-1-x-use-websockets-only
var corsOptions = {transports: ['websocket']};
// socket = io("https://wpseth.netflixparty.com/", corsOptions)
// logMessage('socket: https://wpseth.netflixparty.com/')
if (!npServerIdFromUrl) {
// default server
socket = io('https://' + defaultServer + '.netflixparty.com/', corsOptions);
console.log('socket: https://' + defaultServer + '.netflixparty.com/');
} else if (servers[npServerIdFromUrl]) {
socket = io('https://' + npServerIdFromUrl + '.netflixparty.com/', corsOptions);
console.log('socket: https://' + npServerIdFromUrl + '.netflixparty.com/');
} else {
socket = io('https://netflixparty-server.herokuapp.com', corsOptions);
console.log('socket: https://netflixparty-server.herokuapp.com');
herokuSocket = true;
}
// get the userId from the server
var userId = null;
socket.on('userId', function(data) {
console.log('userId: ' + JSON.stringify(data));
if (userId === null) {
userId = data;
}
});
//////////////////////////////////////////////////////////////////////////
// User Settings //
//////////////////////////////////////////////////////////////////////////
// icon state
// fix content verification errors due to case insensitivity (https://groups.google.com/a/chromium.org/forum/#!searchin/chromium-extensions/%E2%80%9CThis$20extension$20may$20have$20been$20corrupted%E2%80%9D$20%7Csort:date/chromium-extensions/DrSVKXkPCSU/Zw4dg_4MBgAJ)
var icons = ["Batman.svg", "DeadPool.svg", "CptAmerica.svg", "Wolverine.svg", "IronMan.svg", "Goofy.svg", "Alien.svg", "Mulan.svg", "Snow-White.svg", "Poohbear.svg", "Sailormoon.svg", "Sailor Cat.svg", "Pizza.svg", "Cookie.svg", "Chocobar.svg", "hotdog.svg", "Hamburger.svg", "Popcorn.svg", "IceCream.svg", "ChickenLeg.svg"]
var iconsInUse = [];
var userIcons = {};
var userNicknames = {};
var nicknamesInUse = [];
var userSettings = {};
var addIconButton = function(icon) {
var iconButton = jQuery(`
<a class="image-button">
<img class="img-class" src='${chrome.runtime.getURL('img/' + icon)}'>
</a>
`).appendTo(jQuery('#icon-holder')).data('icon', icon);
}
var addIconSelector = function() {
for(var i =0; i < icons.length; i++) {
addIconButton(icons[i]);
}
var buttons = jQuery(".image-button");
}
// TODO: save icon url for userSettings in chrome storage?
// FIX: escape icon URL
var getUserIconURL = function(userId, userIcon) {
if(userIcons[userId]) {
return userIcons[userId]
} else {
var iconURL;
if (userIcon) {
iconURL = chrome.runtime.getURL('img/' + userIcon);
}else {
iconURL = chrome.runtime.getURL('img/' + icons[Math.floor(Math.random() * icons.length)]);
if (iconsInUse.length < icons.length) {
while (iconsInUse.hasOwnProperty(iconURL)) {
iconURL = chrome.runtime.getURL('img/' + icons[Math.floor(Math.random() * icons.length)]);
}
}
}
userIcons[userId] = iconURL;
iconsInUse.push(iconURL);
return userIcons[userId];
}
}
var getUserNickname = function(userId, userNickname) {
if(userNicknames[userId]) {
return userNicknames[userId]
} else {
if(userNickname) {
userNicknames[userId] = userNickname;
nicknamesInUse.push(userNickname);
return userNicknames[userId];
}
}
}
// when user clicks on icon selector button, calls this function
// if (saveToChrome) adds icon as userIcon to chrome storage (async)
// adds userId: userIcon to userIcons map
// add userIcon to userSettings map
// re-renders sidebar based on usersettings map
var setUserIcon = function(userId, userIcon, saveToChrome) {
var userIcon = escapeStr(userIcon);
var render = userIcons[userId];
if(saveToChrome) {
chrome.storage.local.set({"userIcon": userIcon}, function(data) {
if(chrome.runtime.lastError)
{
/* error */
console.log(chrome.runtime.lastError.message);
return;
}
console.log('set user icon chrome storage data: ' + JSON.stringify(data));
console.log('userIcon saved into settings chrome storage: ' + userIcon);
});
userSettings.userIcon = userIcon;
console.log('new user settings after set user icon: ' + JSON.stringify(userSettings));
socket.emit('broadcastUserSettings', { userSettings: userSettings }, function() {});
}
var iconURL = chrome.runtime.getURL('img/' + userIcon);
userIcons[userId] = iconURL;
iconsInUse.push(iconURL);
logIcons();
// if(render) {
// delete old iconUrl from icons in use
renderSidebar();
// }
}
function validateId(id) {
return typeof id === 'string' && id.length === 16;
}
function validateNickname(nickname) {
}
var setUserNickname = function(userId, userNickname, saveToChrome) {
var render = userNicknames[userId];
if(saveToChrome) {
chrome.storage.local.set({"userNickname": userNickname}, function(data) {
if(chrome.runtime.lastError)
{
/* error */
console.log(chrome.runtime.lastError.message);
return;
}
console.log('set user nickname chrome storage data: ' + JSON.stringify(data));
chrome.storage.local.get(['userNickname'], function(result) {
console.log('Value currently is ' + result.key);
});
console.log('userNickname saved into settings chrome storage: ' + userNickname);
});
userSettings.userNickname = userNickname;
console.log('new user settings after set user nickname: ' + JSON.stringify(userSettings));
socket.emit('broadcastUserSettings', { userSettings: userSettings }, function() {});
}
userNicknames[userId] = userNickname;
nicknamesInUse.push(userNickname);
// if(render) {
// delete old iconUrl from icons in use
renderSidebar();
// }
}
chrome.storage.onChanged.addListener(function(changes, areaName) {
console.log("storage change: " + JSON.stringify(changes) + " for " + JSON.stringify(areaName));
});
// re-renders sidebar based on userSettings map
var renderSidebar = function() {
var userIconURL = getUserIconURL(userSettings.userId, userSettings.userIcon);
// console.log("call renderSidebar here: " + userIconURL);
jQuery('#user-icon img').attr('src', userIconURL);
jQuery('.user-icon img').attr('src', userIconURL);
var msgs = jQuery('.msg');
// console.log("msgs length: " + msgs.length);
for(var i = 0; i < msgs.length; i++) {
if(jQuery(msgs[i]).data('permId') && jQuery(msgs[i]).data('userIcon')) {
if(userIcons[jQuery(msgs[i]).data('permId')] != jQuery(msgs[i]).data('userIcon')) {
jQuery(msgs[i]).find("img").attr('src', userIcons[jQuery(msgs[i]).data('permId')]);
jQuery(msgs[i]).data('userIcon', userIcons[jQuery(msgs[i]).data('permId')]);
}
}
if(jQuery(msgs[i]).data('permId') && jQuery(msgs[i]).data('userNickname') == '') {
if(userNicknames[jQuery(msgs[i]).data('permId')] != jQuery(msgs[i]).data('userNickname')) {
if(userNicknames[jQuery(msgs[i]).data('permId')]) {
// jQuery(msgs[i]).find("p .msg-nickname").text(userNicknames[jQuery(msgs[i]).data('permId')]);
// console.log("render message data: " + JSON.stringify(jQuery(msgs[i]).data('message')));
var message = jQuery(msgs[i]).data('message');
var permId = jQuery(msgs[i]).data('permId');
var userIcon = userIcons[jQuery(msgs[i]).data('permId')];
var userNickname = userNicknames[jQuery(msgs[i]).data('permId')]
var nicknameMessage = jQuery(`
<div class="msg-container">
<div class="icon-name">
<div class="icon">
<img src="${jQuery(msgs[i]).data('userIcon')}">
</div>
</div>
<div class="msg-txt message${ message.isSystemMessage ? '-system' : '-txt' }">
<h3>${userNicknames[jQuery(msgs[i]).data('permId')].replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</h3>
<p>${message.body.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
</div>
</div>
`)
jQuery(msgs[i]).replaceWith(nicknameMessage);
jQuery(nicknameMessage).data('permId', permId).data('userIcon', userIcon).data('userNickname', userNickname);
// jQuery(msgs[i]).hide();
}
// replace msg nickname w new nickname
}
}
}
var msgs = jQuery('.msg-container');
// console.log("msgs length: " + msgs.length);
for(var i = 0; i < msgs.length; i++) {
// console.log('msgs data for ' + i + ': ' + jQuery(msgs[i]).data('permId') + ', ' + jQuery(msgs[i]).data('userIcon'));
if(jQuery(msgs[i]).data('permId') && jQuery(msgs[i]).data('userIcon')) {
if(userIcons[jQuery(msgs[i]).data('permId')] != jQuery(msgs[i]).data('userIcon')) {
jQuery(msgs[i]).find("img").attr('src', userIcons[jQuery(msgs[i]).data('permId')]);
jQuery(msgs[i]).data('userIcon', userIcons[jQuery(msgs[i]).data('permId')]);
}
}
if(jQuery(msgs[i]).data('permId') && jQuery(msgs[i]).data('userNickname')) {
if(userNicknames[jQuery(msgs[i]).data('permId')] != jQuery(msgs[i]).data('userNickname')) {
if(userNicknames[jQuery(msgs[i]).data('permId')]) {
jQuery(msgs[i]).find("h3").text(userNicknames[jQuery(msgs[i]).data('permId')]);
jQuery(msgs[i]).data('userNickname', userNicknames[jQuery(msgs[i]).data('permId')]);
}
// replace msg nickname w new nickname
}
}
}
}
var logIcons = function() {
console.log("Icons in use: " + JSON.stringify(iconsInUse));
console.log("user Icons: " + JSON.stringify(userIcons));
}
var getUserIdPromise = function() {
console.log('user Id promise called: ' + userId);
return delayUntil(function() {
return userId;
}, 5000)();
}
function isEmpty(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
var waitUserIdReady = function() {
console.log('wait user id ready called: ');
return function() {
return new Promise(function(resolve, reject) {
return delay(250)().then(delayUntil(function() {
// console.log('userid is called');
return userId != null;
}, Infinity)).then(delayUntil(function() {
// console.log('wait video loading done');
// return (getState() !== 'loading');
// console.log('check user settings');
// var video = getVideo()[0];
return !isEmpty(userSettings);
}, Infinity)).then(function() {
console.log('userId and userSettings are ready!');
// console.log('userId and userSettings is ready!');
if(permIdFix) {
permId = userId;
}
resolve();
});
});
};
}
var getChromeStorage = function() {
return function() {
console.log('fix, userId getChromeStorage: ' + permIdFix + ', ' + userId);
return new Promise(function(resolve, reject) {
if(userSettings.userId && userSettings.userIcon) {
resolve(userSettings);
console.log('fix, userId resolve getChromeStorage: ' + permIdFix + ', ' + userId);
}
chrome.storage.local.get(null, function(data) {
if(permIdFix) {
data.userId = userId;
console.log('fix, userId CALLED getChromeStorage: ' + permIdFix + ', ' + userId);
permId = userId;
}
console.log('icons:' + JSON.stringify(icons));
console.log('user icon:' + JSON.stringify(data.userIcon));
var userIconFix = !icons.includes(data.userIcon);
console.log('userIconFix: ' + userIconFix);
console.log('get chrome storage finished userID: ' + userId);
console.log("get chrome storage finished: " + JSON.stringify(data));
if(!userIconFix && data.userId && data.userIcon) {
userSettings = data
} else if(userIconFix || data.userId && !data.userIcon) {
var dataUserId = data.userId;
var newIcon = icons[Math.floor(Math.random() * icons.length)];
// getUserIconURL(userId, newIcon);
userSettings = {'userId': dataUserId, 'userIcon': newIcon};
setUserIcon(userSettings.userId, userSettings.userIcon, true);
console.log("get chrome storage creating new icon: " + JSON.stringify(userSettings));
resolve(userSettings);
}
resolve(userSettings);
});
});
};
}
// respond to update settings from the server
socket.on('updateSettings', function(data) {
// pushTask(receive(data));
console.log(JSON.stringify(data));
if(data.userSettings.userIcon) setUserIcon(data.userSettings.userId, data.userSettings.userIcon, false);
if(data.userSettings.userNickname) setUserNickname(data.userSettings.userId, data.userSettings.userNickname, false);
});
//////////////////////////////////////////////////////////////////////////
// Chat API //
//////////////////////////////////////////////////////////////////////////
// chat state
var messages = [];
var unreadCount = 0;
var originalTitle = document.title;
// UI constants (80% scale)
// Area(video) before: [((1168+(1274-1168) - 360*0.8)^2)*9/16] = 470k px^2
// Area(video) after: [((1168+(1274-1168) - 360)^2)*9/16] = 546k px^2
var defaultScale = 0.8;
var chatSidebarWidth = 360*defaultScale;
var chatSidebarPadding = 16*defaultScale;
var avatarSize = 20*defaultScale;
var avatarPadding = 4*defaultScale;
var avatarBorder = 2*defaultScale;
var chatVericalMargin = 4*defaultScale;
var chatInputBorder = 2*defaultScale;
var chatMessageHorizontalPadding = 8*defaultScale;
var chatMessageVerticalPadding = 8*defaultScale;
var presenceIndicatorHeight = 30*defaultScale;
// summary state
var messagesCount = 0;
var interactionsCount = 0;
var sessionStartTime; // duration
var summarySent = false;
// this is the markup that needs to be injected onto the page for chat
var chatHTML2 = '';
var setHTML = function() {
console.log('set html called');
chatHtml2 = `
<style>
@import"https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&display=swap";.r-m{margin:0 !important}.r-m-t{margin-top:0 !important}.r-p-t{padding-top:0 !important}.r-m-l{margin-left:0 !important}.r-p-l{padding-left:0 !important}.r-m-r{margin-right:0 !important}.r-p-r{padding-right:0 !important}.r-m-b{margin-bottom:0 !important}.r-p-b{padding-bottom:0 !important}.r-b-r{border-radius:0px !important}.r-boxshadow{box-shadow:none !important}:root{--patreon: #F96854;--base-red: #EF3E3A;--active-red: #ea0f0a;--base-blue: #4da9ff;--base-orange: #ff8d4c;--base-green: #24D154;--base-white: #FAFAFA;--white-5: #F0F0F0;--white-10: #DCDCDC;--white-15: #C8C8C8;--white-20: #B4B4B4;--white-25: #A0A0A0;--white-30: #8C8C8C;--white-35: #787878;--base-black: #191919;--black-5: #5A5A5A;--black-10: #464646;--black-15: #323232;--black-20: #282828;--black-25: #1e1e1e;--black-30: #0a0a0a}.base-white-bg{background-color:var(--base-white)}.white-5-bg{background-color:var(--white-5)}.white-10-bg{background-color:var(--white-10)}.white-15-bg{background-color:var(--white-15)}.white-20-bg{background-color:var(--white-20)}.white-25-bg{background-color:var(--white-25)}.white-30-bg{background-color:var(--white-30)}.white-35-bg{background-color:var(--white-35)}.base-black-bg{background-color:var(--base-black)}.black-5-bg{background-color:var(--black-5)}.black-10-bg{background-color:var(--black-10)}.black-15-bg{background-color:var(--black-15)}.black-20-bg{background-color:var(--black-20)}.black-25-bg{background-color:var(--black-25)}.black-30-bg{background-color:var(--black-30)}.black-35-bg{background-color:var(--black-35)}.base-red-bg{background-color:var(--base-red)}.active-red-bg{background-color:var(--active-red)}.base-orange-bg{background-color:var(--base-orange)}.base-blue-bg{background-color:var(--base-blue)}.base-green-bg{background-color:var(--base-green)}.patreon-bg{background-color:var(--patreon)}.txt-blue{color:var(--base-blue) !important}.txt-red{color:var(--base-red) !important}.txt-white{color:var(--base-white) !important}div,p,span,a,h1,h2,h3,h4,h5,h6,li,ul,button{word-wrap:break-word}:root{--regular: 400;--medium: 500;--semi-bold: 600;--bold: 700;--extra-bold: 800;--black: 900}.extension-title{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--base-red);font-size:16px;letter-spacing:.2px}.extension-txt{font-family:"Poppins",sans-serif;font-weight:var(--regular);color:#fff;font-size:14px}.extension-txt-indicator{font-family:"Poppins",sans-serif;font-weight:var(--regular);color:var(--white-35);font-size:11px}.extension-description{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--white-10);font-size:13px}.extension-border-bot{border-bottom:1px solid var(--black-10)}.extension-border-top{border-top:1px solid var(--black-10)}.extension-btn{width:100%;margin-top:10px;background:var(--base-red);color:var(--base-white);padding:10px 0px;border-radius:2px;font-family:"Poppins",sans-serif;font-weight:var(--medium);transition:background .3s ease;display:flex;flex-flow:wrap row;justify-content:center;font-size:14px}.extension-btn:hover{background:var(--active-red)}.extension-btn a{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--base-white)}#alert,#alert-dialog-wrapper{display:flex;flex-flow:wrap row;position:fixed;width:100%;height:100%;z-index:9999999999;align-items:center;box-shadow:8px 6px 20px 1px rgba(0,0,0,.2)}#alert-dialog-container{background:var(--base-black);max-width:400px;margin:0 auto;border-radius:4px}#alert-title-wrapper{padding:20px 20px 0px 20px}#alert-title-wrapper .alert-title{display:flex;flex-flow:wrap row;justify-content:space-between;align-items:center}#alert-title-wrapper .alert-title .alert-x{color:var(--base-white)}#alert-title-wrapper .extension-border-bot{padding-top:10px}#alert-description{padding:10px 20px 20px 20px}#alert-x-btn{background:none !important;border:none !important}#alert-content-txt{margin:0 !important}#alert-title-txt{margin:0 !important}#alert-return-btn{border:none !important}/*# sourceMappingURL=alert.min.css.map */
</style>
<style tpInjected>
#chat-wrapper {
width: ${chatSidebarWidth}px !important;
height: 100% !important;
background: #1a1a1a;
position: fixed !important;
top: 0 !important;
left: auto !important;
right: 0 !important;
bottom: 0 !important;
cursor: auto;
user-select: text;
-webkit-user-select: text;
z-index: 9999999999 !important;
}
#chat-wrapper #chat-container {
// width: 228px;
height: 100%;
position: relative;
left: 0;
right: 0;
margin: 0 auto;
}
.with-chat {
right: ${chatSidebarWidth}px !important;
width: calc(100% - ${chatSidebarWidth}px) !important;
}
// Raymond's Styling Code
@import"https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800;900&display=swap";body,html{font-size:16px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*{box-sizing:border-box}h1,h2,h3,h4,h5,h6,a,p,ul,li,ol,button,body,html{padding:0em}h1,h2,h3,h4,h5,h6,a,p,ul,li,ol,button,body,html{margin:0em}ul,li,ol,a{text-decoration:none;list-style:none}input,button{border:none}h1,h2,h3,h4,h5,h6,p,span,body,html{user-select:text !important;cursor:auto !important}img{user-select:none !important}div,section,button,input,form,article{outline:none}a{display:block;width:fit-content}button,input,form,fieldset{background:none}button:hover{cursor:pointer}.r-m{margin:0 !important}.r-m-t{margin-top:0 !important}.r-p-t{padding-top:0 !important}.r-m-l{margin-left:0 !important}.r-p-l{padding-left:0 !important}.r-m-r{margin-right:0 !important}.r-p-r{padding-right:0 !important}.r-m-b{margin-bottom:0 !important}.r-p-b{padding-bottom:0 !important}.r-b-r{border-radius:0px !important}.r-boxshadow{box-shadow:none !important}:root{--patreon: #F96854;--base-red: #EF3E3A;--active-red: #ea0f0a;--base-blue: #4da9ff;--base-orange: #ff8d4c;--base-green: #24D154;--base-white: #FAFAFA;--white-5: #F0F0F0;--white-10: #DCDCDC;--white-15: #C8C8C8;--white-20: #B4B4B4;--white-25: #A0A0A0;--white-30: #8C8C8C;--white-35: #787878;--base-black: #191919;--black-5: #5A5A5A;--black-10: #464646;--black-15: #323232;--black-20: #282828;--black-25: #1e1e1e;--black-30: #0a0a0a}.base-white-bg{background-color:var(--base-white)}.white-5-bg{background-color:var(--white-5)}.white-10-bg{background-color:var(--white-10)}.white-15-bg{background-color:var(--white-15)}.white-20-bg{background-color:var(--white-20)}.white-25-bg{background-color:var(--white-25)}.white-30-bg{background-color:var(--white-30)}.white-35-bg{background-color:var(--white-35)}.base-black-bg{background-color:var(--base-black)}.black-5-bg{background-color:var(--black-5)}.black-10-bg{background-color:var(--black-10)}.black-15-bg{background-color:var(--black-15)}.black-20-bg{background-color:var(--black-20)}.black-25-bg{background-color:var(--black-25)}.black-30-bg{background-color:var(--black-30)}.black-35-bg{background-color:var(--black-35)}.base-red-bg{background-color:var(--base-red)}.active-red-bg{background-color:var(--active-red)}.base-orange-bg{background-color:var(--base-orange)}.base-blue-bg{background-color:var(--base-blue)}.base-green-bg{background-color:var(--base-green)}.patreon-bg{background-color:var(--patreon)}.txt-blue{color:var(--base-blue) !important}.txt-red{color:var(--base-red) !important}.txt-white{color:var(--base-white) !important}div,p,span,a,h1,h2,h3,h4,h5,h6,li,ul,button{word-wrap:break-word}:root{--regular: 400;--medium: 500;--semi-bold: 600;--bold: 700;--extra-bold: 800;--black: 900}.extension-title{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--base-red);font-size:16px;letter-spacing:.2px}.extension-txt{font-family:"Poppins",sans-serif;font-weight:var(--regular);color:var(--white-5);font-size:14px}.extension-txt-indicator{font-family:"Poppins",sans-serif;font-weight:var(--regular);color:var(--white-35);font-size:11px}.extension-description{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--white-10);font-size:13px}.extension-border-bot{border-bottom:1px solid var(--black-10)}.extension-border-top{border-top:1px solid var(--black-10)}.extension-btn{width:100%;margin-top:10px;background:var(--base-red);color:var(--base-white);padding:10px 0px;border-radius:2px;font-family:"Poppins",sans-serif;font-weight:var(--medium);transition:background .3s ease;display:flex;flex-flow:wrap row;justify-content:center}.extension-btn:hover{background:var(--active-red)}.extension-btn a{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--base-white)}::-webkit-scrollbar{width:2px}::-webkit-scrollbar-thumb{background:var(--base-red);border-radius:10px}#chat-wrapper{position:fixed;right:0;width:288px;height:100%;background:var(--base-black)}#chat-container{width:250px;height:100%;margin:0 auto;padding:12px 0px}#chat-menu-container{display:flex;flex-flow:wrap row;justify-content:space-between;align-items:center}#chat-menu-container #title h1{font-family:"Poppins",sans-serif;font-weight:var(--medium);color:var(--base-red);font-size:16px;letter-spacing:.5px}#chat-menu-container #function-user{display:flex;flex-flow:wrap row}#chat-menu-container #function-user #link-icon{display:flex;flex-flow:wrap row;align-items:center;padding-right:10px;cursor:pointer}#chat-menu-container #function-user #link-icon .chat-link{color:var(--base-white);width:18px;height:18px;transform:scale(1);transition:color .3s ease}#chat-menu-container #function-user #link-icon .chat-link:hover{color:var(--base-red);transform:scale(1.05)}#chat-menu-container #function-user #user-icon img{width:38px;height:38px;transform:scale(1);transition:transform .3s ease}#chat-menu-container #function-user #user-icon img:hover{transform:scale(1.05)}#chat-history-container{display:flex;flex-flow:wrap column;justify-content:flex-end;height:calc(100% - 140px)}#chat-history-container #chat-history{overflow:auto;width:100%;height:auto;padding-top:10px}#chat-history-container #chat-history .msg,#chat-history-container #chat-history .msg-container{display:flex;flex-flow:wrap row;justify-content:space-between;padding:5px 0px;align-items:center}#chat-history-container #chat-history .msg-container{align-items:flex-start}#chat-history-container #chat-history .msg .icon img,#chat-history-container #chat-history .msg .icon-name img,#chat-history-container #chat-history .msg-container .icon img,#chat-history-container #chat-history .msg-container .icon-name img{width:36px;height:36px}#chat-history-container #chat-history .msg .msg-txt,#chat-history-container #chat-history .msg-container .msg-txt{display:flex;flex-flow:wrap column;width:80%}#chat-history-container #chat-history .msg .message,#chat-history-container #chat-history .msg .message-system,#chat-history-container #chat-history .msg .message-txt,#chat-history-container #chat-history .msg-container .message,#chat-history-container #chat-history .msg-container .message-system,#chat-history-container #chat-history .msg-container .message-txt{width:80%}#chat-history-container #chat-history .msg .message h3,#chat-history-container #chat-history .msg .message-system h3,#chat-history-container #chat-history .msg .message-txt h3,#chat-history-container #chat-history .msg-container .message h3,#chat-history-container #chat-history .msg-container .message-system h3,#chat-history-container #chat-history .msg-container .message-txt h3{font-family:"Poppins",sans-serif;font-weight:var(--semi-bold);color:var(--base-white);font-size:14px}#chat-history-container #chat-history .msg .message p,#chat-history-container #chat-history .msg .message-system p,#chat-history-container #chat-history .msg .message-txt p,#chat-history-container #chat-history .msg-container .message p,#chat-history-container #chat-history .msg-container .message-system p,#chat-history-container #chat-history .msg-container .message-txt p{font-family:"Poppins",sans-serif;font-weight:var(--regular);font-size:14px}#chat-history-container #chat-history .msg .message-txt p,#chat-history-container #chat-history .msg-container .message-txt p{color:var(--white-5);word-break:break-word !important}#chat-history-container #chat-history .msg .message-system p,#chat-history-container #chat-history .msg-container .message-system p{color:var(--white-35);font-style:italic}#chat-input-container input{padding-top:5px;width:100%}#chat-input-container input:hover{cursor:auto !important}#chat-icon-container{display:flex;flex-flow:wrap column;padding-top:10px}#chat-icon-container #icon-title-container{padding-bottom:10px}#chat-icon-container #icon-holder{display:flex;flex-flow:wrap row}#chat-icon-container #icon-holder .image-button{width:25%;padding:1px 3.75px}#chat-icon-container #icon-holder .image-button .img-class{width:100%;height:100%;transform:scale(0.95);transition:transform .3s ease}#chat-icon-container #icon-holder .image-button .img-class:hover{transform:scale(1)}.setting,.setting-container{display:flex;flex-flow:wrap column;display:none}.setting-usericon{width:100%;display:flex;flex-flow:wrap row;justify-content:center;padding-top:10px}.setting-usericon img{width:80px;height:80px;transform:scale(1);transition:transform .3s ease}.setting-usericon img:hover{transform:scale(1.05)}.setting-nickname{margin-top:10px}.setting-nickname .nickname,.setting-nickname .nickname-input,.setting-nickname .nickname-wrap{width:100%}.setting-nickname .nickname-wrap{display:flex;flex-flow:wrap column}.setting-nickname .nickname-input{margin-top:5px}.setting-nickname .nickname-input input{border-radius:2px;padding:8px 10px;width:100%;background:var(--black-15)}.setting-nickname .nickname-input input:hover{cursor:auto !important}#presence-indicator{display:block;padding-bottom:5px;height:20px}#patreon,#patreon-link,#patreon-container{display:flex;flex-flow:wrap row;justify-content:space-between;align-items:center;width:100%}#patreon-container{padding-top:10px}#patreon-link img{border-radius:20px;width:130px}#teleparty-blog-container{display:flex;flex-flow:wrap row;padding-top:10px;z-index:10}#teleparty-blog-btn{display:flex;flex-flow:wrap row;align-items:center;justify-content:space-between;width:100%;height:40px}#teleparty-blog-btn img{height:32px}#teleparty-blog-btn p{font-family:"Poppins",sans-serif;font-weight:var(--medium);background:var(--base-red);color:var(--base-white);padding:10px 20px;border-radius:20px}#teleparty-blog-btn p:hover{cursor:pointer !important}/*# sourceMappingURL=style.min.css.map */
// Raymond's Styling Code
</style>
<script tpInjected>
var script = document.createElement('script');
script.src = 'https://code.jquery.com/jquery-1.11.0.min.js';
document.getElementsByTagName('head')[0].appendChild(script);
</script>
<div id="notification-link" class="notification-links" tpInjected>
</div>
<div id="chat-wrapper" tpInjected>
<div id="chat-container">
<div id="chat-header-container">
<ul id="chat-menu-container">
<li id="function-title">
<div id="title">
<p class="extension-title">Teleparty</p>
</div>
</li>
<li id="function-user">
<div id="link-icon">
<img class="chat-link" src='${chrome.runtime.getURL("img/Link.svg")}'>
<input id="share-url" type="text" readonly="true" autocomplete="off" autofocus style="display:none;" />
</div>
<a id="user-icon">
<img src='${getUserIconURL(userSettings.userId, userSettings.userIcon)}'>
</a>
</li>
</ul>
<div id="chat-link-container" style='display:none;'>
<div id="chat-link">
<div id="chat-link-url">
<p>The url link goes here.</p>
</div>
<div id="chat-link-icon">
<img src='${chrome.runtime.getURL("img/Link.svg")}'>
</div>
</div>
</div>
<div id="chat-icon-container" style="display:none">
<div id="icon-title-container">
<div id="icon-title">
<p class="extension-description">Click to switch icon</p>
</div>
</div>
<div id="icon-holder-container">
<div id="icon-holder-template">
<ul id="icon-holder">
</ul>
</div>
</div>
</div>
</div>
<div id="setting-edit" class="chat-settings-container setting-container" style="display:none">
<div class="setting-usericon">
<div class="section-b-inner section-inner">
<a class="user-icon">
<img src='${getUserIconURL(userSettings.userId, userSettings.userIcon)}' />
</a>
</div>
</div>
<div class="section-c setting-nickname">
<div class="section-c-inner section-inner">
<div class="nickname-section row-wrap">
<div class="nickname-wrap row-one">
<p class="extension-description">Nickname</p>
</div>
<div class="nickname-input row-two">
<input id="nickname-edit" class="extension-txt" type="text" placeholder='${userSettings.userNickname ? escapeStr(userSettings.userNickname) : "Add a nickname"}'/>
</div>
</div>
</div>
</div>
</div>
<div id="settings-save" class="chat-settings-container setting-container" style="display:none">
<div class="section-d">
<div class="section-d-inner section-inner">
<div class="btns">
<button class='extension-btn'>Save Changes</button>
</div>
</div>
</div>
</div>
<div id="chat-history-container">
<div id="chat-history">
</div>
</div>
<div id="presence-indicator" class="extension-txt-indicator">
<p class="extension-txt-indicator">People are currently typing...</p>
</div>
<div id="chat-input-container" class="extension-border-top">
<input id="chat-input" class="extension-txt" type="text" placeholder="Type a message..." autocomplete="off"></input>
</div>
<div id="teleparty-blog-container">
<a id="teleparty-blog-btn" target="none" href="https://www.netflixparty.com/introducing-teleparty">
<img src="${escapeStr(chrome.runtime.getURL('img/tp_logo.svg'))}" alt="">
<p class="extension-txt-indicator">Introducing Teleparty</p>
</a>
</div>
</div>
</div>
`;
}
var checkTestExtension = function() {
if(jQuery(".ltr-fntwn3").length > 0) {
var testParticipationUrl = "https://www.netflix.com/donottest";
showButtonMessage(testParticipationModal, testParticipationUrl);
}
}
// this is used for the chat presence feature
var typingTimer = null;
var allowFullScreen = function() {
// allow keyboard input on fullscreen
var script = document.createElement( "script" );
var fsCode = `
// document.getElementsByClassName("sizing-wrapper")[0].webkitRequestFullScreen = function() {
// console.log("fullscreen test");
// var fullScreenWrapper = document.getElementsByClassName("nf-kb-nav-wrapper")[0];
// fullScreenWrapper.webkitRequestFullScreen(fullScreenWrapper.ALLOW_KEYBOARD_INPUT);
// }
// var fullScreenWrapper = document.getElementsByClassName("nf-kb-nav-wrapper")[0];
// fullScreenWrapper.webkitRequestFullScreen(fullScreenWrapper.ALLOW_KEYBOARD_INPUT);
// document.getElementsByClassName("sizing-wrapper")[0].requestFullScreen = function() {
// console.log("fullscreen test");
// var fullScreenWrapper = document.getElementsByClassName("nf-kb-nav-wrapper")[0];
// fullScreenWrapper.webkitRequestFullScreen(fullScreenWrapper.ALLOW_KEYBOARD_INPUT);
// }
document.getElementsByClassName("sizing-wrapper")[0].requestFullscreen = function() {console.log('test');}
console.log("fullscreen loaded? :" + document.getElementsByClassName('button-nfplayerFullscreen').length);
document.getElementsByClassName('button-nfplayerFullscreen')[0].onclick = function() {
console.log('fullscreen click');
var fullScreenWrapper = document.getElementsByClassName("nf-kb-nav-wrapper")[0];
fullScreenWrapper.webkitRequestFullScreen(fullScreenWrapper.ALLOW_KEYBOARD_INPUT);
}
// var fullScreenWrapper = document.getElementsByClassName("nf-kb-nav-wrapper")[0];
// fullScreenWrapper.webkitRequestFullScreen(fullScreenWrapper.ALLOW_KEYBOARD_INPUT);
// }
`;
script.text = fsCode;
document.head.appendChild( script );
}
// set up the chat state, or reset the state if the system has already been set up
var initChat = function() {
if (jQuery('#chat-wrapper').length === 0) {
// setHTML().then(jQuery('.sizing-wrapper').after(chatHtml2));
if (jQuery('.sizing-wrapper').length == 0 || jQuery(".ltr-fntwn3").length > 0) {
var testParticipationUrl = "https://www.netflix.com/donottest";
showButtonMessage(testParticipationModal, testParticipationUrl);
jQuery('body').after(chatHtml2);
teardown();
return;
}
jQuery('.sizing-wrapper').after(chatHtml2);
allowFullScreen();
jQuery('#presence-indicator').data('typing', false);
jQuery('#presence-indicator').data('buffering', false);
setPresenceVisible(false);
// setPresenceText();
// jQuery('#presence-indicator').hide();
var userIconListener = function(e) {
console.log('userIcon button clicked');
// console.log()
toggleIconContainer();
}
jQuery('#user-icon').click(userIconListener);
var linkIconListener = function(e) {
console.log('linkIcon button clicked');
if(sessionId && serverId) {
const el = document.createElement('textarea');
el.value = getPartyUrl();
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
}
jQuery('#link-icon').click(linkIconListener);
// large user icon listener to go to icon holder menu
var largeUserIconListener = function(e) {
console.log('userIcon button clicked');
// console.log()
toggleLargeUserIconButton();
}
jQuery('.user-icon').click(largeUserIconListener);
addIconSelector();
var userIconSelectorListener = function(e) {
if(jQuery(this).data('icon')) {
console.log('userIconSelector button clicked: ' + jQuery(this).data('icon'));
setUserIcon(userSettings.userId, jQuery(this).data('icon'), true);
}
// setUserIcon();
// logIcons();
// console.log()
toggleIconContainer();
}
jQuery('.image-button').click(userIconSelectorListener);
var saveChangesListener = function(e) {
var nicknameText = jQuery('.nickname-input input').val().slice(0,25).replace(/^\s+|\s+$/g, '');
if(nicknameText != '') {
console.log('saveChanges button clicked: ' + nicknameText);
setUserNickname(userSettings.userId, nicknameText, true);
}
// setUserIcon();
// logIcons();
// console.log()
toggleIconContainer();
}
jQuery('.btns button').click(saveChangesListener);
var oldPageX = null;
var oldPageY = null;
jQuery('#chat-wrapper').mousedown(function(e) {
oldPageX = e.pageX;
oldPageY = e.pageY;
});
jQuery('#chat-wrapper').mouseup(function(e) {
if ((e.pageX - oldPageX) * (e.pageX - oldPageX) + (e.pageY - oldPageY) * (e.pageY - oldPageY) < 5) {
jQuery('#chat-input').focus();
e.stopPropagation();
}
});
jQuery('#chat-input-container').click(function(e) {
jQuery('#chat-input').focus();
});
jQuery('#chat-input').keyup(function(e) {
e.stopPropagation();
// event keycode 13 is the enter key
if (e.which === 13) {
var body = jQuery('#chat-input').val().replace(/^\s+|\s+$/g, '');
if (body !== '') {
if (typingTimer !== null) {
clearTimeout(typingTimer);
typingTimer = null;
socket.emit('typing', { typing: false }, function() {});
}
jQuery('#chat-input').prop('disabled', true);
socket.emit('sendMessage', {
body: body
}, function() {
jQuery('#chat-input').val('').prop('disabled', false).focus();
});
}
} else {
if (typingTimer === null) {
socket.emit('typing', { typing: true }, function() {});
} else {
clearTimeout(typingTimer);
}
typingTimer = setTimeout(function() {
typingTimer = null;
socket.emit('typing', { typing: false }, function() {});
}, 500);
}
});
// jQuery('#chat-input-avatar').html(`<img src="data:image/png;base64,${new Identicon(Sha256.hash(userId).substr(0, 32), avatarSize * 2, 0).toString()}" />`);
// receive messages from the server
socket.on('sendMessage', function(data) {
addMessage(data, false);
});
// receive presence updates from the server
socket.on('setPresence', function(data) {
setPresenceVisible(data.anyoneTyping);
});
// receive buffering presence updates from the server
socket.on('setBufferingPresence', function(data) {
setBufferingPresenceVisible(data.anyoneBuffering);
othersAreBuffering = data.anyoneBuffering;
var time = new Date();
var timeStatus = ' at' + time.getHours() + ':' + time.getMinutes() + ':' + time.getMilliseconds() + 'AM';
// console.log('received buffering update: ' + data.anyoneBuffering + timeStatus);
// if(othersAreBuffering) console.log('others are buffering: ' + othersAreBuffering + timeStatus);
});
} else {
jQuery('#chat-history').html('');
}
};
// query whether the chat sidebar is visible
var getChatVisible = function() {
return jQuery('.sizing-wrapper').hasClass('with-chat');
};
// show or hide the chat sidebar
var setChatVisible = function(visible) {
if (visible) {
jQuery('.sizing-wrapper').addClass('with-chat');
jQuery('.sizing-wrapper').css('right', chatSidebarWidth + 'px');
jQuery('#chat-wrapper').show();
if (!document.hasFocus()) {
clearUnreadCount();
}
} else {
jQuery('#chat-wrapper').hide();
jQuery('.sizing-wrapper').removeClass('with-chat');
jQuery('.sizing-wrapper').css('right', '0px');
}
};
var toggleIconContainer = function() {
if(jQuery("#chat-icon-container").data('active')) {
jQuery("#chat-icon-container").data('active', false);
jQuery("#chat-icon-container").hide();
jQuery(".chat-settings-container").hide();
jQuery("#chat-history-container").show();
jQuery("#chat-input-container").show();
jQuery("#teleparty-blog-container").show();
jQuery('#presence-indicator').show();
} else {
jQuery("#chat-icon-container").data('active', true);
jQuery(".chat-settings-container").show();
jQuery("#chat-icon-container").hide();
jQuery("#chat-link-container").hide();
jQuery("#chat-history-container").hide();
jQuery("#chat-input-container").hide();
jQuery("#teleparty-blog-container").hide();
jQuery('#presence-indicator').hide();
}
};
// show or hide the icon container
var toggleLargeUserIconButton = function() {
// console.log('toggle')
// document.getElementById("demo").innerHTML = "Hello World";
if(jQuery("#chat-icon-container").data('active')) {
// jQuery("#chat-icon-container").data('active', false);
jQuery("#chat-icon-container").show();
// jQuery("#chat-link-container").show();
jQuery(".chat-settings-container").hide();
// jQuery(".chat-settings-container").data('active', false);
}
};
// remove chat
var removeChat = function() {
clearUnreadCount();
jQuery('#chat-container').remove();
jQuery('#chat-wrapper').remove();
jQuery('.sizing-wrapper').removeClass('with-chat');
jQuery('.sizing-wrapper').css('right', '0px');
};
// show or hide the "People are typing..." indicator
var setPresenceVisible = function(visible) {
jQuery('#presence-indicator').data('typing', visible);
setPresenceText();
};
// show or hide the "People are buffering..." indicator
var setBufferingPresenceVisible = function(visible) {
jQuery('#presence-indicator').data('buffering', visible);
setPresenceText();
};
var setPresenceText = function() {
var typing = jQuery('#presence-indicator').data('typing');
var buffering = jQuery('#presence-indicator').data('buffering');
if(typing && buffering) {
jQuery('#presence-indicator').text('People are typing and buffering...')
} else if (typing) {
jQuery('#presence-indicator').text('People are typing...')
} else if(buffering) {
jQuery('#presence-indicator').text('People are buffering...')
} else {
jQuery('#presence-indicator').text(' ')
}
}
// var iconURL = chrome.runtime.getURL('img/' + icons[Math.floor(Math.random() * icons.length)]);
// add a message to the chat history
var addMessage = function(message, firstTime) {
if(message.isSystemMessage && message.body === 'left') {
console.log("trying to add left message");
if(!(message.userIcon)) {
// ignore invalid left message
return;
}
}
if (firstTime && message.isSystemMessage && message.body.indexOf('updated their user icon') > -1) {
// Fixes the issue where nicknames are incorrect when someone joins
if(message.userIcon) setUserIcon(message.permId, message.userIcon, false);
if(message.userNickname) setUserNickname(message.permId, message.userNickname, false);
}
messages.push(message);
var userIcon = message.userIcon ? getUserIconURL(message.permId, message.userIcon) : getUserIconURL(message.permId);
var userNickname = message.userNickname ? getUserNickname(message.permId, message.userNickname) : '' // todo create getUserNickame method
if(userNickname == '') {
var message = jQuery(`
<div class="msg">
<div class="icon">
<img src="${escapeStr(userIcon)}"/>
</div>
<div class="message${ message.isSystemMessage ? '-system' : '-txt' }">
<p class="msg-nickname">${userNickname.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
<p>${message.body.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
</div>
</div>
`).appendTo(jQuery('#chat-history')).data('permId', message.permId).data('userIcon', userIcon).data('userNickname', userNickname).data('message', message);
} else {
var nicknameMessage = jQuery(`
<div class="msg-container">
<div class="icon-name">
<div class="icon">
<img src="${escapeStr(userIcon)}">
</div>
</div>
<div class="msg-txt message${ message.isSystemMessage ? '-system' : '-txt' }">
<h3>${userNickname.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</h3>
<p>${message.body.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</p>
</div>
</div>
`).appendTo(jQuery('#chat-history')).data('permId', message.permId).data('userIcon', userIcon).data('userNickname', userNickname);
}
jQuery('#chat-history').scrollTop(jQuery('#chat-history').prop('scrollHeight'));
unreadCount += 1;
messagesCount += 1;
if (!document.hasFocus()) {
document.title = '(' + String(unreadCount) + ') ' + originalTitle;
}
};
// clear the unread count
var clearUnreadCount = function() {
if (unreadCount > 0) {
unreadCount = 0;
document.title = originalTitle;
}
};
// clear the unread count when the window is focused
jQuery(window).focus(function() {
if (getChatVisible()) {
clearUnreadCount();
}
});
// send summary statistics to data server
function logSummary() {
try {
if(permId && sessionStartTime && sessionId && videoId && !summarySent) {
var data = {
userId: permId,
sessionId: sessionId,
messagesCount: messagesCount,
interactionsCount: interactionsCount,
duration: new Date() - sessionStartTime,
videoId: videoId,
videoDuration: videoDuration
}
console.log("summaryData: " + JSON.stringify(data));
chrome.runtime.sendMessage({summary: data}, function(response) {
console.log("background script sent xhr");
});
summarySent = true;
}
} catch(e) {
console.log("log event error");
}
}
var unloadInteraction = function (event) {
logSummary();
}
window.addEventListener("unload", unloadInteraction , true);
//////////////////////////////////////////////////////////////////////////
// Next episode logic //
//////////////////////////////////////////////////////////////////////////
// whether a simulated next episode click is ongoing
var simulClick = false;
// whether the new episode is some other episode or the next episode
var otherEpisode = false
// receive next episode updates from the server
socket.on('jumpToNextEpisode', function(data) {
console.log('received next Episode event: ' + JSON.stringify(data));
if(data.otherEpisode) {
continueOtherEpisode(data.nextEpisode)
} else {
continueNextEpisode(data.nextEpisode);
}
videoId = data.nextEpisode;
});
// enable or disable the session, used during episode transitions
var oldSessionId = null;
var sessionDisabledStartTime = null;
var setSessionEnabled = function(enabled) {
// enable session
if (enabled) {
if(oldSessionId != null && sessionId == null) {
sessionId = oldSessionId;
oldSessionId = null;
sessionDisabledStartTime = null;
// uiEventsHappening = 0;
}
}
// disable session
else {
if(oldSessionId == null && sessionId != null) {
oldSessionId = sessionId;
sessionId = null;
var now = new Date();
sessionDisabledStartTime = new Date(now.getTime() - localTimeMinusServerTimeMedian)
console.log('sessionDisabledStartTime CREATED: ' + sessionDisabledStartTime.getTime());
}
}
};
// disable video synchronization for Netflix Party
var disableSync = function() {
}
// enable video synchronization for Netlfix Party
var enableSync = function() {
}
// jump to next episode if not already on it
var continueNextEpisode = function(nextEpisode) {
var parseIds = window.location.href.match(/^.*\/([0-9]+)\??.*/);
var currVideoId = parseIds.length !== 0 ? parseInt(window.location.href.match(/^.*\/([0-9]+)\??.*/)[1]) : null;
// jump to next episode if currEpisode != next Epsiode
if (currVideoId != nextEpisode) {
// disable the session until the video loads
setSessionEnabled(false);
// oldSessionId = sessionId;
// sessionId = null;
// tasks = Promise.resolve();
simulClick = true;
// otherwise click on the autoplay next episode hover container (short episodes like Friends)
if (jQuery('.WatchNext-still-hover-container').length > 0) {
jQuery('.WatchNext-still-hover-container').click();
}
// click on the next episode button (in the middle of episodes)
else if (jQuery('.button-nfplayerNextEpisode').length > 0) {
jQuery('.button-nfplayerNextEpisode').click();
}
// otherwise click on the credits next episode button (before AND after credits on long episodes like Umbrella Academy)
else if (jQuery('.nf-flat-button-text').length > 0) {
if(jQuery('.nf-flat-button-text')[0].text.toLowerCase().includes('next episode')) {
jQuery('.nf-flat-button-text')[0].click();
// console.log('after credits click');
}
}
}
};
var continueOtherEpisode = function(otherEpisode) {
// tabs[0].url.split('?')[0] + '?npSessionId=' + encodeURIComponent(sessionId) + '&npServerId=' + encodeURIComponent(serverIdFromUrl);
// console.log()
var parseIds = window.location.href.match(/^.*\/([0-9]+)\??.*/);
var currVideoId = parseIds.length !== 0 ? parseInt(window.location.href.match(/^.*\/([0-9]+)\??.*/)[1]) : null;
if (currVideoId != otherEpisode) {
showButtonMessage(longPartyRedirectModal, getPartyUrl());
}else {
showButtonMessage(longPartyModal, getPartyUrl())
}
teardown();
}
// listen to URL changes on the webpage when next episode changes
// modifies the window.history.replaceScript API which Netflix uses in its SPA
// https://stackoverflow.com/questions/4570093/how-to-get-notified-about-changes-of-the-history-via-history-pushstate
// http://jsfiddle.net/UZHTW/1/
var replaceStateScript = `
if(!window.replaceScriptLoaded) {
console.log("loaded script");
window.replaceScriptLoaded = true;
(function(history){
var replaceState = history.replaceState;
history.replaceState = function(state) {
if (typeof history.onreplacestate == "function") {
history.onreplacestate({state: state});
}
return replaceState.apply(history, arguments);
}
})(window.history);
var reloadInteraction = function(e) {
console.log("window.history replaceState event; autoplay / next episode clicked / navigated away");
// send message to content script w next episode
window.postMessage({ type: "FROM_PAGE", text: "next episode from the webpage!"}, "*");
}
window.onpopstate = history.onreplacestate = reloadInteraction;
}
`;
console.log('replace script loaded: '+ window.replaceScriptLoaded);
if(!window.replaceScriptLoaded) {
console.log('injecting replace script');
injectScript(replaceStateScript);
}
// listen for next episode events from webpage script via the postMessage API
var replaceStateInteraction = function(event) {
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
console.log('***********************************');
logState = true;
console.log("Content script received: " + event.data.text);
var episodePage = window.location.href.match(/^.*\/(watch)\/.*/);
console.log(window.location.href)
if (!episodePage) {
console.log("time to teardown");
teardown();
} else {
var parseIds = window.location.href.match(/^.*\/([0-9]+)\??.*/);
var nextVideoId = parseIds.length !== 0 ? parseInt(window.location.href.match(/^.*\/([0-9]+)\??.*/)[1]) : null;
// console.log("simulClick:" + simulClick);
// console.log("otherEpisode:" + otherEpisode);
socket.emit('nextEpisode', {nextEpisode: nextVideoId, simulClick: simulClick, otherEpisode: otherEpisode}, function (data) {
if(data != null && data.errorMessage != null && data.errorMessage == "Locked Session") {
delayUntil(function () {
return nextVideoId == videoId }, 5000)().catch((err) => {
// We wait 5 seconds to see if the owner changes the video. otherwise we remove the user and show them an alert.
showButtonMessage(ownerOnlyNextEpisodeModal, getPartyUrl());
teardown();
})
}
});
if(otherEpisode) {
showButtonMessage(longPartyModal, getPartyUrl());
teardown();
} else if(!otherEpisode) {
console.log('replace disable session next episode')
setSessionEnabled(false);
// if(oldSessionId == null) {
// oldSessionId = sessionId;
// sessionId = null;
// }
if(oldSessionId != null) {
console.log('oldSessionId is called');
swallow(
delayUntil(function() {
console.log('video unloading');
return !getPlaybackPosition();
}, 5000)
)().then(swallow(
delayUntil(function() {
console.log('replace state loading START');
// todo make sure remaining time > 0:00
var remainingTimeLoaded = false;
var remainingTime = getRemainingTime();
var remainingTimeText = getRemainingTimeText();
// console.log('remainingTime: ' + remainingTime);
// console.log('remainingTimeText: ' + remainingTimeText);
// console.log('remainingTimeLoaded: ' + remainingTimeLoaded);
if(remainingTime != null) {
remainingTimeLoaded = true;
}
return remainingTimeLoaded && getPlaybackPosition() && getState() !== 'loading';
}, Infinity)
)).then(function() {
console.log('replace state loading DONE');
showControls();
// get scrubber time
var localTime = getPlaybackPosition();
var scrubberHead = jQuery(jQuery('.scrubber-head')[0]);
var scrubberTime = parseInt(scrubberHead.attr('aria-valuenow') * 1000);
showControls();
// TODO: get remaining time
// console.log('replace localTime: '+ getPlaybackPosition());
// console.log('replace scrubberTime: '+ scrubberTime);
var now = new Date();
var newLastKnownTime = localTime; // update to use remaining time
// var newLastKnownTime = localTime; // update to use remaining time
var newLastKnownTimeUpdatedAt = new Date(now.getTime() - localTimeMinusServerTimeMedian);
var newState = getState() === 'loading' ? 'paused' : (getState() === 'playing' ? 'playing' : 'paused');
console.log('***********************************');
console.log('localTimeMinusServerTimeMedian: ' + localTimeMinusServerTimeMedian);
console.log('session disabled start time: ' + sessionDisabledStartTime.getTime());
console.log('lastKnownTimeUpdatedAt: ' + lastKnownTimeUpdatedAt.getTime());
var recentUpdate = (lastKnownTimeUpdatedAt.getTime() > sessionDisabledStartTime.getTime())
console.log("RECENT UPDATE: " + recentUpdate);
console.log('***********************************');
if(!recentUpdate) {
oldLastKnownTime = lastKnownTime;
oldLastKnownTimeUpdatedAt = lastKnownTimeUpdatedAt;
oldState = state;
lastKnownTime = newLastKnownTime;
lastKnownTimeUpdatedAt = newLastKnownTimeUpdatedAt;
state = newState;
// console.log('simulClick is false so UPDATE');
socket.emit(updateSessionTarget, {
lastKnownTime: newLastKnownTime,
lastKnownTimeUpdatedAt: newLastKnownTimeUpdatedAt.getTime(),
state: newState,
lastKnownTimeRemaining: getRemainingTime(),
lastKnownTimeRemainingText: getRemainingTimeText(),
videoDuration: getDuration(),
bufferingState: false // change: bufferingState
}, function(data) {
if (data !== undefined && data.errorMessage !== null) {
lastKnownTime = oldLastKnownTime;
lastKnownTimeUpdatedAt = oldLastKnownTimeUpdatedAt;
state = oldState;
// reject();
} else {
// lastKnownTime = newLastKnownTime;
// lastKnownTimeUpdatedAt = newLastKnownTimeUpdatedAt;
// state = newState;
// resolve();
}
// console.log('delay before reenabling for 1 sec');
// delay(1000)().then(function() {
console.log('re-enabling sync after next episode');
console.log('***********************************');
logState = false;
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
allowFullScreen();
setSessionEnabled(true);
// sessionId = oldSessionId;
// oldSessionId = null;
// pushTask(sync);
// pushTask(broadcast(false));
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
simulClick = false;
otherEpisode = false;
// });
});
} else {
console.log('recent update is true NO UPDATE');
// insert logic that checks for recent update between when sync was disable and re-enabled
// if there is one then re-enable sync
// if not then send over our own update
// compare syncDisabledTime to lastKnownUpdatedTimeAt
// if syncDisabledTime is after lastKnowUpdatedTimeAt then
console.log('re-enabling sync after next episode');
console.log('***********************************');
logState = false;
allowFullScreen();
setSessionEnabled(true);
// sessionId = oldSessionId;
// oldSessionId = null;
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
simulClick = false;
otherEpisode = false;
pushTask(sync);
}
})
}
}
}
}
}
window.addEventListener("message", replaceStateInteraction, false);
// listen to clicks on next episode button
var nextEpisodeListener = function(e) {
console.log('next Episode button clicked');
}
// listen to clicks for other episodes
var otherEpisodeListener = function() {
// console.log('listener triggered');
// find() selects on all nested descendants, children() selects on children
// if(jQuery(this).find('.can-play').length > 0) {
console.log('other episode clicked!');
otherEpisode = true;
// }
}
jQuery('.button-nfplayerNextEpisode').click(nextEpisodeListener);
jQuery(document).on('click', '.nfp-episode-preview.expanded.can-play', otherEpisodeListener);
//////////////////////////////////////////////////////////////////////////
// Main logic //
//////////////////////////////////////////////////////////////////////////
// the Netflix player be kept within this many milliseconds of our
// internal representation for the playback time
var maxTimeError = 2500;
var maxFreezeTimeError = 1000;
// configure sync from end
var syncFromEnd = false;
var updateSessionTarget = syncFromEnd ? 'updateSessionFromEnd' : 'updateSession';
// replace permId with userId in userSettings and var permId;
var permIdFix = true;
// the session
var sessionId = null;
var lastKnownTime = null;
var lastKnownTimeUpdatedAt = null;
var ownerId = null;
var state = null;
var videoId = null;
var hostOnly = false;
var videoDuration = null;
var currentPage = window.location.href;
// ping the server periodically to estimate round trip time and client-server time offset
var roundTripTimeRecent = [];
var roundTripTimeMedian = 0;
var localTimeMinusServerTimeRecent = [];
var localTimeMinusServerTimeMedian = 0;
var ping = function() {
return new Promise(function(resolve, reject) {
var startTime = (new Date()).getTime();
socket.emit('getServerTime', { version: version }, function(serverTime) {
var now = new Date();
// compute median round trip time
shove(roundTripTimeRecent, now.getTime() - startTime, 5);
roundTripTimeMedian = median(roundTripTimeRecent);
// compute median client-server time offset
shove(localTimeMinusServerTimeRecent, (now.getTime() - Math.round(roundTripTimeMedian / 2)) - (new Date(serverTime)).getTime(), 5);
localTimeMinusServerTimeMedian = median(localTimeMinusServerTimeRecent);
// console.log('localTime - server time median: ' + localTimeMinusServerTimeMedian);
// console.log('roundTripTimeMedian: ' + roundTripTimeMedian);
});
resolve();
});
};
// this function should be called periodically to ensure the Netflix
// player matches our internal representation of the playback state
var sync = function() {
if (sessionId === null) {
// console.log('sync promise resolved');
return Promise.resolve();
}
if (state === 'paused') {
var promise;
if (getState() === 'paused') {
promise = Promise.resolve();
} else {
promise = pause();
}
return promise.then(function() {
if (Math.abs(lastKnownTime - getPlaybackPosition()) > maxTimeError) {
// console.log('seek event while paused added to promiseChain due to local time exceeding server time');
return seek(lastKnownTime)();
}
});
} else {
return delayUntil(function() {
var syncDelayState = getState();
// console.log('syncDelayState: ' + getState());
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
return getState() !== 'loading';
}, Infinity)().then(function() {
var localTime = getPlaybackPosition();
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
if (Math.abs(localTime - serverTime) > maxTimeError) {
// console.log('seek event added to promiseChain due to local time exceeding server time');
return seek(serverTime)().then(function() {
var localTime = getPlaybackPosition();
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
if (localTime > serverTime && localTime <= serverTime + maxTimeError) {
return freeze(localTime - serverTime)();
} else {
return play();
}
});
} else {
return play();
}
});
}
};
// this is called when we need to send an update to the server
// waitForChange is a boolean that indicates whether we should wait for
// the Netflix player to update itself before we broadcast
var logState = false;
var oldState = getState();
// console.log('oldState: ' + oldState);
stateTimer = setInterval(function() {
oldState = getState();
if(logState) {
// console.log('oldState: ' + oldState);
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
}
}, 100);
var broadcast = function(waitForChange) {
return function() {
console.log('broadcast called w taskInFlight: ' + tasksInFlight);
var localTime = getPlaybackPosition() || "not defined";
var serverTime = lastKnownTime + (state === 'playing' ? ((new Date()).getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
// console.log("localtime, internal servertime, state: " + localTime + ", " + serverTime + ", " + state);
// wait for video player state to change
var promise;
if (waitForChange) {
var oldPlaybackPosition = getPlaybackPosition(); // TODO: fix crazy errors related to errors getting thrown here between episodes (BIG BUG)
// var oldState = getState();
var lastState = state;
// console.log("broadcast state, playback: " + oldState + ", " + oldPlaybackPosition);
promise = swallow(delayUntil(function() {
var newPlaybackPosition = getPlaybackPosition();
var newState = getState();
// console.log("new broadcast state, playback: " + newState + ", " + newPlaybackPosition);
return Math.abs(newPlaybackPosition - oldPlaybackPosition) >= 1500 || newState !== oldState || newState !== lastState;
}, 2500))();
} else {
promise = Promise.resolve();
}
var alreadyUpdated = false;
var bufferingState = false;
return promise.then(delayUntil(function() {
// get scrubber time
var localTime = getPlaybackPosition();
var scrubberHead = jQuery(jQuery('.scrubber-head')[0]);
var scrubberTime = parseInt(scrubberHead.attr('aria-valuenow') * 1000);
// var sessionIdString = sessionId ? sessionId : 'null';
// console.log('showControls Start sessionId, uiEventsHappening: ' + sessionIdString + ', ' + uiEventsHappening);
showControls(); // TODO: fix crazy errors related to errors getting thrown here between episodes (BIG BUG)
// console.log('showControls End sessionId, uiEventsHappening: ' + sessionIdString + ', ' + uiEventsHappening);
// what can i do about this
var now = new Date();
var newLastKnownTime = scrubberTime;
var newLastKnownTimeRemaining = getDuration() - scrubberTime;
var newLastKnownTimeUpdatedAt = new Date(now.getTime() - localTimeMinusServerTimeMedian);
var newState = getState() === 'loading' ? 'paused' : (getState() === 'playing' ? 'playing' : 'paused');
// console.log('currently buffering. current Time: ' + localTime + 'scrubber time: ' + scrubberTime);
if(!alreadyUpdated) {
bufferingState = getState() === 'loading';
if(bufferingState) {
// broadcast start of buffering
alreadyUpdated = true;
socket.emit('buffering', { buffering: true }, function() {});
var time = new Date();
var timeStatus = ' at' + time.getHours() + ':' + time.getMinutes() + ':' + time.getMilliseconds() + 'AM';
// console.log('broadcast user seek: buffering start -> server' + timeStatus);
// send update video event
// console.log('updateSession -> socket connection' + timeStatus);
socket.emit(updateSessionTarget, {
lastKnownTime: newLastKnownTime,
lastKnownTimeUpdatedAt: newLastKnownTimeUpdatedAt.getTime(),
state: newState,
lastKnownTimeRemaining: newLastKnownTimeRemaining,
lastKnownTimeRemainingText: getRemainingTimeText(),
videoDuration: getDuration(),
bufferingState: bufferingState // change: bufferingState
}, function(data) {
if (data !== undefined && data.errorMessage !== null) {
lastKnownTime = oldLastKnownTime;
lastKnownTimeUpdatedAt = oldLastKnownTimeUpdatedAt;
state = oldState;
// reject();
} else {
// resolve();
}
});
}
}
return getState() !== 'loading';
}, Infinity)).then(function() {
if(bufferingState) {
var time = new Date();
var timeStatus = ' at' + time.getHours() + ':' + time.getMinutes() + ':' + time.getMilliseconds() + 'AM';
// console.log('broadcast user seek: buffering end -> server' + timeStatus);
socket.emit('buffering', { buffering: false }, function() {});
}
var now = new Date();
var localTime = getPlaybackPosition();
var serverTime = lastKnownTime + (state === 'playing' ? (now.getTime() - (lastKnownTimeUpdatedAt.getTime() + localTimeMinusServerTimeMedian)) : 0);
var newLastKnownTime = localTime;
var newLastKnownTimeUpdatedAt = new Date(now.getTime() - localTimeMinusServerTimeMedian);
var newState = getState() === 'playing' ? 'playing' : 'paused';
if (state === newState && Math.abs(localTime - serverTime) < 1) {
return Promise.resolve();
} else {
var oldLastKnownTime = lastKnownTime;
var oldLastKnownTimeUpdatedAt = lastKnownTimeUpdatedAt;
var oldState = state;
lastKnownTime = newLastKnownTime;
lastKnownTimeUpdatedAt = newLastKnownTimeUpdatedAt;
state = newState;
return new Promise(function(resolve, reject) {
// console.log('updateSession -> socket connection');
socket.emit(updateSessionTarget, {
lastKnownTime: newLastKnownTime,
lastKnownTimeUpdatedAt: newLastKnownTimeUpdatedAt.getTime(),
state: newState,
lastKnownTimeRemaining: getRemainingTime(),
lastKnownTimeRemainingText: getRemainingTimeText(),
videoDuration: getDuration(),
bufferingState: bufferingState
}, function(data) {
if (data !== undefined && data.errorMessage !== null) {
lastKnownTime = oldLastKnownTime;
lastKnownTimeUpdatedAt = oldLastKnownTimeUpdatedAt;
state = oldState;
reject();
} else {
resolve();
}
});
});
}
}).then(function() {
// wait when others are buffering
// if(othersAreBuffering) {
// return freezeUntil(function() {
// return !othersAreBuffering;
// }, 3000)()
// }
});
};
};
// this is called when data is received from the server
var receive = function(data) {
console.log("received data: " + JSON.stringify(data));
// console.log("received data: " + getDuration());
if(syncFromEnd) {
// try to sync to local video duration - lastKnownTimeRemaining from server update
// negative lastKnownTime -> do nothing
if (getDuration() < data.lastKnownTimeRemaining) {
// update
pushTask(broadcast(false));
return function() { return Promise.resolve(); }
} else {
lastKnownTime = getDuration() - data.lastKnownTimeRemaining;
}
// TODO: if negative last known time fix to send update to force everyone to get onto video
} else {
// try to sync to lastKnownTime from server update
lastKnownTime = data.lastKnownTime;
}
state = data.state;
lastKnownTimeUpdatedAt = new Date(data.lastKnownTimeUpdatedAt);
return sync;
};
// the following allows us to linearize all tasks in the program to avoid interference
var tasks = null;
var tasksInFlight = 0;
var pushTask = function(task) {
// console.log('pushTask called w tasksInFlight: ' + tasksInFlight);
if (tasksInFlight === 0) {
// why reset tasks here? in case the native promises implementation isn't
// smart enough to garbage collect old completed tasks in the chain.
tasks = Promise.resolve();
}
tasksInFlight += 1;
tasks = tasks.then(function() {
if (getState() === 'idle') {
swallow(wakeUp)();
}
}).then(swallow(task)).then(function() {
tasksInFlight -= 1;
});
};
// returns true if a user action is on a next episode button
var isNextEpisodeClick = function(target) {
if(jQuery(target).hasClass('button-nfplayerNextEpisode')) {
console.log('BUTTON NEXT EPISODE CLICK?:')
return true;
}
// otherwise click on the autoplay next episode hover container (short episodes like Friends)
else if (jQuery(target).hasClass('WatchNext-still-hover-container')) {
console.log('HOVER NEXT EPISODE CLICK');
return true;
}
else if (jQuery(target).hasClass('PlayIcon')) {
console.log('HOVER NEXT EPISODE CLICK');
return true;
}
// click on the next episode button (in the middle of episodes)
else if (jQuery(target).hasClass('button-nfplayerNextEpisode').length > 0) {
// jQuery('.button-nfplayerNextEpisode').click()
return true;
}
// otherwise click on the credits next episode button (before AND after credits on long episodes like Umbrella Academy)
else if (jQuery(target).hasClass('nf-flat-button-text')) {
console.log('CREDITS NEXT EPISODE CLICK?:' + jQuery(target).text());
if(jQuery(target).text().toLowerCase().includes('next episode')) {
return true;
// jQuery('.nf-flat-button-text')[0].click();
// console.log('after credits click');
}
} else {
return false;
}
}
var mouseupListener = function() {
console.log("mouseup");
// console.log(jQuery(event.target).attr('class'));
// console.log(jQuery(event.target).text());
// console.log(jQuery(event.target).hasClass('button-nfplayerNextEpisode'));
if(!isNextEpisodeClick(event.target)) {
var sessionIdString = sessionId ? sessionId : 'null';
console.log('sessionId, uiEventsHappening: ' + sessionIdString + ', ' + uiEventsHappening);
if (sessionId !== null && uiEventsHappening === 0 && tasksInFlight < 5) {
pushTask(function() {
return broadcast(true)().catch(sync);
});
}
} else {
console.log("sessionid disabled");
setSessionEnabled(false);
// oldSessionId = sessionId;
// sessionId = null;
// lastKnownTime = null;
// lastKnownTimeUpdatedAt = null;
// var ownerId = null;
// state = null;
}
interactionsCount += 1;
}
var keyupListener = function(e) {
// e.stopPropagation();
console.log("keyup");
// console.log(event.target.className);
// if(jQuery(event.target).hasClass('nfp')) {
if (sessionId !== null && uiEventsHappening === 0 && tasksInFlight < 5) {
pushTask(function() {
return broadcast(true)().catch(sync);
});
}
// }
}
// broadcast the playback state if there is any user activity
jQuery(window).mouseup(mouseupListener);
jQuery(window).keyup(keyupListener);
var scriptInterval = null;
var teardown = function() {
window.postMessage({ type: "teardown"}, "*");
window.removeEventListener("message", replaceStateInteraction, false);
window.removeEventListener("unload", unloadInteraction , true);
jQuery(window).off('mouseup', mouseupListener);
jQuery(window).off('keyup', keyupListener);
jQuery('[tpInjected]').remove();
if(sessionId) sessionId = null;
window.telepartyLoaded = false;
chrome.runtime.onMessage.removeListener(popupInteraction);
if(socket) socket.disconnect();
if(scriptInterval) clearInterval(scriptInterval);
setChatVisible(false);
removeChat();
logState = false;
}
socket.on('connect', function() {
pushTask(ping);
console.log(currentPage);
var lastTime = new Date();
if (!scriptInterval) {
scriptInterval = setInterval(function() {
if (tasksInFlight === 0) {
var newTime = new Date();
// console.log("ping sync: " + (newTime - lastTime));
lastTime = new Date();
pushTask(ping);
pushTask(sync);
}
}, 5000);
}
});
// if the server goes down, it can reconstruct the session with this
socket.on('reconnect', function() {
if (sessionId !== null) {
socket.emit('reboot', {
sessionId: sessionId,
lastKnownTime: lastKnownTime,
lastKnownTimeUpdatedAt: lastKnownTimeUpdatedAt.getTime(),
messages: messages,
state: state,
ownerId: ownerId,
userId: userId,
userSettings: userSettings,
videoService: 'netflix',
videoId: videoId,
videoDuration: getDuration(), //change: sending over video duration
permId: permId, //change: sending over permId
userSettings: userSettings,
videoService: 'netflix'
}, function(data) {
if (!data.errorMessage) {
if (data.videoIds && data.videoIds.pop() !== videoId) {
showButtonMessage(failedNextEpisodeModal, getPartyUrl());
teardown();
}else {
logMessage("Reconnect success");
pushTask(receive(data));
}
}else {
showButtonMessage(lostConnectionModal, getPartyUrl())
teardown();
}
});
}
});
// respond to updates from the server
socket.on('update', function(data) {
// console.log('***********************************');
// console.log('on server update:' + JSON.stringify(data));
pushTask(receive(data));
});
// interaction with the popup
var popupInteraction = function(request, sender, sendResponse) {
if (request.type === 'getInitData') {
version = request.data.version;
// fix bug where urls redirect to title page sometimes
var parseIds = window.location.href.match(/^.*\/([0-9]+)\??.*/);
var currVideoId = parseIds.length !== 0 ? parseInt(window.location.href.match(/^.*\/([0-9]+)\??.*/)[1]) : null;
var videoDomId = (typeof jQuery("video").parent()[0] !== "undefined") ? parseInt(jQuery("video").parent()[0].id) : null;
var videoId = request.data.videoId
if((videoDomId != null) && (videoDomId != currVideoId)) {
history.replaceState('data to be passed', 'Title of the page', '/watch/' + jQuery(jQuery("video").parent())[0].id);
videoId = videoDomId;
}
sendResponse({
sessionId: sessionId,
serverId: serverId,
videoId: videoId,
chatVisible: getChatVisible()
});
return;
}
// TODO: edit for optional create session data
if (request.type === 'createSession') {
waitUserIdReady()().then(function() {
console.log('create session permId: ' + permId);
console.log('create session permId: ' + JSON.stringify(userSettings));
socket.emit('createSession', {
controlLock: request.data.controlLock,
videoId: request.data.videoId,
videoService: 'netflix',
syncFromEnd: syncFromEnd, //change: sending over syncFromEnd
videoDuration: getDuration(), //change: sending over video duration
permId: permId, //change: sending over permId
userSettings: userSettings // change: add userSettings here
}, function(data) {
if (data.errorMessage) {
sendResponse({
errorMessage: data.errorMessage
});
return;
}
initChat();
// getChromeStorage()().then(initChat);
setChatVisible(true);
lastKnownTime = data.lastKnownTime;
lastKnownTimeUpdatedAt = new Date(data.lastKnownTimeUpdatedAt);
messages = [];
sessionId = data.sessionId;
hostOnly = false;
ownerId = request.data.controlLock ? userId : null;
state = data.state;
videoId = request.data.videoId;
videoDuration = getDuration();
pushTask(broadcast(false));
// summary state
sessionStartTime = new Date();
// console.log('defaultServer create: ' + defaultServer);
sendResponse({
sessionId: sessionId,
});
});
})
return true;
}
if (request.type === 'joinSession') { /* change: add permId here. before: socket.emit('joinSession', request.data.sessionId, function(data) { */
waitUserIdReady()().then(function() {
var joinData = herokuSocket ? request.data.sessionId : {sessionId: request.data.sessionId, permId: permId, userSettings: userSettings, videoService: 'netflix',videoId: request.data.videoId} /* change: add usersettings here */
socket.emit('joinSession', joinData, function(data) {
if (data.errorMessage) {
sendResponse({
errorMessage: data.errorMessage
});
return;
}
initChat();
// getChromeStorage()().then(initChat);
setChatVisible(true);
sessionId = request.data.sessionId;
lastKnownTime = data.lastKnownTime;
syncFromEnd = data.syncFromEnd ? data.syncFromEnd : false;
updateSessionTarget = syncFromEnd ? 'updateSessionFromEnd' : 'updateSession';
if(syncFromEnd) {
lastKnownTime = getDuration() - data.lastKnownTimeReamining;
}
lastKnownTimeUpdatedAt = new Date(data.lastKnownTimeUpdatedAt);
messages = [];
for (var i = 0; i < data.messages.length; i += 1) {
addMessage(data.messages[i], true);
}
ownerId = data.ownerId;
hostOnly = data.ownerId != null && data.ownerId !== userId;
state = data.state;
videoId = request.data.videoId;
videoDuration = getDuration();
pushTask(receive(data));
sendResponse({});
});
})
return true;
}
if (request.type === 'leaveSession') {
socket.emit('leaveSession', null, function(_) {
logSummary();
sessionId = null;
hostOnly = false;
setChatVisible(false);
sendResponse({});
});
return true;
}
if (request.type === 'showChat') {
// console.log('showChat');
if (request.data.visible) {
setChatVisible(true);
} else {
setChatVisible(false);
}
sendResponse({});
}
} // end of popup interaciton
//
getUserIdPromise()
.then(getChromeStorage())
.then(setHTML)
.then(checkTestExtension)
.then(chrome.runtime.onMessage.addListener(popupInteraction));
};
if (!window.telepartyLoaded) {
console.log('Teleparty: Injecting Content Script');
window.telepartyLoaded = true;
injectContentScript();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment