Last active
February 19, 2020 06:05
-
-
Save LinusCDE/88d804b60df0ede98ee7ac633458db93 to your computer and use it in GitHub Desktop.
Youtube Comment-Block Detector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Raw elements: | |
RAW_ELEMENT_COMMENT_BLOCKED = ` | |
<span class="ytcbd ytcbd-blocked-info" | |
style="background-color: #800000; color: #fefefe; padding: 0 4px; margin: 0 3px; border-radius: 4px; font-weight: 800; font-size: 8pt;" | |
title="Dieser Kommentar wurde höchstwarscheinlich blockiert und ist nur für dich sichtbar! | |
YouTubes Interne Antwort lautet: | |
%MESSAGE%"> | |
<span style="font-size: 9pt">✘</span> | |
Kommentar blockiert! | |
</span> | |
` | |
RAW_ELEMENT_COMMENT_OK = ` | |
<span class="ytcbd ytcbd-accepted-info" | |
style="color: #088000; padding: 0 4px; font-size: 10pt;" | |
title="Dieser Kommentar scheint in Ordnung zu sein."> | |
✔ | |
</span> | |
` | |
YTCBD_DEBUGING = false; | |
function debug(message) { | |
if(YTCBD_DEBUGING) | |
console.log('[DEBUG] ' + message); | |
} | |
function toEnclosedElement(html) { | |
let span = document.createElement('span'); | |
span.innerHTML = html; | |
return span; | |
} | |
function createOkElement() { | |
return toEnclosedElement(RAW_ELEMENT_COMMENT_OK); | |
} | |
function createBlockedElement(message) { | |
while(message.indexOf('"') !== -1) | |
message = message.replace('"', '"'); | |
return toEnclosedElement(RAW_ELEMENT_COMMENT_BLOCKED.replace('%MESSAGE%', message)); | |
} | |
// Intercept request (Source: https://stackoverflow.com/a/48134114) | |
(function(xhr) { | |
var XHR = XMLHttpRequest.prototype; | |
var open = XHR.open; | |
var send = XHR.send; | |
var setRequestHeader = XHR.setRequestHeader; | |
XHR.open = function(method, url) { | |
this._method = method; | |
this._url = url; | |
this._requestHeaders = {}; | |
this._startTime = new Date().toISOString(); | |
return open.apply(this, arguments); | |
}; | |
XHR.setRequestHeader = function(header, value) { | |
this._requestHeaders[header] = value; | |
return setRequestHeader.apply(this, arguments); | |
}; | |
XHR.send = function(postData) { | |
this.addEventListener("load", function() { | |
var endTime = new Date().toISOString(); | |
var myUrl = this._url ? this._url.toLowerCase() : this._url; | |
if (myUrl) { | |
if (postData) { | |
if (typeof postData === "string") { | |
try { | |
// here you get the REQUEST HEADERS, in JSON format, so you can also use JSON.parse | |
this._requestHeaders = postData; | |
} catch (err) { | |
console.log( | |
"Request Header JSON decode failed, transfer_encoding field could be base64" | |
); | |
console.log(err); | |
} | |
} else if ( | |
typeof postData === "object" || | |
typeof postData === "array" || | |
typeof postData === "number" || | |
typeof postData === "boolean" | |
) { | |
// do something if you need | |
} | |
} | |
// here you get the RESPONSE HEADERS | |
var responseHeaders = this.getAllResponseHeaders(); | |
try { | |
if (this.responseType != "blob" && this.responseText) { | |
// responseText is string or null | |
debug('Response from ' + this._url); | |
onResponseReceived(this._url, this.responseText); | |
} | |
} catch (err) { | |
// Probably not in json format | |
} | |
} | |
}); | |
return send.apply(this, arguments); | |
}; | |
})(XMLHttpRequest); | |
async function delay(millis) { | |
return new Promise((resolve, reject) => { | |
setTimeout(resolve, millis); | |
}); | |
} | |
async function onResponseReceived(url, responseText) { | |
debug('Response received.'); | |
if (url != 'https://www.youtube.com/service_ajax?name=createCommentEndpoint' && | |
url != 'https://www.youtube.com/service_ajax?name=updateCommentEndpoint') | |
return; // Not a response to a created comment | |
debug('Response is comment.'); | |
try { | |
let responseObj = JSON.parse(responseText); | |
let commentRenderer; | |
if(url.endsWith('createCommentEndpoint')) // Comment created | |
commentRenderer = commentRenderer = responseObj.data.actions[0].createCommentAction.contents.commentThreadRenderer.comment.commentRenderer; | |
else // Commend edited | |
commentRenderer = commentRenderer = responseObj.data.actions[0].updateCommentAction.contents.commentRenderer; | |
let element = null; | |
if (commentRenderer.hasOwnProperty('replyDisabledNoticeText')) { | |
// Blocked | |
debug('Comment is likely blocked.'); | |
let messageLines = []; | |
try { | |
for (run of commentRenderer.replyDisabledNoticeText.runs) { | |
messageLines.push(run.text); | |
} | |
} catch (ex) { } | |
if (messageLines.length == 0) { | |
messageLines = [ 'No message given.' ] | |
} | |
element = createBlockedElement(messageLines.join('\n')); | |
}else { | |
debug('Comment is likely ok.'); | |
// Not blocked | |
element = createOkElement(); | |
} | |
let commentId = commentRenderer.commentId; | |
debug('Comment Id is ' + commentId + '.'); | |
let comment = null; | |
let polls = 0; | |
while(polls < 20 && comment == null) { | |
polls++; | |
await delay(100); | |
debug('Poll comment.'); | |
comment = findComment(commentId); | |
} | |
debug('Found comment (after ' + polls + ' of 20 polls).'); | |
if(commentId === null) { | |
console.log('ERROR: Couldn\'t find comment!'); | |
return; | |
} | |
clearOldMessages(comment); | |
appendToHeader(comment, element); | |
debug('Likely appended to header.'); | |
} catch (ex) { | |
console.log('ERROR: Failed to check comment!'); | |
console.log(ex); | |
} | |
} | |
function findComment(searchedCommentId) { | |
for(comment of document.getElementsByTagName('ytd-comment-renderer')) { | |
for(elementWithId of comment.querySelectorAll('.published-time-text > a')) { | |
if(! elementWithId.hasAttribute('href')) | |
continue; | |
let href = elementWithId.getAttribute('href'); | |
// href="/watch?v=8O7qr-36mzc&lc=UgxyGddo8fEowF3Oa2l4A5AzAg" | |
let commentId = null; | |
try { | |
let queryString = href.split('?')[1]; | |
let queryArgs = ((queryString.indexOf('&') !== -1) ? queryString.split('&') : [ queryString ]); | |
for(arg of queryArgs) { | |
if(arg.startsWith('lc=')) | |
commentId = arg.split('lc=')[1]; | |
} | |
} catch (ex) {} // Parsing fail. Probably no id here. | |
if(commentId !== null && commentId === searchedCommentId) | |
return comment; | |
} | |
} | |
return null; | |
} | |
function clearOldMessages(comment) { | |
// Deleting and readding comments keeps the old messages also. | |
let header = comment.querySelector('#header-author'); | |
for(oldMessage of header.querySelectorAll('.ytcbd')) { | |
header.removeChild(oldMessage.parentNode); | |
} | |
} | |
function appendToHeader(comment, element) { | |
let header = comment.querySelector('#header-author'); | |
header.appendChild(element); | |
} | |
console.log('[YoutubeCommentBlockDetector] Script running.'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment