Skip to content

Instantly share code, notes, and snippets.

@marktaiwan
Last active September 26, 2018 14:15
Show Gist options
  • Save marktaiwan/5fb1d60583079ae34b3ae0619f54177f to your computer and use it in GitHub Desktop.
Save marktaiwan/5fb1d60583079ae34b3ae0619f54177f to your computer and use it in GitHub Desktop.
/*
* First things first, I couldn't find a way to disconnect an established
* websocket connection or stop incoming updates and new messages.
* So instead I mutilated the page by changing element ids or deleting them
* altogether so that it breaks most of the existing code.
*/
// modify the chat box to clear out existing messages and prevent new ones
const BUFFER_ID = 'dummybuffer';
const buffer = document.querySelector('#chatbuffer');
buffer.id = BUFFER_ID;
buffer.innerHTML = '';
// Changing the id messed up some of my userstyles I needed for my recording,
// you can probably delete this line.
buffer.style.fontSize = '18px';
// Delete the poll pane
document.querySelector('#pollpane').remove();
// Remove current video.
// Not sure how well this works, probably won't last past video change
// removeCurrentPlayer();
function fetchAndParseChatLog(logURL) {
return new Promise(resolve => {
// Actual chat logs are too big to embed into the code directly,
// so I rehosted them on Pastebin and have the script fetch it from there.
// Why use Cors-Anywhere?
// We need it to get around Pastebin's pesky lack of 'Access-Control-Allow-Origin' header limitation:
// https://medium.com/netscape/hacking-it-out-when-cors-wont-let-you-be-great-35f6206cc646
fetch('https://cors-anywhere.herokuapp.com/' + logURL)
.then((response) => response.text())
.then((text) => {
/* Parsing the log into array of individual messages.
* Example of normal message log:
* 00:01:01+0200 <+Arowid> MarbleMan: damn straight
* Status and poll updates:
* 00:18:36+0200 -!- ServerMode/#berrytube [+v Kurtis] by localhost
* 00:11:38+0200 * Malsententia opened poll: Best Dictionary
*/
const parsedLogs = text.split('\n').reduce((arr, string) => {
if (string != '') {
let splitArray = string.split(' ');
let timestamp, privilege, username, message;
// filter out the status and poll message from the logs
if (splitArray[1] != '-!-' && splitArray[1] != '*') {
timestamp = splitArray.shift();
username = splitArray.shift();
timestamp = timestamp.substring(0, timestamp.length - 5);
privilege = username.charAt(1);
username = username.substring(2, username.length - 1);
message = splitArray.join(' ');
arr.push({timestamp: timestamp, usr: username, msg: message, priv: privilege});
}
}
return arr;
}, []);
resolve(parsedLogs);
});
});
}
// This part is probably over-engineered.
//
// The chat log's timestamps only has a time resolution of seconds. If we post the messages
// directly, during busy segments a whole chunk of messages could appear at once.
//
// So instead, all messages to be posted are sent to messageBuffer first, which imposes a
// minimum of 200 ms interval between each message.
const messageBuffer = (function () {
let interval = 200; //ms
let posting = false;
const queue = [];
const postMessage = function (packet) {
appendMessage(packet);
return new Promise(resolve => {
window.setTimeout(() => resolve(), interval);
});
};
return async function (packet) {
queue.push(packet);
if (posting != true) {
posting = true;
while (queue.length > 0) {
await postMessage(queue.shift());
}
posting = false;
}
};
})();
function appendMessage(packet) {
const myNick = 'marker';
let wrapper = document.createElement('div');
if (packet.msg.toLowerCase().trim().endsWith(' drink!') && packet.priv == '@') {
// check drink call.
// They appear as normal messages in the log, so it's determined by seeing if the
// user has the right privilage and if the message ended in ' drink!'.
wrapper.innerHTML = '<div class="message drink"><table><tr><td width="100%"><span class="nick"></span><span class="msg"><span></span></span></td><td><span class="multi">1x</span></td></tr></table></div>';
wrapper.classList.add('drinkWrap');
} else if (packet.msg.toLowerCase().trim().startsWith('>')) {
wrapper.innerHTML = '<div class="message"><span class="nick"></span><span class="msg"><span class="green"></span></span></div>';
} else {
wrapper.innerHTML = '<div class="message"><span class="nick"></span><span class="msg"><span></span></span></div>';
}
let usrSpan = wrapper.querySelector('.nick');
let msgSpan = wrapper.querySelector('.msg>span');
let textNode = document.createTextNode(packet.msg);
// check if the message contains my name
if (packet.msg.toLowerCase().includes(myNick)) {
wrapper.classList.add('highlight');
}
usrSpan.innerText = packet.usr + ':';
msgSpan.appendChild(textNode);
// Apply emotes. Gotta love unencapsulated functions, saved me a LOT of work
Bem.applyEmotesToTextNode(textNode);
buffer.appendChild(wrapper);
scrollToBottom();
}
function timestampToSeconds(str) {
let array = str.split(':').map(ele => window.parseInt(ele));
return (array[0] * 60 * 60) + (array[1] * 60) + array[2];
}
function getUnixTimestamp() {
return Math.round((new Date()).getTime() / 1000);
}
// Copied from BerryTube, only modified the chat box id.
function scrollToBottom() {
if (!KEEP_BUFFER) {
return;
}
$('#' + BUFFER_ID).prop({
scrollTop: $('#' + BUFFER_ID).prop("scrollHeight")
});
$('#adminbuffer').prop({
scrollTop: $('#adminbuffer').prop("scrollHeight")
});
}
async function startChatReplay(logURL) {
let parsedLogs = await fetchAndParseChatLog(logURL);
const startingTimestamp = timestampToSeconds(parsedLogs[0].timestamp);
const playbackStartTime = getUnixTimestamp();
// Checks the elapsed time against the timestamp in the log at 250 ms intervals.
function messageLoop() {
if (parsedLogs.length == 0 || killswitch) return;
const timeElapsed = getUnixTimestamp() - playbackStartTime;
const timestampInSeconds = timestampToSeconds(parsedLogs[0].timestamp) - startingTimestamp;
if (timestampInSeconds <= timeElapsed) {
let currentTimestamp = parsedLogs[0].timestamp;
do {
let packet = parsedLogs.shift();
messageBuffer(packet);
} while (parsedLogs.length > 0 && currentTimestamp == parsedLogs[0].timestamp); // string comparison will suffice
messageLoop();
} else {
window.setTimeout(messageLoop, 250);
}
}
messageLoop();
}
let killswitch = false; // to stop the replay, set this to 'true' from the console
/* Run this to start the replay:
startChatReplay('https://pastebin.com/raw/u2VrtQmv');
//*/
@DTM9025
Copy link

