├── .gitattributes
├── .github
├── CODEOWNERS
├── workflows
│ ├── delete_old_workflows.yml
│ ├── dependency-review.yml
│ └── codeql-analysis.yml
└── FUNDING.yml
├── images
├── ytn128.png
└── ytn48.png
├── background.js
├── manifest.json
├── LICENSE
├── popup.js
├── popup.html
├── README.md
└── script.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-detectable
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Fallback to project maintainer
2 | * @Nomes77
3 |
--------------------------------------------------------------------------------
/images/ytn128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nomes77/YT-Nonstop/HEAD/images/ytn128.png
--------------------------------------------------------------------------------
/images/ytn48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Nomes77/YT-Nonstop/HEAD/images/ytn48.png
--------------------------------------------------------------------------------
/background.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | chrome.runtime.onInstalled.addListener(() => {
4 | Reload();
5 | });
6 |
7 | function Reload() {
8 | chrome.tabs.query({
9 | url: [
10 | "https://www.youtube.com/*",
11 | "https://music.youtube.com/*"
12 | ]
13 | }, (tabs) => {
14 | for(let tab of tabs) {
15 | chrome.tabs.reload(tab.id)
16 | }
17 | });
18 | };
19 |
--------------------------------------------------------------------------------
/.github/workflows/delete_old_workflows.yml:
--------------------------------------------------------------------------------
1 | name: Delete old workflow runs
2 | on:
3 | workflow_dispatch:
4 | schedule:
5 | - cron: '00 15 1 1,3,5,7,9,11 *'
6 |
7 | jobs:
8 | del_runs:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Delete workflow runs
12 | uses: Mattraks/delete-workflow-runs@main
13 | with:
14 | token: ${{ github.token }}
15 | repository: ${{ github.repository }}
16 | retain_days: 30
17 | keep_minimum_runs: 3
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ["https://www.paypal.com/donate/?hosted_button_id=NRARDMBBMV3LC"]
13 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "YT-Nonstop",
3 | "version": "1.5.3",
4 | "description": "Deletes the annoying \"Video paused. Continue watching?\" confirmation on YouTube and keeps YouTube running nonstop without autoplay message!",
5 | "permissions": [ "storage", "https://www.youtube.com/*", "https://music.youtube.com/*" ],
6 | "background": {
7 | "scripts": [ "background.js" ]
8 | },
9 | "content_scripts": [
10 | {
11 | "matches": [ "https://www.youtube.com/*", "https://music.youtube.com/*" ],
12 | "js": [ "script.js" ],
13 | "run_at": "document_end"
14 | }
15 | ],
16 | "page_action": {
17 | "default_icon": "images/ytn128.png",
18 | "default_popup": "popup.html",
19 | "show_matches": [ "https://www.youtube.com/*", "https://music.youtube.com/*" ]
20 | },
21 | "icons": {
22 | "48": "images/ytn48.png",
23 | "128": "images/ytn128.png"
24 | },
25 | "manifest_version": 2
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
4 | #
5 | # Source repository: https://github.com/actions/dependency-review-action
6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
7 | name: 'Dependency Review'
8 | on: [pull_request]
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | dependency-review:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: 'Checkout Repository'
18 | uses: actions/checkout@v3
19 | - name: 'Dependency Review'
20 | uses: actions/dependency-review-action@v3.0.2
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 BvdP
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/popup.js:
--------------------------------------------------------------------------------
1 | window.onload = () => {
2 | document.getElementById('autoskip-toggle').addEventListener("click", function(event) {
3 | //native html input element toggles on click
4 | setAutoTubeListeners('autoSkip');
5 | });
6 | setSettings([
7 | {key: 'autoSkip', cb: setAutoSkip},
8 | ]);
9 | }; // end window.onload
10 |
11 | // helpers
12 | function setSettings(items){
13 | chrome.storage.sync.get(null, function(data) {
14 | const setItems = {};
15 | items.forEach( ({key, cb}) => {
16 | if (data === undefined || data[key] === undefined || data[key] === null) {
17 | setItems.key = cb(true);
18 | } else if(!data[key]) {
19 | cb(false);
20 | } else {
21 | cb(true);
22 | }
23 | });
24 | // update items if they were undefined or null
25 | Object.keys(setItems).length > 0 && chrome.storage.sync.set(setItems, function() {});
26 | });
27 | }
28 |
29 | function setAutoTubeListeners(key) {
30 | const value = {
31 | autoSkip: document.getElementById('autoskip-toggle').checked,
32 | }[key];
33 | chrome.tabs.query({
34 | url: [
35 | "https://www.youtube.com/*",
36 | "https://music.youtube.com/*"
37 | ]
38 | },
39 | (tabs) => {
40 | for (let tab of tabs) {
41 | chrome.tabs.sendMessage(tab.id, {[key]: value});
42 | }
43 | });
44 | chrome.storage.sync.set({[key]: value});
45 | }
46 |
47 | function setAutoSkip(status) {
48 | return document.getElementById('autoskip-toggle').toggleAttribute('checked', status);
49 | }
50 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ main; manifest_3 ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ main; manifest_3 ]
20 | schedule:
21 | - cron: '00 12 1 * *'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
116 |
117 |
118 |
119 | YT-Nonstop
120 |
121 |
122 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/Nomes77/YT-Nonstop/commits/)
2 | [](https://github.com/Nomes77/YT-Nonstop/commits/main)
3 | [](https://github.com/Nomes77/YT-Nonstop/commits/manifest_3)
4 | [](https://github.com/Nomes77/YT-Nonstop/commits/manifest_3)
5 | [](https://github.com/Nomes77/YT-Nonstop/issues)
6 | [](https://github.com/Nomes77/YT-Nonstop/issues?q=is%3Aissue+is%3Aclosed)
7 | [](https://github.com/Nomes77/YT-Nonstop/releases/)
8 | [](https://github.com/Nomes77/YT-Nonstop/blob/main/LICENSE)
9 | [](https://microsoftedge.microsoft.com/addons/detail/ytnonstop/ddobgngkifgapahlheghhckckkcgpikf)
10 | [](https://microsoftedge.microsoft.com/addons/detail/ytnonstop/ddobgngkifgapahlheghhckckkcgpikf)
11 | [](https://microsoftedge.microsoft.com/addons/detail/ytnonstop/ddobgngkifgapahlheghhckckkcgpikf)
12 | [](https://addons.mozilla.org/en-US/firefox/addon/yt-nonstop/)
13 | [](https://addons.mozilla.org/en-US/firefox/addon/yt-nonstop/)
14 | [](https://addons.mozilla.org/en-US/firefox/addon/yt-nonstop/)
15 | ***
16 | # YT-Nonstop
17 |
18 | Autoclicker for Youtube's latest "feature" - Video paused. Continue watching?
19 | Furthermore it keeps YouTube running and auto-skip to the next video on the list 🔥
20 |
21 |
22 |
23 |
24 |
25 |
26 | ***
27 | #### If you want to load the extension by yourself in your browser without installing it from store or Microsoft suddenly decides to take action against it, follow the instructions below:
28 |
29 | 1. Clone or download this repository
30 | - If you download it, make sure to extract it first
31 | 3. Open the Extension Management page by navigating to `edge://extensions`
32 | - The Extension Management page can also be opened by clicking on the Edge menu, hovering over More Tools then selecting Extensions.
33 | 6. Enable Developer Mode by clicking the toggle switch next to Developer mode.
34 | 7. Click the Load unpacked button and select the extension directory.
35 | 8. Ta-da! The extension has been successfully installed!
36 |
37 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | function injectScript(YTNonstop, tag) {
2 | var node = document.getElementsByTagName(tag)[0];
3 | var init_inject_script = document.createElement('script');
4 | var run_inject_script = document.createElement('script');
5 |
6 | init_inject_script.setAttribute('type', 'text/javascript');
7 | run_inject_script.setAttribute('type', 'text/javascript');
8 |
9 | init_inject_script.append(`YTNonstop = ${YTNonstop}()`);
10 | node.appendChild(init_inject_script);
11 |
12 | run_inject_script.append("autotube = YTNonstop = new YTNonstop();");
13 | node.appendChild(run_inject_script);
14 |
15 | init_inject_script.remove();
16 | }
17 |
18 | let YTNonstop = (function YTNonstop(options) {
19 | const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
20 | const autotube = {
21 | _autoSkip: null,
22 | //getters
23 | getIsAutoSkip: function() { return autotube._autoSkip},
24 | // setters
25 | setAutoSkip: function(value) { return autotube._autoSkip = value},
26 | }
27 | const YTMusic = window.location.hostname === 'music.youtube.com';
28 | const videoPlayer = {
29 | player: () => document.getElementById('movie_player'),
30 | };
31 |
32 | function getTimestamp() {
33 | return new Date().toLocaleTimeString();
34 | }
35 | function log(message) {
36 | console.log(`[YT-Nonstop | ${getTimestamp()}] ${message}`);
37 | }
38 |
39 | // .getPlayerState(): -1 = unstarted, 0 = ended, 1 = playing, 2 = paused, 3 = buffering, 5 = video cued
40 | // if video paused ---> play video
41 | const play = () => {
42 | const popupEventNodename = YTMusic ? document.querySelector('YTMUSIC-YOU-THERE-RENDERER') :
43 | document.querySelector('YT-CONFIRM-DIALOG-RENDERER');
44 | const popupContainer = YTMusic ? document.getElementsByTagName('ytmusic-popup-container')[0] :
45 | document.getElementsByTagName('ytd-popup-container')[0];
46 | // Make sure that the right popup is shown
47 | const wrongPopup = document.querySelector('YT-CONFIRM-DIALOG-RENDERER #cancel-button:not([hidden])');
48 |
49 | if (videoPlayer.player().getPlayerState() === 2 && popupEventNodename && !wrongPopup) {
50 | videoPlayer.player().playVideo();
51 | popupContainer.handleClosePopupAction_();
52 | log('Popup hidden and video played again');
53 | }
54 | }
55 |
56 | // if video ended ---> skip to next video
57 | const skip = () => {
58 | if (videoPlayer.player().getPlayerState() === 0 && !YTMusic) {
59 | const overlay = document.querySelector('.ytp-autonav-endscreen-countdown-overlay[style="display: none;"]');
60 | const overlay_v = document.getElementsByClassName('ytp-autonav-endscreen-countdown-overlay')[0];
61 | const next = document.getElementsByClassName('ytp-autonav-endscreen-upnext-play-button')[0];
62 | const cancel = document.getElementsByClassName('ytp-autonav-endscreen-upnext-cancel-button')[0];
63 | const autonav_off = document.querySelector('.ytp-autonav-toggle-button-container > .ytp-autonav-toggle-button[aria-checked="false"]');
64 |
65 | if (autotube.getIsAutoSkip() == true && (!overlay || autonav_off)) {
66 | // videoPlayer.player().setAutonav(true);
67 | // videoPlayer.player().nextVideo();
68 | overlay_v.remove();
69 | next.click();
70 | log('Skipped to next video');
71 | } else
72 | if (autotube.getIsAutoSkip() == false && !overlay) {
73 | // videoPlayer.player().setAutonav(false);
74 | overlay_v.remove();
75 | cancel.click();
76 | log('Canceled next video');
77 | }
78 | }
79 | }
80 |
81 | const autonav_button = () => {
82 | const autonav_on = YTMusic ? document.querySelector('#automix[role="button"][aria-pressed="true"]') :
83 | document.querySelector('.ytp-autonav-toggle-button-container > .ytp-autonav-toggle-button[aria-checked="true"]');
84 | const autonav_off = YTMusic ? document.querySelector('#automix[role="button"][aria-pressed="false"]') :
85 | document.querySelector('.ytp-autonav-toggle-button-container > .ytp-autonav-toggle-button[aria-checked="false"]');
86 |
87 | if (autotube.getIsAutoSkip() == true && autonav_off) {
88 | autonav_off.click();
89 | log('Enabled autoplay/autonav');
90 | } else
91 | if (autotube.getIsAutoSkip() == false && autonav_on) {
92 | autonav_on.click();
93 | log('Disabled autoplay/autonav');
94 | }
95 | }
96 |
97 | const autonav_button_style = () => {
98 | const autonav = YTMusic ? document.getElementsByClassName('autoplay')[1] :
99 | document.querySelector('.ytp-button[data-tooltip-target-id="ytp-autonav-toggle-button"]');
100 |
101 | autonav.setAttribute("style", "height:0px; width:0px; opacity:0");
102 | log('Hide autoplay/autonav, since the button is overriden');
103 | }
104 |
105 | function run() {
106 | const play_button = {
107 | getButton: window.document.getElementsByClassName('ytp-play-button ytp-button')[0]
108 | || window.document.getElementById('play-pause-button'),
109 | config: { attributes: true, childList: true, subtree: true },
110 | callback: (mutationsList, observer) => {
111 | play();
112 | skip();
113 | }
114 | }
115 |
116 | const loadSettings = {
117 | setSettings: setInterval(() => {
118 | if (window.location.href.indexOf("/watch") == -1) return;
119 |
120 | // set play button observer
121 | try {
122 | const play_button_observer = new MutationObserver(play_button.callback);
123 | play_button_observer.observe(play_button.getButton, play_button.config);
124 | } catch(e) {
125 | log('Play button does not exist; reload page');
126 | window.location.reload();
127 | }
128 |
129 | // set autonav button
130 | autonav_button();
131 | autonav_button_style();
132 |
133 | clearInterval(loadSettings.setSettings);
134 | }, 1000),
135 |
136 | setAutonavButton: setInterval(() => {
137 | if (window.location.href.indexOf("/watch") == -1) {
138 | if (document.querySelector('ytd-app[miniplayer-is-active]') || document.querySelector('ytmusic-player-bar:not([player-page-open_])')) {
139 | autonav_button();
140 | } else {
141 | return;
142 | }
143 | }
144 | autonav_button();
145 | }, 5000),
146 |
147 | // Autoplay Method 1: Set last time active all 20 minutes to now
148 | // Autoplay Method 2: If video paused and popup visible ---> play video
149 | // Autoplay Method 3: Pause and UnPause after 20 minutes
150 | setOtherMethods: setInterval(() => {
151 | if (window.location.href.indexOf("/watch") == -1) {
152 | if (document.querySelector('ytd-app[miniplayer-is-active]') || document.querySelector('ytmusic-player-bar:not([player-page-open_])')) {
153 | window._lact = Date.now();
154 | log('Reset last time active');
155 | play();
156 | } else {
157 | return;
158 | }
159 | }
160 | window._lact = Date.now();
161 | log('Reset last time active');
162 | play();
163 | }, 600000)
164 | }
165 |
166 | return autotube;
167 | };
168 |
169 | // exposing functions
170 | function _getIsAutoSkip() { return autotube.getIsAutoSkip() };
171 | function YTNonstop () {
172 | this.isAutoSkip = _getIsAutoSkip;
173 | run();
174 | };
175 |
176 | const eventHandler = (key, value) => {
177 | switch(key) {
178 | case "autoSkip":
179 | autotube.setAutoSkip(value);
180 | break;
181 | }
182 | }
183 | addEventListener('message', function(data) {
184 | for (key in data.data){
185 | eventHandler(key, data.data[key]);
186 | }
187 | });
188 |
189 | // Return YTNonstop object
190 | return YTNonstop;
191 | });
192 |
193 | window.onload = (event) => {
194 | chrome.runtime.onMessage.addListener( (data) => {
195 | postMessage(data, '*');
196 | });
197 | chrome.storage.sync.get(null, function(data) {
198 | data = {
199 | autoSkip: data.autoSkip === undefined || data.autoSkip === null ? true : JSON.parse(data.autoSkip),
200 | }
201 | postMessage(data, '*');
202 | // injectScript(YTNonstop, 'html');
203 | });
204 | };
205 |
206 | injectScript(YTNonstop, 'html');
207 |
--------------------------------------------------------------------------------