skip to Main Content

I try to automatically add some ad breaks to my own YouTube videos by executing a small script in the browser console (written in Javascript).

YouTube ad breaks

My approach is the following one:

const videoLengthInSeconds = 615;
const breakInterval = 15;
const desiredAmountOfBreaks = Math.round(videoLengthInSeconds / breakInterval);

const breakTimeStamps = Array(desiredAmountOfBreaks)
  .fill(0)
  .map((_, i) => {
    return (
      ("0" + ~~(i / 4) + ":0" + 60 * ((i / 4) % 1)).replace(/d(dd)/g, "$1") +
      ":00"
    );
  })
  .slice(1);

function start() {
  breakTimeStamps.forEach(() => {
    document.querySelector("#add-ad-break").click();
  });
  doPolling();
}

let stopPolling = false;

function doPolling() {
  setTimeout(function() {
    if (!stopPolling) {
      const allAdsInDom = document.querySelectorAll(
        ".ytve-ad-breaks-editor-options-panel .ad-break-row"
      );

      if (allAdsInDom.length === desiredAmountOfBreaks - 1) {
        stopPolling = true;
        fillBreaksTimeStamps();
      }
      doPolling();
    }
  }, 2000);
}

function fillBreaksTimeStamps() {
  getAllAdsInDom().forEach((adsItem, i) => {
    const adBreakInput = adsItem.querySelector("#content-container input");
    const relatedBreakTimeStamp = breakTimeStamps[i];
    adBreakInput.value = relatedBreakTimeStamp;
  });
}

function getAllAdsInDom() {
  const allAds = Array.from(
    document.querySelectorAll(
      ".ytve-ad-breaks-editor-options-panel .ad-break-row"
    )
  );
  return allAds;
}

The current status is the following:
Current status after code execution

So it seems that the scripts adds the breaks and also fills out the time stamps.

The problem is: The time stamp input field is somehow not correctly "triggered".
The ad markers are not added somehow. They are not correctly recognized as new ad breaks. See here:

Add breaks - somehow no markers

Normally there should be these markers:

markers

So no ad breaks are really added.

Therefor I tried to improve the function fillBreaksTimeStamps() a bit and tried this:

adBreakInput.focus();
adBreakInput.value = relatedBreakTimeStamp;
adBreakInput.dispatchEvent(new Event("change"));

But also triggering the .focus() or dispatching the change event does not help.

Any suggestions?

2