DTM9025 commented Sep 26, 2018

Once again, thanks for sharing this! This could be improved though by rendering green text (ones that start with ">") green by changing lines 107-155

  if (packet.msg.toLowerCase().trim().endsWith(' drink!') && packet.priv == '@') {
    // check drink call.
    // They appear as normal messages in the log, so it's determined by seeing if the
    // user has the right privilage and if the message ended in ' drink!'.
    wrapper.innerHTML = '<div class="message drink"><table><tr><td width="100%"><span class="nick"></span><span class="msg"><span></span></span></td><td><span class="multi">1x</span></td></tr></table></div>';
    wrapper.classList.add('drinkWrap');
  } else {
    wrapper.innerHTML = '<div class="message"><span class="nick"></span><span class="msg"><span></span></span></div>';
  }

with the following:

  if (packet.msg.toLowerCase().trim().endsWith(' drink!') && packet.priv == '@') {
    // check drink call.
    // They appear as normal messages in the log, so it's determined by seeing if the
    // user has the right privilage and if the message ended in ' drink!'.
    wrapper.innerHTML = '<div class="message drink"><table><tr><td width="100%"><span class="nick"></span><span class="msg"><span></span></span></td><td><span class="multi">1x</span></td></tr></table></div>';
    wrapper.classList.add('drinkWrap');
  } else if (packet.msg.toLowerCase().trim().startsWith('>')) {
    wrapper.innerHTML = '<div class="message"><span class="nick"></span><span class="msg"><span class="green"></span></span></div>';
  } else {
    wrapper.innerHTML = '<div class="message"><span class="nick"></span><span class="msg"><span></span></span></div>';
  }

@marktaiwan
Copy link
Author

Thanks for the heads up! I've completely forgotten about the greentext formattings. The changes have been incorporated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment