@oxymoron.13/tizentube-s95b 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +49 -42
  2. package/S95B-CHANGES.txt +10 -4
  3. package/UPSTREAM-README.md +42 -0
  4. package/dist/userScript.js +2 -2
  5. package/mods/config.js +117 -0
  6. package/mods/domrect-polyfill.js +104 -0
  7. package/mods/features/adblock.js +584 -0
  8. package/mods/features/autoFrameRate.js +42 -0
  9. package/mods/features/enableFeatures.js +19 -0
  10. package/mods/features/moreSubtitles.js +378 -0
  11. package/mods/features/pictureInPicture.js +198 -0
  12. package/mods/features/preferredVideoQuality.js +101 -0
  13. package/mods/features/s95bCodec.js +45 -0
  14. package/mods/features/s95bI18n.js +28 -0
  15. package/mods/features/s95bMenu.js +142 -0
  16. package/mods/features/sponsorblock.js +437 -0
  17. package/mods/features/standaloneUserscript.js +107 -0
  18. package/mods/features/updater.js +119 -0
  19. package/mods/features/userAgentSpoofing.js +73 -0
  20. package/mods/features/videoQueuing.js +64 -0
  21. package/mods/package-lock.json +2321 -0
  22. package/mods/package.json +33 -0
  23. package/mods/resolveCommand.js +376 -0
  24. package/mods/rollup.config.js +41 -0
  25. package/mods/spatial-navigation-polyfill.js +1756 -0
  26. package/mods/tiny-sha256.js +99 -0
  27. package/mods/translations/i18nResources.js +11 -0
  28. package/mods/translations/index.js +18 -0
  29. package/mods/translations/language-names.js +981 -0
  30. package/mods/translations/resources/ar.json +308 -0
  31. package/mods/translations/resources/az.json +308 -0
  32. package/mods/translations/resources/bs.json +308 -0
  33. package/mods/translations/resources/cs.json +308 -0
  34. package/mods/translations/resources/de.json +308 -0
  35. package/mods/translations/resources/dk.json +308 -0
  36. package/mods/translations/resources/el.json +308 -0
  37. package/mods/translations/resources/en.json +308 -0
  38. package/mods/translations/resources/es-419.json +308 -0
  39. package/mods/translations/resources/es.json +308 -0
  40. package/mods/translations/resources/fi-FI.json +308 -0
  41. package/mods/translations/resources/fr.json +311 -0
  42. package/mods/translations/resources/hr.json +308 -0
  43. package/mods/translations/resources/hu.json +308 -0
  44. package/mods/translations/resources/id.json +308 -0
  45. package/mods/translations/resources/it.json +308 -0
  46. package/mods/translations/resources/ja.json +308 -0
  47. package/mods/translations/resources/lt.json +308 -0
  48. package/mods/translations/resources/lv.json +308 -0
  49. package/mods/translations/resources/pl.json +308 -0
  50. package/mods/translations/resources/pt-BR.json +308 -0
  51. package/mods/translations/resources/pt-PT.json +308 -0
  52. package/mods/translations/resources/ro.json +310 -0
  53. package/mods/translations/resources/ru.json +308 -0
  54. package/mods/translations/resources/sr-Cyrl.json +308 -0
  55. package/mods/translations/resources/sr-Latn.json +308 -0
  56. package/mods/translations/resources/tlh.json +308 -0
  57. package/mods/translations/resources/tr.json +308 -0
  58. package/mods/translations/resources/uk.json +308 -0
  59. package/mods/translations/resources/vi.json +309 -0
  60. package/mods/translations/resources/zh-CN.json +308 -0
  61. package/mods/translations/resources/zh-TW.json +308 -0
  62. package/mods/translations/update-translations.js +55 -0
  63. package/mods/ui/chapters.js +137 -0
  64. package/mods/ui/clock.js +71 -0
  65. package/mods/ui/customCommandExecution.js +49 -0
  66. package/mods/ui/customGuideAction.js +119 -0
  67. package/mods/ui/customUI.js +192 -0
  68. package/mods/ui/customYTSettings.js +29 -0
  69. package/mods/ui/disableWhosWatching.js +48 -0
  70. package/mods/ui/settings.js +1024 -0
  71. package/mods/ui/sidebarModification.js +179 -0
  72. package/mods/ui/speedUI.js +119 -0
  73. package/mods/ui/theme.js +26 -0
  74. package/mods/ui/ui.css +157 -0
  75. package/mods/ui/ui.js +261 -0
  76. package/mods/ui/ytUI.js +510 -0
  77. package/mods/userScript.js +28 -0
  78. package/mods/utils/ASTParser.js +67 -0
  79. package/mods/utils/innerTubeCalls.js +138 -0
  80. package/package.json +11 -5
  81. package/service/package-lock.json +4193 -0
  82. package/service/package.json +29 -0
  83. package/service/rollup.config.js +47 -0
  84. package/service/service.js +118 -0