Answers


  1. first off there are some things, one can not imitate such as the Event.isTrusted property from which one can discern if it was a userinput or a programmaically added input.
    I think youtube might have some boundaries set in order to prevent any malicious actions.
    there are many resources that might help:

    How to simulate a mouse click using JavaScript?

    Is it possible to simulate key press events programmatically?

    How to trigger event in JavaScript?

    you might just find your solution there.

    I, personally find it easier to just create an autohotkey script, which allows you to click any of the buttons in the right order.
    (you can either hardcode the mouse positions or just tell ahk to find an Image on the screen e.g. the button.)
    here is some prototype code, that might solve your problem with js:

    //modified code from https://stackoverflow.com/questions/6157929/how-to-simulate-a-mouse-click-using-javascript
    //added a function that makes pointer event coordinates to be at the exact coordinates of the clicked element
    var defaultOptions = {
        pointerX: 0,
        pointerY: 0,
        button: 0,
        ctrlKey: false,
        altKey: false,
        shiftKey: false,
        metaKey: false,
        bubbles: true,
        cancelable: true
    }
    function extend(destination, source) {
        for (var property in source)
          destination[property] = source[property];
        return destination;
    }
    
    var eventMatchers = {
        'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
        'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/
    }
    
    //function that simulates typing of a string
    function typeString(element, str){
        for (var i = 0; i < str.length; i++){
            var keyCode = str.charCodeAt(i);
            simulateKeyboard(element, "keydown", {keyCode: keyCode});
            simulateKeyboard(element, "keypress", {keyCode: keyCode});
            simulateKeyboard(element, "keyup", {keyCode: keyCode});
            element.value += str[i];
        }
    }
    
    
    //in order to automatically add ad breaks to the video, we can use it like this:
    simulate(document.querySelector("#add-ad-break"), "mousedown");
    simulate(document.querySelector("#add-ad-break"), "click");
    simulate(document.querySelector("#add-ad-break"), "mouseup");
    
    //you can play around a bit and see if it works
    typeString(document.querySelector("#search"), "hello world");
    //now send enter or tab in order for youtube to recognize the input
    simulateKeyboard(document.querySelector("#search"), "keydown", {keyCode: 13});
    
    
    function simulate(element, eventName, extraoptions) {
        var options = extend(defaultOptions, extraoptions || {});
        var oEvent, eventType = null;
        //get position of element and set pointer event coordinates to be at the exact coordinates of the clicked element
    var rect = element.getBoundingClientRect();
    options.pointerX = rect.left + rect.width/2;
    options.pointerY = rect.top + rect.height/2;
    
        for (var name in eventMatchers) {
            if (eventMatchers[name].test(eventName)) { eventType = name; break; }
        }
    
        if (!eventType)
            throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
    
        if (document.createEvent) {
            oEvent = document.createEvent(eventType);
            if (eventType == 'HTMLEvents') {
                oEvent.initEvent(eventName, options.bubbles, options.cancelable);
            }
            else {
                oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
                options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
                options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
            }
            element.dispatchEvent(oEvent);
        }
        else {
            options.clientX = options.pointerX;
            options.clientY = options.pointerY;
            var evt = document.createEventObject();
            oEvent = extend(evt, options);
            element.fireEvent('on' + eventName, oEvent);
        }
        return element;
    }
    function simulateKeyboard(element, eventName, extraoptions) {
        var options = extend(defaultOptions, extraoptions || {});
        var oEvent, eventType = null;
    
        if (document.createEvent) {
            oEvent = document.createEvent("KeyboardEvent");
            oEvent.initKeyboardEvent(eventName, options.bubbles, options.cancelable, document.defaultView, options.key, options.location, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.repeat, options.locale);
            element.dispatchEvent(oEvent);
        }
        else {
            var evt = document.createEventObject();
            oEvent = extend(evt, options);
            element.fireEvent('on' + eventName, oEvent);
        }
        return element;
    }
    <input type=text id=search>
    <button onclick="console.log('click')" id=add-ad-break>onclick</button>

    if this helps or you want a ahk script instead write a comment.

    Login or Signup to reply.
  2. Since I don’t have a monetized youtube account, this is pretty hard to test. I think you are on the right track on your script if that input value is actually the one that triggers the "ad breaks" things on the bottom. The problem is probably how you update the input value, you probably need to find a way how the text input triggers the adding of the breaks:

    One of the methods that I used before was using document.execCommand:

    textInput.focus();
    document.execCommand('insertText', false, '00:10:00');
    

    You can also try something like changing the value, then trigger its onChange event after changing its value with either .value or execCommand or other things:

    textInput.value = '00:10:00';
    
    // one of these OR both of them
    textInput.dispatchEvent(new Event('input', { bubbles: true }));
    textInput.dispatchEvent(new Event('change'));
    

    But these won’t really guarantee it because I can’t test it on my youtube channel, so you really have to try them one by one or even combine them all.

    I think it’s also worth checking what event listeners are attached to the text input using getEventListeners(element), e.g. this is the event listeners we can see if we check the searchbar of Wikipedia homepage:

    getEventListeners($0);
    

    results:

    {
        "input": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "input"
            },
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "input"
            }
        ],
        "compositionstart": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "compositionstart"
            }
        ],
        "compositionend": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "compositionend"
            }
        ],
        "change": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "change"
            },
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "change"
            }
        ],
        "focus": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "focus"
            }
        ],
        "blur": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "blur"
            }
        ],
        "keydown": [
            {
                "useCapture": false,
                "passive": false,
                "once": false,
                "type": "keydown"
            }
        ]
    }
    

    Then you can try to trigger those events.

    If those didn’t work, we can also try to check what events are triggered if we do the normal input manually (without javascript). First we can select the element that you need with inspect element, and look for the input (or you can find a way to get the element input like with document.querySelector or something). Once you found it, select it on dev tools, then enter this line:

    monitorEvents($0); // if you use the select method, $0 will be the element that you selected in the inspect element
    // OR
    monitorEvents(document.querySelector('input');
    

    Then press enter.

    As an example, this is the monitored events when I input "key" to wikipedia search bar:
    enter image description here

    Once you found those events, you can try to replicate those events in order and also combine it with above methods if needed.

    It’s honestly pretty hard to do this without testing it because unfortunately I don’t have a monetized youtube account. I strongly believe that this is very possible with just console scripting, we just need to find a way to trigger it. I will try to make a monetized youtube account for this, it might take a long time though, but I will update this answer once I got the working script, wish me luck.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search