@@ -0,0 +1,137 @@
1
+ function parseTimestamps(input) {
2
+ var lines = input.trim().split('\n');
3
+ var result = [];
4
+ var timestampRegex = /^\d+:\d{2}/;
5
+
6
+ for (var i = 0; i < lines.length; i++) {
7
+ var line = lines[i];
8
+ var match = line.match(timestampRegex);
9
+ if (match) {
10
+ var timePart = match[0];
11
+ var parts = line.split(' ');
12
+ parts.shift();
13
+ var timeParts = timePart.split(':');
14
+ var minutes = parseInt(timeParts[0], 10);
15
+ var seconds = parseInt(timeParts[1], 10);
16
+ var milliseconds = (minutes * 60 + seconds) * 1000;
17
+ var name = parts.join(' ');
18
+ result.push({ time: milliseconds, name: name });
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+
24
+ function marker(title, start, duration, videoID, i) {
25
+ return {
26
+ title: {
27
+ simpleText: title
28
+ },
29
+ startMillis: start,
30
+ durationMillis: duration,
31
+ thumbnailDetails: {
32
+ thumbnails: [
33
+ {
34
+ url: `https://i.ytimg.com/vi/${videoID}/hqdefault.jpg`,
35
+ width: 320,
36
+ height: 180
37
+ }
38
+ ]
39
+ },
40
+ onActive: {
41
+ innertubeCommand: {
42
+ clickTrackingParams: null,
43
+ entityUpdateCommand: {
44
+ entityBatchUpdate: {
45
+ mutations: [
46
+ {
47
+ entityKey: `${videoID}${start}${duration}`,
48
+ type: 'ENTITY_MUTATION_TYPE_REPLACE',
49
+ payload: {
50
+ markersEngagementPanelSyncEntity: {
51
+ key: `${videoID}${start}${duration}`,
52
+ panelId: 'engagement-panel-macro-markers-description-chapters',
53
+ activeItemIndex: i,
54
+ syncEnabled: true
55
+ }
56
+ }
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ function markerEntity(videoID, markers) {
67
+ return {
68
+ entityKey: `${videoID}-key`,
69
+ type: 'ENTITY_MUTATION_TYPE_REPLACE',
70
+ payload: {
71
+ macroMarkersListEntity: {
72
+ key: `${videoID}-key`,
73
+ externalVideoId: videoID,
74
+ markersList: {
75
+ markerType: 'MARKER_TYPE_CHAPTERS',
76
+ markers: markers,
77
+ headerTitle: {
78
+ runs: [
79
+ {
80
+ text: 'Chapters'
81
+ }
82
+ ]
83
+ },
84
+ onTap: {
85
+ innertubeCommand: {
86
+ clickTrackingParams: null,
87
+ changeEngagementPanelVisibilityAction: {
88
+ targetId: 'engagement-panel-macro-markers-description-chapters',
89
+ visibility: 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED'
90
+ }
91
+ }
92
+ },
93
+ markersEdu: {
94
+ enterNudgeText: {
95
+ runs: [
96
+ {
97
+ text: 'To view chapters, press the up arrow button'
98
+ }
99
+ ]
100
+ },
101
+ enterNudgeA11yText: 'To view chapters, press the up arrow button',
102
+ navNudgeText: {
103
+ runs: [
104
+ {
105
+ text: 'Navigate between chapters'
106
+ }
107
+ ]
108
+ },
109
+ navNudgeA11yText: 'Press the left or right arrow button to navigate between chapters'
110
+ },
111
+ loggingDirectives: {
112
+ trackingParams: null,
113
+ enableDisplayloggerExperiment: true
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ export default function Chapters(video) {
122
+ const videoID = video.contents.singleColumnWatchNextResults.results.results.contents[0].itemSectionRenderer.contents[0].videoMetadataRenderer.videoId;
123
+ const videoDescription = video.contents.singleColumnWatchNextResults.results.results.contents[0].itemSectionRenderer.contents[0].videoMetadataRenderer.description.runs[0].text;
124
+ const chapters = parseTimestamps(videoDescription);
125
+ const videoDuration = document.getElementsByTagName('video')[0].duration * 1000;
126
+ let markers = [];
127
+
128
+ for (let i = 0; i < chapters.length; i++) {
129
+ const chapter = chapters[i];
130
+ const nextChapter = chapters[i + 1];
131
+ const duration = nextChapter ? nextChapter.time - chapter.time : videoDuration - chapter.time;
132
+ markers.push(marker(chapter.name, String(chapter.time), String(duration), videoID, i));
133
+ }
134
+
135
+ const markerEntityData = markerEntity(videoID, markers);
136
+ return markerEntityData;
137
+ }
@@ -0,0 +1,71 @@
1
+ import { configChangeEmitter, configRead } from '../config.js';
2
+
3
+ configChangeEmitter.addEventListener('configChange', (e) => {
4
+ if (e.detail.key === 'enableClock') {
5
+ toggleClock(e.detail.value);
6
+ }
7
+ });
8
+
9
+ let actualClock;
10
+
11
+ function toggleClock(value) {
12
+ const existingClock = document.getElementById('tizentube-clock');
13
+ if (value && existingClock) return;
14
+ if (!value && existingClock) {
15
+ existingClock.remove();
16
+ return;
17
+ }
18
+ if (!value && !existingClock) {
19
+ return;
20
+ } else {
21
+ const clock = document.createElement('div');
22
+
23
+ clock.id = 'tizentube-clock';
24
+ clock.style.height = '45rem';
25
+ clock.style.width = '80rem';
26
+ clock.style.position = 'absolute';
27
+ clock.style.top = '50%';
28
+ clock.style.left = '50%';
29
+ clock.style.marginTop = '-22.5rem';
30
+ clock.style.marginLeft = '-40rem';
31
+
32
+ actualClock = document.createElement('div');
33
+
34
+ actualClock.style.position = 'absolute';
35
+ actualClock.style.zIndex = '9999';
36
+ actualClock.style.right = '5%';
37
+ actualClock.style.top = '2%';
38
+ actualClock.style.fontSize = '1.5em';
39
+ clock.appendChild(actualClock);
40
+ document.body.appendChild(clock);
41
+
42
+ function updateClock() {
43
+ const now = new Date();
44
+ const is12HourFormat = configRead('isClock12HourFormat');
45
+ const secondsEnabled = configRead('clockShowSeconds');
46
+ const shouldHideWhenVideoPlaying = configRead('clockHideWhenVideoPlaying');
47
+ const ytlrWatchDefault = document.querySelector('ytlr-watch-default');
48
+
49
+ if (shouldHideWhenVideoPlaying) {
50
+ if (ytlrWatchDefault && ytlrWatchDefault.getAttribute('hybridnavfocusable') === 'true')
51
+ actualClock.style.display = 'none';
52
+ else actualClock.style.display = 'block';
53
+ }
54
+
55
+ let hours = now.getHours();
56
+ if (is12HourFormat) {
57
+ hours = hours % 12 || 12;
58
+ }
59
+
60
+ hours = hours.toString().padStart(2, '0');
61
+ const minutes = now.getMinutes().toString().padStart(2, '0');
62
+ const seconds = now.getSeconds().toString().padStart(2, '0');
63
+ actualClock.textContent = `${hours}:${minutes}${secondsEnabled ? `:${seconds}` : ''}${is12HourFormat ? (now.getHours() >= 12 ? ' PM' : ' AM') : ''}`;
64
+ }
65
+
66
+ updateClock();
67
+ setInterval(updateClock, 1000);
68
+ }
69
+ }
70
+
71
+ toggleClock(configRead('enableClock'));
@@ -0,0 +1,49 @@
1
+ function getCommandExecutor() {
2
+ let instance;
3
+ let executeFunction;
4
+
5
+ for (const key in window._yttv) {
6
+ if (window._yttv[key] && window._yttv[key].getInstance) {
7
+ if (window._yttv[key].toString().includes('ytlrActionRouter')) instance = window._yttv[key].getInstance();
8
+ else {
9
+ let isInstance = false;
10
+ const tempInstance = window._yttv[key].getInstance();
11
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(tempInstance));
12
+ for (const key of keys) {
13
+ if (typeof tempInstance[key] === 'function' && tempInstance[key].toString().includes('ytlrActionRouter')) {
14
+ executeFunction = tempInstance[key];
15
+ isInstance = true;
16
+ }
17
+ }
18
+
19
+ if (isInstance) instance = window._yttv[key].getInstance();
20
+ }
21
+ }
22
+ }
23
+
24
+ if (!instance) return;
25
+
26
+ if (!executeFunction) {
27
+ const keys = Object.getOwnPropertyNames(Object.getPrototypeOf(instance));
28
+ for (const key of keys) {
29
+ if (typeof instance[key] === 'function' && instance[key].toString().includes('ytlrActionRouter')) {
30
+ executeFunction = instance[key];
31
+ }
32
+ }
33
+ }
34
+
35
+ if (!executeFunction) return;
36
+
37
+ let commandFunction;
38
+ for (const key in window._yttv) {
39
+ if (window._yttv[key] && typeof window._yttv[key] === 'function' && window._yttv[key].toString().includes('this.actionName')) {
40
+ commandFunction = window._yttv[key];
41
+ }
42
+ }
43
+ return {
44
+ executeFunction: executeFunction.bind(instance),
45
+ commandFunction
46
+ }
47
+ }
48
+
49
+ export default getCommandExecutor;
@@ -0,0 +1,119 @@
1
+ import { configChangeEmitter, configRead, configWrite } from "../config.js";
2
+ import getCommandExecutor from "./customCommandExecution.js";
3
+ import { GuideEntryRenderer } from "./ytUI.js";
4
+
5
+ const origParse = JSON.parse;
6
+ JSON.parse = function () {
7
+ const r = origParse.apply(this, arguments);
8
+ const guideSection = r.items?.[0]?.guideSectionRenderer;
9
+ const order = configRead('sidebarContentsOrder');
10
+ if (guideSection && Array.isArray(order)) {
11
+ let orderChanged = false;
12
+ for (const item of guideSection.items) {
13
+ // Not every guide item is a guideEntryRenderer: a throw here would fail
14
+ // the caller's JSON.parse and lose the whole guide response.
15
+ const itemOrder = item.guideEntryRenderer?.navigationEndpoint?.browseEndpoint?.browseId
16
+ || (item.guideEntryRenderer?.navigationEndpoint?.searchEndpoint && 'search');
17
+ if (itemOrder && !order.some(orderItem =>
18
+ (typeof orderItem === 'object' ? orderItem.browseId : orderItem) === itemOrder)) {
19
+ order.push(itemOrder);
20
+ orderChanged = true;
21
+ }
22
+ }
23
+ if (orderChanged) configWrite('sidebarContentsOrder', order);
24
+ }
25
+ if (configRead('sidebarContentsOrder')?.length === 0) {
26
+ // Add all of the items in the sidebar to the sidebarContentsOrder
27
+ if (r.items && Array.isArray(r.items) && r.items[0].guideSectionRenderer) {
28
+ const order = [];
29
+ for (const item of r.items[0].guideSectionRenderer.items) {
30
+ const browseId = item.guideEntryRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
31
+ if (browseId) {
32
+ order.push(browseId);
33
+ } else if (item.guideEntryRenderer?.navigationEndpoint?.searchEndpoint) {
34
+ order.push('search');
35
+ }
36
+ }
37
+ configWrite('sidebarContentsOrder', order);
38
+ }
39
+ } else {
40
+ // Reorder the items based on the sidebarContentsOrder config key
41
+
42
+ if (r.items && Array.isArray(r.items) && r.items[0].guideSectionRenderer) {
43
+ const order = configRead('sidebarContentsOrder');
44
+ const items = r.items[0].guideSectionRenderer.items;
45
+ const copiedItems = JSON.parse(JSON.stringify(items));
46
+
47
+ for (const orderItem of order) {
48
+ if (typeof orderItem === 'object' && orderItem !== null) {
49
+ const customItem = GuideEntryRenderer(
50
+ orderItem.title,
51
+ {
52
+ browseEndpoint: {
53
+ browseId: orderItem.browseId
54
+ }
55
+ },
56
+ 'PERSON'
57
+ );
58
+ copiedItems.push(customItem);
59
+ }
60
+ }
61
+
62
+ const orderedItems = [];
63
+ const placed = new Set();
64
+ for (const orderItem of order) {
65
+ const browseId = typeof orderItem === 'object' ? orderItem.browseId : orderItem;
66
+ const index = copiedItems.findIndex(item => {
67
+ const itemBrowseId = item.guideEntryRenderer?.navigationEndpoint?.browseEndpoint?.browseId;
68
+ return itemBrowseId === browseId || (browseId === 'search' && item.guideEntryRenderer?.navigationEndpoint?.searchEndpoint);
69
+ });
70
+ if (index !== -1) {
71
+ orderedItems.push(copiedItems[index]);
72
+ placed.add(index);
73
+ }
74
+ }
75
+ // Entries the stored order knows nothing about must survive, after the
76
+ // ordered ones. The order is not scoped to a profile: it is learned from
77
+ // whichever profile loads first, and only from entries exposing a
78
+ // browseEndpoint.browseId. A YouTube Kids profile's guide entries don't,
79
+ // so they were neither learned above nor matched here — dropping them
80
+ // emptied the whole left menu on that profile (#643, #648).
81
+ copiedItems.forEach((item, index) => {
82
+ if (!placed.has(index)) orderedItems.push(item);
83
+ });
84
+ r.items[0].guideSectionRenderer.items = orderedItems;
85
+ }
86
+ }
87
+
88
+ const disabledSidebarContents = configRead('disabledSidebarContents');
89
+ const disableChannelsOnSidebar = configRead('disableChannelsOnSidebar');
90
+ if (r.items && Array.isArray(r.items) && r.items[0].guideSectionRenderer) {
91
+ for (let i = 0; i < r.items.length; i++) {
92
+ const section = r.items[i].guideSectionRenderer;
93
+ section.originalItems = section.items.slice();
94
+ for (let j = 0; j < section.items.length; j++) {
95
+ const item = section.items[j].guideEntryRenderer;
96
+ if (!item) continue;
97
+ if ((disabledSidebarContents?.length &&
98
+ disabledSidebarContents.includes(item.navigationEndpoint?.browseEndpoint ?
99
+ item.navigationEndpoint.browseEndpoint.browseId :
100
+ 'search'))
101
+ || (disableChannelsOnSidebar && item?.thumbnail)) {
102
+ section.items.splice(j, 1);
103
+ j--;
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ return r;
110
+ }
111
+
112
+ configChangeEmitter.addEventListener('configChange', (e) => {
113
+ if (e.detail.key === 'disabledSidebarContents' || e.detail.key === 'disableChannelsOnSidebar' || e.detail.key === 'sidebarContentsOrder') {
114
+ const commandExecutor = getCommandExecutor();
115
+ if (commandExecutor) {
116
+ commandExecutor.executeFunction(new commandExecutor.commandFunction('reloadGuideAction'));
117
+ }
118
+ }
119
+ });
@@ -0,0 +1,192 @@
1
+ // Custom UI for video player
2
+
3
+ import { extractAssignedFunctions } from "../utils/ASTParser.js";
4
+ import { configRead } from "../config.js";
5
+ import { ButtonRenderer } from "./ytUI.js";
6
+ import { t } from 'i18next';
7
+
8
+ // S95B: result of the AST scan is constant for a given player class, so compute it once.
9
+ let s95bFunctions = null;
10
+ function applyPatches() {
11
+ if (!window._yttv) return setTimeout(applyPatches, 250);
12
+ if (!document.querySelector('video')) return setTimeout(applyPatches, 250);
13
+ const methods = Object.keys(window._yttv).filter(key => {
14
+ return typeof window._yttv[key] === 'function' && window._yttv[key].toString().includes('TRANSPORT_CONTROLS_BUTTON_TYPE_FEATURED_ACTION');
15
+ });
16
+
17
+ if (methods.length === 0) {
18
+ setTimeout(applyPatches, 250);
19
+ return;
20
+ }
21
+
22
+ const origMethod = window._yttv[methods[0]];
23
+
24
+ function YtlrPlayerActionsContainer() {
25
+ const args = Array.prototype.slice.call(arguments);
26
+ const isClass = /^class\s/.test(origMethod.toString());
27
+
28
+ function constructAsNew(ctor, argsList) {
29
+ if (typeof Reflect !== 'undefined' && typeof Reflect.construct === 'function') {
30
+ return Reflect.construct(ctor, argsList, YtlrPlayerActionsContainer);
31
+ }
32
+ return new origMethod(...argsList);
33
+ }
34
+
35
+ if (!(this instanceof YtlrPlayerActionsContainer)) {
36
+ if (isClass) return constructAsNew(origMethod, args);
37
+ return origMethod.apply(this, args);
38
+ }
39
+
40
+ let inst;
41
+ if (isClass) {
42
+ inst = constructAsNew(origMethod, args);
43
+ } else {
44
+ origMethod.apply(this, args);
45
+ inst = this;
46
+ }
47
+
48
+ const functions = s95bFunctions || (s95bFunctions = extractAssignedFunctions(origMethod.toString()));
49
+
50
+ const pipCommand = {
51
+ "type": "TRANSPORT_CONTROLS_BUTTON_TYPE_PIP",
52
+ "button": {
53
+ "buttonRenderer": ButtonRenderer(
54
+ false,
55
+ configRead('enableSwapMPWithPIP') ? t('player.pictureInPicture') : t('player.miniPlayer'),
56
+ 'CLEAR_COOKIES',
57
+ {
58
+ customAction: {
59
+ action: configRead('enableSwapMPWithPIP') ? 'ENTER_PIP' : 'ENTER_MP',
60
+ }
61
+ }
62
+ )
63
+ }
64
+ }
65
+
66
+ const settingActionGroup = functions.find(func => {
67
+ return func.rhs.includes('TRANSPORT_CONTROLS_BUTTON_TYPE_PLAYBACK_SETTINGS');
68
+ }).left.split('.')[1];
69
+
70
+ if (!settingActionGroup) return inst;
71
+
72
+ const origSettingActionGroup = inst[settingActionGroup];
73
+ if (configRead('enableMPButton')) {
74
+ inst[settingActionGroup] = function () {
75
+ const res = origSettingActionGroup.apply(this, arguments);
76
+ const idx = res.findIndex(item => item.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_PLAYBACK_SETTINGS');
77
+ res.find(item => item.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_PIP') || res.splice(idx, 0, pipCommand);
78
+ return res;
79
+ };
80
+ }
81
+
82
+ const previousButtonName = functions.find(func => {
83
+ if (func.rhs.includes('skipNextButton')) {
84
+ const skipNextButtonIndex = func.rhs.indexOf('skipNextButton');
85
+ const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton');
86
+ if (skipPreviousButtonIndex > skipNextButtonIndex) {
87
+ return true;
88
+ }
89
+ }
90
+ }).left.split('.')[1];
91
+
92
+ const nextButtonName = functions.find(func => {
93
+ if (func.rhs.includes('skipPreviousButton')) {
94
+ const skipNextButtonIndex = func.rhs.indexOf('skipNextButton');
95
+ const skipPreviousButtonIndex = func.rhs.indexOf('skipPreviousButton');
96
+ if (skipNextButtonIndex > skipPreviousButtonIndex) {
97
+ return true;
98
+ }
99
+ }
100
+ }).left.split('.')[1];
101
+
102
+ const engagementActionButton = functions.find(func => func.rhs.includes('props.data.engagementActions')).left.split('.')[1];
103
+
104
+ if (engagementActionButton && configRead('enableSpeedControlsButton')) {
105
+ const origEngagementActionButton = inst[engagementActionButton];
106
+ inst[engagementActionButton] = function () {
107
+ const res = origEngagementActionButton.apply(this, arguments);
108
+ res.find(item => item.type === 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED') || res.push({
109
+ type: 'TRANSPORT_CONTROLS_BUTTON_TYPE_SPEED',
110
+ button: {
111
+ buttonRenderer: ButtonRenderer(
112
+ false,
113
+ t('player.playbackSpeed.button'),
114
+ 'SLOW_MOTION_VIDEO',
115
+ {
116
+ customAction:
117
+ {
118
+ action: 'TT_SPEED_SETTINGS_SHOW',
119
+ }
120
+ }
121
+ )
122
+ }
123
+ });
124
+ return res;
125
+ }
126
+ }
127
+
128
+ if (!configRead('enableSuperThanksButton')) {
129
+ const origEngagementActionButton = inst[engagementActionButton];
130
+ inst[engagementActionButton] = function () {
131
+ const res = origEngagementActionButton.apply(this, arguments);
132
+ const superThanksFiltered = res.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SUPER_THANKS');
133
+ const shoppingFiltered = superThanksFiltered.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_SHOPPING');
134
+ return shoppingFiltered;
135
+ }
136
+ }
137
+
138
+ if (!configRead('enableAIAskButton')) {
139
+ const origEngagementActionButton = inst[engagementActionButton];
140
+ inst[engagementActionButton] = function () {
141
+ const res = origEngagementActionButton.apply(this, arguments);
142
+ const superThanksFiltered = res.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_YOUCHAT_BUTTON');
143
+ const shoppingFiltered = superThanksFiltered.filter(item => item.type !== 'TRANSPORT_CONTROLS_BUTTON_TYPE_YOUCHAT_BUTTON');
144
+ return shoppingFiltered;
145
+ }
146
+ }
147
+
148
+ if (configRead('enablePreviousNextButtons')) {
149
+ if (!previousButtonName || !nextButtonName) return inst;
150
+ inst[previousButtonName] = function () {
151
+ return ButtonRenderer(
152
+ false,
153
+ t('player.previous'),
154
+ 'SKIP_PREVIOUS',
155
+ {
156
+ signalAction: {
157
+ signal: 'PLAYER_PLAY_PREVIOUS'
158
+ }
159
+ }
160
+ )
161
+ }
162
+
163
+ inst[nextButtonName] = function () {
164
+ return ButtonRenderer(
165
+ false,
166
+ t('player.next'),
167
+ 'SKIP_NEXT',
168
+ {
169
+ signalAction: {
170
+ signal: 'PLAYER_PLAY_NEXT'
171
+ }
172
+ }
173
+ )
174
+ }
175
+
176
+ }
177
+
178
+ return inst;
179
+ }
180
+
181
+ if (configRead('enablePatchingVideoPlayer')) {
182
+ YtlrPlayerActionsContainer.prototype = origMethod.prototype;
183
+ window._yttv[methods[0]] = YtlrPlayerActionsContainer;
184
+ }
185
+ }
186
+
187
+
188
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
189
+ applyPatches();
190
+ } else {
191
+ window.addEventListener('DOMContentLoaded', applyPatches);
192
+ }
@@ -0,0 +1,29 @@
1
+ import { SettingActionRenderer, SettingsCategory } from './ytUI.js';
2
+ import { t } from 'i18next';
3
+
4
+ function PatchSettings(settingsObject) {
5
+ const tizentubeOpenAction = SettingActionRenderer(
6
+ t('settings.ttSettings.title'),
7
+ 'tizentube_open_action',
8
+ {
9
+ customAction: {
10
+ action: 'TT_SETTINGS_SHOW',
11
+ parameters: []
12
+ }
13
+ },
14
+ t('settings.ttSettings.summary'),
15
+ 'https://www.gstatic.com/ytlr/img/parent_code.png'
16
+ )
17
+
18
+ const tizenTubeCategory = SettingsCategory(
19
+ 'tizentube_category',
20
+ [tizentubeOpenAction]
21
+ );
22
+ // Add it as the first item in the settings object
23
+ settingsObject.items.unshift(tizenTubeCategory);
24
+
25
+ }
26
+
27
+ export {
28
+ PatchSettings
29
+ }
@@ -0,0 +1,48 @@
1
+ import { configChangeEmitter, configRead } from '../config.js';
2
+
3
+ configChangeEmitter.addEventListener('configChange', (event) => {
4
+ const { key, value } = event.detail;
5
+ if (key === 'enableWhoIsWatchingMenu') {
6
+ disableWhosWatching(value);
7
+ }
8
+ });
9
+
10
+ let interval;
11
+
12
+ function disableWhosWatching(value) {
13
+ const LeanbackRecurringActions = JSON.parse(localStorage['yt.leanback.default::recurring_actions']);
14
+ const shouldPermanentlyEnable = configRead('permanentlyEnableWhoIsWatchingMenu');
15
+ const date = new Date();
16
+ if (!value) {
17
+ // Setting it after 7 days should be enough, as it'll get executed every time the app launches.
18
+ date.setDate(date.getDate() + 7);
19
+ LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"] &&
20
+ (LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"].lastFired = date.getTime());
21
+ LeanbackRecurringActions.data.data.whos_watching_fullscreen_zero_accounts.lastFired = date.getTime();
22
+ LeanbackRecurringActions.data.data["startup-screen-signed-out-welcome-back"] &&
23
+ (LeanbackRecurringActions.data.data["startup-screen-signed-out-welcome-back"].lastFired = date.getTime());
24
+ localStorage['yt.leanback.default::recurring_actions'] = JSON.stringify(LeanbackRecurringActions);
25
+ } else {
26
+ // Do nothing if the last fired action is less than 2 hours ago.
27
+ if (date.getTime() - LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"]?.lastFired > 0 && date.getTime() - LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"]?.lastFired < 2 * 60 * 60 * 1000
28
+ && !shouldPermanentlyEnable) {
29
+ return;
30
+ }
31
+ function setActions() {
32
+ LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"] &&
33
+ (LeanbackRecurringActions.data.data["startup-screen-account-selector-with-guest"].lastFired = date.getTime());
34
+ LeanbackRecurringActions.data.data.whos_watching_fullscreen_zero_accounts.lastFired = date.getTime();
35
+ LeanbackRecurringActions.data.data["startup-screen-signed-out-welcome-back"] &&
36
+ (LeanbackRecurringActions.data.data["startup-screen-signed-out-welcome-back"].lastFired = date.getTime());
37
+ localStorage['yt.leanback.default::recurring_actions'] = JSON.stringify(LeanbackRecurringActions);
38
+ }
39
+ setActions();
40
+ if (shouldPermanentlyEnable) {
41
+ date.setDate(date.getDate() - 7);
42
+ setActions();
43
+ interval = setInterval(setActions, 60 * 1000);
44
+ } else if (interval) clearInterval(interval);
45
+ }
46
+ }
47
+
48
+ disableWhosWatching(configRead('enableWhoIsWatchingMenu'));