} wrapperMap
1029 | * A DefaultWeakMap object which will create the appropriate wrapper
1030 | * for a given listener function when one does not exist, and retrieve
1031 | * an existing one when it does.
1032 | *
1033 | * @returns {object}
1034 | */
1035 |
1036 |
1037 | const wrapEvent = wrapperMap => ({
1038 | addListener(target, listener, ...args) {
1039 | target.addListener(wrapperMap.get(listener), ...args);
1040 | },
1041 |
1042 | hasListener(target, listener) {
1043 | return target.hasListener(wrapperMap.get(listener));
1044 | },
1045 |
1046 | removeListener(target, listener) {
1047 | target.removeListener(wrapperMap.get(listener));
1048 | }
1049 |
1050 | });
1051 |
1052 | const onRequestFinishedWrappers = new DefaultWeakMap(listener => {
1053 | if (typeof listener !== "function") {
1054 | return listener;
1055 | }
1056 | /**
1057 | * Wraps an onRequestFinished listener function so that it will return a
1058 | * `getContent()` property which returns a `Promise` rather than using a
1059 | * callback API.
1060 | *
1061 | * @param {object} req
1062 | * The HAR entry object representing the network request.
1063 | */
1064 |
1065 |
1066 | return function onRequestFinished(req) {
1067 | const wrappedReq = wrapObject(req, {}
1068 | /* wrappers */
1069 | , {
1070 | getContent: {
1071 | minArgs: 0,
1072 | maxArgs: 0
1073 | }
1074 | });
1075 | listener(wrappedReq);
1076 | };
1077 | }); // Keep track if the deprecation warning has been logged at least once.
1078 |
1079 | let loggedSendResponseDeprecationWarning = false;
1080 | const onMessageWrappers = new DefaultWeakMap(listener => {
1081 | if (typeof listener !== "function") {
1082 | return listener;
1083 | }
1084 | /**
1085 | * Wraps a message listener function so that it may send responses based on
1086 | * its return value, rather than by returning a sentinel value and calling a
1087 | * callback. If the listener function returns a Promise, the response is
1088 | * sent when the promise either resolves or rejects.
1089 | *
1090 | * @param {*} message
1091 | * The message sent by the other end of the channel.
1092 | * @param {object} sender
1093 | * Details about the sender of the message.
1094 | * @param {function(*)} sendResponse
1095 | * A callback which, when called with an arbitrary argument, sends
1096 | * that value as a response.
1097 | * @returns {boolean}
1098 | * True if the wrapped listener returned a Promise, which will later
1099 | * yield a response. False otherwise.
1100 | */
1101 |
1102 |
1103 | return function onMessage(message, sender, sendResponse) {
1104 | let didCallSendResponse = false;
1105 | let wrappedSendResponse;
1106 | let sendResponsePromise = new Promise(resolve => {
1107 | wrappedSendResponse = function (response) {
1108 | if (!loggedSendResponseDeprecationWarning) {
1109 | console.warn(SEND_RESPONSE_DEPRECATION_WARNING, new Error().stack);
1110 | loggedSendResponseDeprecationWarning = true;
1111 | }
1112 |
1113 | didCallSendResponse = true;
1114 | resolve(response);
1115 | };
1116 | });
1117 | let result;
1118 |
1119 | try {
1120 | result = listener(message, sender, wrappedSendResponse);
1121 | } catch (err) {
1122 | result = Promise.reject(err);
1123 | }
1124 |
1125 | const isResultThenable = result !== true && isThenable(result); // If the listener didn't returned true or a Promise, or called
1126 | // wrappedSendResponse synchronously, we can exit earlier
1127 | // because there will be no response sent from this listener.
1128 |
1129 | if (result !== true && !isResultThenable && !didCallSendResponse) {
1130 | return false;
1131 | } // A small helper to send the message if the promise resolves
1132 | // and an error if the promise rejects (a wrapped sendMessage has
1133 | // to translate the message into a resolved promise or a rejected
1134 | // promise).
1135 |
1136 |
1137 | const sendPromisedResult = promise => {
1138 | promise.then(msg => {
1139 | // send the message value.
1140 | sendResponse(msg);
1141 | }, error => {
1142 | // Send a JSON representation of the error if the rejected value
1143 | // is an instance of error, or the object itself otherwise.
1144 | let message;
1145 |
1146 | if (error && (error instanceof Error || typeof error.message === "string")) {
1147 | message = error.message;
1148 | } else {
1149 | message = "An unexpected error occurred";
1150 | }
1151 |
1152 | sendResponse({
1153 | __mozWebExtensionPolyfillReject__: true,
1154 | message
1155 | });
1156 | }).catch(err => {
1157 | // Print an error on the console if unable to send the response.
1158 | console.error("Failed to send onMessage rejected reply", err);
1159 | });
1160 | }; // If the listener returned a Promise, send the resolved value as a
1161 | // result, otherwise wait the promise related to the wrappedSendResponse
1162 | // callback to resolve and send it as a response.
1163 |
1164 |
1165 | if (isResultThenable) {
1166 | sendPromisedResult(result);
1167 | } else {
1168 | sendPromisedResult(sendResponsePromise);
1169 | } // Let Chrome know that the listener is replying.
1170 |
1171 |
1172 | return true;
1173 | };
1174 | });
1175 |
1176 | const wrappedSendMessageCallback = ({
1177 | reject,
1178 | resolve
1179 | }, reply) => {
1180 | if (extensionAPIs.runtime.lastError) {
1181 | // Detect when none of the listeners replied to the sendMessage call and resolve
1182 | // the promise to undefined as in Firefox.
1183 | // See https://github.com/mozilla/webextension-polyfill/issues/130
1184 | if (extensionAPIs.runtime.lastError.message === CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE) {
1185 | resolve();
1186 | } else {
1187 | reject(new Error(extensionAPIs.runtime.lastError.message));
1188 | }
1189 | } else if (reply && reply.__mozWebExtensionPolyfillReject__) {
1190 | // Convert back the JSON representation of the error into
1191 | // an Error instance.
1192 | reject(new Error(reply.message));
1193 | } else {
1194 | resolve(reply);
1195 | }
1196 | };
1197 |
1198 | const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
1199 | if (args.length < metadata.minArgs) {
1200 | throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
1201 | }
1202 |
1203 | if (args.length > metadata.maxArgs) {
1204 | throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
1205 | }
1206 |
1207 | return new Promise((resolve, reject) => {
1208 | const wrappedCb = wrappedSendMessageCallback.bind(null, {
1209 | resolve,
1210 | reject
1211 | });
1212 | args.push(wrappedCb);
1213 | apiNamespaceObj.sendMessage(...args);
1214 | });
1215 | };
1216 |
1217 | const staticWrappers = {
1218 | devtools: {
1219 | network: {
1220 | onRequestFinished: wrapEvent(onRequestFinishedWrappers)
1221 | }
1222 | },
1223 | runtime: {
1224 | onMessage: wrapEvent(onMessageWrappers),
1225 | onMessageExternal: wrapEvent(onMessageWrappers),
1226 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1227 | minArgs: 1,
1228 | maxArgs: 3
1229 | })
1230 | },
1231 | tabs: {
1232 | sendMessage: wrappedSendMessage.bind(null, "sendMessage", {
1233 | minArgs: 2,
1234 | maxArgs: 3
1235 | })
1236 | }
1237 | };
1238 | const settingMetadata = {
1239 | clear: {
1240 | minArgs: 1,
1241 | maxArgs: 1
1242 | },
1243 | get: {
1244 | minArgs: 1,
1245 | maxArgs: 1
1246 | },
1247 | set: {
1248 | minArgs: 1,
1249 | maxArgs: 1
1250 | }
1251 | };
1252 | apiMetadata.privacy = {
1253 | network: {
1254 | "*": settingMetadata
1255 | },
1256 | services: {
1257 | "*": settingMetadata
1258 | },
1259 | websites: {
1260 | "*": settingMetadata
1261 | }
1262 | };
1263 | return wrapObject(extensionAPIs, staticWrappers, apiMetadata);
1264 | };
1265 |
1266 | if (typeof chrome != "object" || !chrome || !chrome.runtime || !chrome.runtime.id) {
1267 | throw new Error("This script should only be loaded in a browser extension.");
1268 | } // The build process adds a UMD wrapper around this file, which makes the
1269 | // `module` variable available.
1270 |
1271 |
1272 | module.exports = wrapAPIs(chrome);
1273 | } else {
1274 | module.exports = browser;
1275 | }
1276 | });
1277 | //# sourceMappingURL=browser-polyfill.js.map
1278 |
--------------------------------------------------------------------------------
/src/ch_manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Zen mode for YouTube",
3 | "manifest_version": 3,
4 | "name": "ZYT",
5 | "version": "1.1.5",
6 | "author": "surajsharma",
7 | "homepage_url": "https://evenzero.in/experiments",
8 | "icons": {
9 | "144": "icons/favicon4_144.png"
10 | },
11 |
12 | "background": {
13 | "service_worker": "background.js"
14 | },
15 |
16 | "content_scripts": [
17 | {
18 | "matches": ["*://*.youtube.com/*"],
19 | "js": ["browser-polyfill.js", "defaultSettings.js", "contentscript.js", "emoji.js"],
20 | "run_at": "document_start"
21 | }
22 | ],
23 |
24 | "action": {
25 | "default_icon": "icons/favicon4_144.png",
26 | "default_title": "Nirvana for YouTube",
27 | "default_popup": "options/options.html"
28 | },
29 |
30 | "permissions": ["storage", "tabs", "scripting", "activeTab"],
31 | "host_permissions": ["*://*.youtube.com/*"]
32 | }
--------------------------------------------------------------------------------
/src/contentscript.js:
--------------------------------------------------------------------------------
1 | /* This script interacts with the YT DOM
2 | It is injected into all YT tabs at install and on popup open
3 | */
4 |
5 | // TODO: check if offline
6 |
7 | if (typeof browser === "undefined") {
8 | var browser = chrome;
9 | }
10 |
11 | async function injectTransitionClass(options) {
12 | /* for every class css, add a transition by
13 | * looping over the state object and populating a css string
14 | * for each class this css string is then injected into the
15 | * page */
16 |
17 | let el = document.getElementById("zentubeTransitions");
18 |
19 | if (el) {
20 | el.parentNode.removeChild(el);
21 | }
22 |
23 | let css = "";
24 | let customStyles = document.createElement("style");
25 |
26 | if (options) {
27 | for (const page of Object.keys(options)) {
28 | for (const item of Object.keys(options[page])) {
29 | if (options[page][item].classes) {
30 | for (const c of options[page][item].classes) {
31 | css += `${c}{transition: all 0.2s;}`;
32 | }
33 | }
34 | }
35 | }
36 | customStyles.setAttribute("type", "text/css");
37 | customStyles.setAttribute("id", "zentubeTransitions");
38 | customStyles.appendChild(document.createTextNode(css));
39 | document.documentElement.appendChild(customStyles);
40 | }
41 | }
42 |
43 | async function toggleCSS(options) {
44 | /* for every css class, add appropriate opacity by
45 | * looping over the state object and populating a css string
46 | * for each class; this css string is then injected into the
47 | * page */
48 | if (!options) return;
49 | let css = "";
50 | for (const page of Object.keys(options)) {
51 | for (const item of Object.keys(options[page])) {
52 | if (options[page][item]["show"]) {
53 | if (options[page][item].classes) {
54 | for (const c of options[page][item].classes) {
55 | css += `${c}{display:none; opacity:0}`;
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | let el = document.getElementById("zentube");
63 |
64 | if (!el) {
65 | let customStyles = document.createElement("style");
66 | customStyles.setAttribute("type", "text/css");
67 | customStyles.setAttribute("id", "zentube");
68 | customStyles.appendChild(document.createTextNode(css));
69 | document.documentElement.appendChild(customStyles);
70 | }
71 | if (el) {
72 | el.textContent = css;
73 | }
74 | }
75 |
76 | async function checkStoredSettings() {
77 | /* On startup, check whether we have stored settings.
78 | If not, then store the default settings.*/
79 | const { settings } = await browser.storage.local.get();
80 | if (!settings || Object.keys(settings).length == 0)
81 | return await browser.storage.local.set({ settings: defaultSettings });
82 | }
83 |
84 | async function msgListener(request, sender) {
85 | /* popup clicks */
86 | const { settings } = JSON.parse(request);
87 | const { options } = settings;
88 | toggleCSS(options);
89 | }
90 |
91 | /**
92 | * init
93 | **/
94 |
95 | async function initializePageAction() {
96 | /* install message listener, inject transition css, toggle css */
97 | try {
98 | await browser.runtime.onMessage.addListener(msgListener);
99 | const { settings } = await browser.storage.local.get();
100 | const { options } = settings;
101 | injectTransitionClass(options);
102 | toggleCSS(options);
103 | } catch (err) {
104 | onError(err);
105 | }
106 | }
107 |
108 | (async () => {
109 | try {
110 | await checkStoredSettings();
111 | await initializePageAction();
112 | } catch (err) {
113 | onError(err);
114 | }
115 | })();
116 |
117 | /**
118 | * utils
119 | **/
120 |
121 | function onError(e) {
122 | console.error(e);
123 | }
124 |
--------------------------------------------------------------------------------
/src/defaultSettings.js:
--------------------------------------------------------------------------------
1 | //TODO: explore > live
2 | var defaultSettings = {
3 | currentPage: "Home",
4 | options: {
5 | Home: {
6 | preview: {
7 | label: "Preview-On-Hover",
8 | classes: [
9 | ".ytd-video-preview",
10 | "#mouseover-overlay",
11 | ".ytd-thumbnail-overlay-loading-preview-renderer"
12 | ],
13 | show: true
14 | },
15 | communityPosts: {
16 | label: "Latest Posts",
17 | classes: ["ytd-rich-shelf-renderer"],
18 | id: "communityPosts",
19 | show: true
20 | },
21 | adThumbs: {
22 | label: "Ad Thumbnails",
23 | classes: [".ytd-display-ad-renderer", ".ytd-ad-slot-renderer"],
24 | show: true
25 | },
26 | chipBar: {
27 | label: "Feed Filter Chip Bar",
28 | classes: [".ytd-feed-filter-chip-bar-renderer"],
29 | show: true
30 | },
31 | title: {
32 | label: "Video Title",
33 | classes: ["yt-formatted-string.style-scope.ytd-rich-grid-media"],
34 | show: true
35 | }
36 | },
37 | Video: {
38 | sidebar: {
39 | label: "Video Sidebar",
40 | classes: [".ytd-watch-next-secondary-results-renderer"],
41 | show: true
42 | },
43 | endvideos: {
44 | label: "Recommendations",
45 | classes: [
46 | ".ytp-endscreen-content",
47 | ".ytp-ce-video .ytp-ce-channel .ytp-ce-covering-overlay"
48 | ],
49 | show: true
50 | },
51 | chat: {
52 | label: "Chat",
53 | classes: ["#chat"],
54 | show: true
55 | },
56 | likes: {
57 | label: "Likes",
58 | classes: [
59 | "ytd-menu-renderer.style-scope.ytd-watch-metadata .yt-core-attributed-string"
60 | ],
61 | show: true
62 | },
63 | comments: {
64 | label: "Comments",
65 | classes: [".ytd-comments"],
66 | show: true
67 | },
68 | playlist: {
69 | label: "Playlist",
70 | classes: [".ytd-playlist-panel-renderer"],
71 | show: true
72 | },
73 | chapters: {
74 | label: "Chapters",
75 | classes: [
76 | "ytd-engagement-panel-section-list-renderer.style-scope.ytd-watch-flexy"
77 | ],
78 | show: true
79 | },
80 | title: {
81 | label: "Video Title",
82 | classes: ["yt-formatted-string.style-scope.ytd-watch-metadata"],
83 | show: true
84 | },
85 | sub_count: {
86 | label: "Subscriber Count",
87 | classes: ["yt-formatted-string.style-scope.ytd-video-owner-renderer"],
88 | show: true
89 | },
90 | description: {
91 | label: "Description Box",
92 | classes: ["ytd-text-inline-expander.style-scope.ytd-watch-metadata"],
93 | show: true
94 | },
95 | merch: {
96 | label: "Merchandise Box",
97 | classes: ["ytd-merch-shelf-renderer.style-scope.ytd-watch-flexy"],
98 | show: true
99 | },
100 | subscribe: {
101 | label: "Subscribe Button",
102 | classes: ["yt-button-shape.style-scope.ytd-subscribe-button-renderer"],
103 | show: true
104 | }
105 | },
106 | Everywhere: {
107 | metadata: {
108 | label: "Video Metadata",
109 | classes: [
110 | "span.inline-metadata-item.style-scope.ytd-video-meta-block",
111 | "yt-formatted-string.style-scope.ytd-channel-name",
112 | "yt-icon.style-scope.ytd-badge-supported-renderer",
113 | "div.badge.badge-style-type-live-now-alternate.style-scope.ytd-badge-supported-renderer",
114 | "#metadata-line",
115 | "#byline-container"
116 | ],
117 | show: true
118 | },
119 | duration: {
120 | label: "Video Duration",
121 | classes: [
122 | ".ytd-thumbnail-overlay-time-status-renderer",
123 | "ytd-thumbnail-overlay-time-status-renderer.style-scope.ytd-thumbnail"
124 | ],
125 | show: true
126 | },
127 | thumbnails: {
128 | label: "Video Thumbnails",
129 | classes: [
130 | ".ytd-macro-markers-list-item-renderer>img",
131 | ".thumbnail-container.style-scope.ytd-notification-renderer",
132 | ".yt-core-image--loaded"
133 | ],
134 | show: true
135 | },
136 | resume: {
137 | label: "Resume Bar",
138 | classes: [
139 | ".ytd-thumbnail-overlay-resume-playback-renderer",
140 | "ytd-thumbnail-overlay-resume-playback-renderer.style-scope.ytd-thumbnail "
141 | ],
142 | show: true
143 | },
144 | logo: {
145 | label: "YouTube Logo",
146 | classes: ["#logo .ytd-topbar-logo-renderer"],
147 | show: true
148 | },
149 | channelThumb: {
150 | label: "Channel Avatar",
151 | classes: [
152 | "#avatar",
153 | "#channel-thumbnail",
154 | "tp-yt-paper-item.style-scope.ytd-guide-entry-renderer > yt-img-shadow"
155 | ],
156 | show: true
157 | }
158 | },
159 | Special: {
160 | emoji: {
161 | label: "Emoji",
162 | classes: ["small-emoji"],
163 | show: true
164 | },
165 | greyscale: {
166 | label: "Greyscale",
167 | classes: null,
168 | show: true
169 | }
170 | }
171 | }
172 | };
173 |
--------------------------------------------------------------------------------
/src/emoji.js:
--------------------------------------------------------------------------------
1 | (async () => {
2 | // TODO: tighten-up debounced scheduling
3 | // TODO: node.attributes.title match and strip
4 | // TODO: emoji in chat (img with small-emoji class)
5 |
6 | if (typeof browser === "undefined") {
7 | var browser = chrome;
8 | }
9 |
10 | const pattern =
11 | /\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F/gu;
12 |
13 | const nodesToScan = ["SPAN", "YT-FORMATTED-STRING", "TITLE", "A"];
14 | let emojishow = true,
15 | ignoreMutations = false,
16 | fullClearFirstScheduledTime = 0,
17 | fullClearTimeout = null,
18 | totalTime = 0,
19 | node,
20 | hashmap = new Map();
21 |
22 | function scheduleDebouncedFullClear(debounceTimeMs, maxDebounceTimeMs) {
23 | const scheduled = fullClearTimeout !== null;
24 |
25 | if (scheduled) {
26 | const timeDiff = Date.now() - fullClearFirstScheduledTime;
27 | const shouldBlock = timeDiff + debounceTimeMs > maxDebounceTimeMs;
28 | if (maxDebounceTimeMs && shouldBlock) return;
29 | clearTimeout(fullClearTimeout);
30 | } else {
31 | fullClearFirstScheduledTime = Date.now();
32 | }
33 |
34 | fullClearTimeout = emojishow
35 | ? setTimeout(fullClear, debounceTimeMs)
36 | : setTimeout(fullRestore, debounceTimeMs);
37 | }
38 |
39 | async function fullClear() {
40 | const start = Date.now();
41 | await toggleEmoji(document.body, true);
42 |
43 | totalTime += Date.now() - start;
44 | fullClearTimeout = null;
45 | }
46 |
47 | async function fullRestore() {
48 | const start = Date.now();
49 | await toggleEmoji(document.body, false);
50 |
51 | totalTime += Date.now() - start;
52 | fullClearTimeout = null;
53 | }
54 |
55 | async function toggleEmoji(element, remove) {
56 | if (!element) return;
57 | if (remove === undefined) return;
58 |
59 | const treeWalker = document.createTreeWalker(
60 | element,
61 | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT
62 | );
63 |
64 | var refId = 0;
65 | // index to start allocating at
66 |
67 | var refs = {};
68 | // store all refs here
69 |
70 | function mallocRef(obj) {
71 | // will return an int to wa, which is the index at which obj starts
72 | var id = refId;
73 | ++refId;
74 | refId[id] = obj;
75 | return id;
76 | }
77 |
78 | // This looks up the JS object based upon its id
79 | function lookupJsRef(id) {
80 | return refs[id];
81 | }
82 |
83 | // This cleans up the memory for the JS object (by allowing it to be garbage collected)
84 | function freeJsRef(id) {
85 | delete refs[id];
86 | }
87 |
88 | var imports = {
89 | env: {
90 | // push: function (id, value) {
91 | // lookupJsRef(id).push(value);
92 | // },
93 |
94 | restore: function (hmapId, nodeId) {
95 | let hmap = lookupJsRef(hmapId);
96 | let n = lookupJsRef(nodeId);
97 |
98 | for (let o = 0; o < Object.keys(hmap).length; o++) {
99 | const el = hmap[Object.keys(hmap)[o]];
100 | if (n.nodeValue == el.strip) {
101 | n.nodeValue = el.orig;
102 | }
103 | }
104 | },
105 |
106 | createMapRef: function () {
107 | return mallocRef(hashmap);
108 | },
109 |
110 | createNodeRef: function () {
111 | return mallocRef(node);
112 | },
113 |
114 | length: function (id) {
115 | return lookupJsRef(id).length;
116 | },
117 |
118 | logInt: function (value) {
119 | console.log(value);
120 | },
121 |
122 | logRef: function (id) {
123 | console.log(lookupJsRef(id));
124 | },
125 |
126 | free: function (id) {
127 | freeJsRef(id);
128 | }
129 | }
130 | };
131 |
132 | while ((node = treeWalker.nextNode())) {
133 | // TODO: move loop to wasm polynomial-time (currently exponential)
134 |
135 | if (nodesToScan.indexOf(node.parentElement.tagName) >= 0) {
136 | // TODO: use google/re2-wasm to match(how else?)
137 |
138 | const matches = node.nodeValue && node.nodeValue.match(pattern);
139 |
140 | if (matches && remove) {
141 | // TODO: replace string in WA
142 |
143 | let strip = node.nodeValue.replace(pattern, "");
144 |
145 | if (!strip.length) {
146 | strip = " ";
147 | }
148 |
149 | if (hashmap[node.nodeValue] === undefined) {
150 | hashmap[node.nodeValue] = {
151 | orig: node.nodeValue,
152 | strip
153 | };
154 | return;
155 | }
156 |
157 | if (hashmap[node.nodeValue] != undefined) {
158 | hashmap[node.nodeValue] = {
159 | orig: node.nodeValue,
160 | strip
161 | };
162 | }
163 |
164 | node.nodeValue = strip;
165 | }
166 |
167 | if (!emojishow) {
168 | var wasmPath = chrome.runtime.getURL("wasm/emoji.wasm");
169 | fetch(wasmPath)
170 | .then((response) => response.arrayBuffer())
171 | .then((bytes) => WebAssembly.instantiate(bytes, imports))
172 | .then((results) => {
173 | results.instance.exports.put_back();
174 | });
175 | }
176 | }
177 | }
178 | return;
179 | }
180 |
181 | async function onMutation(mutations) {
182 | if (ignoreMutations) return;
183 |
184 | const start = Date.now();
185 |
186 | for (let i = 0; i < mutations.length; i++) {
187 | const mutation = mutations[i];
188 | for (let j = 0; j < mutation.addedNodes.length; j++) {
189 | // mutated node
190 | const mnode = mutation.addedNodes[j];
191 | const el = hashmap[mnode.nodeValue];
192 |
193 | if (!el) {
194 | ignoreMutations = true;
195 | await toggleEmoji(mnode, emojishow);
196 | ignoreMutations = false;
197 | } else {
198 | const matches = mnode.nodeValue && mnode.nodeValue.match(pattern);
199 | if (matches && el) {
200 | mnode.nodeValue = emojishow ? el.strip : el.orig;
201 | }
202 | }
203 | }
204 | }
205 |
206 | totalTime += Date.now() - start;
207 | scheduleDebouncedFullClear(500, 1000);
208 | }
209 |
210 | MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
211 | let observer = new MutationObserver(onMutation);
212 |
213 | observer.observe(document, {
214 | attributes: true,
215 | childList: true,
216 | subtree: true
217 | });
218 |
219 | await browser.runtime.onMessage.addListener(async (request, sender) => {
220 | const { element } = await JSON.parse(request);
221 | const { settings } = await browser.storage.local.get();
222 | const { options } = settings;
223 | emojishow = options["Special"].emoji.show;
224 |
225 | switch (element) {
226 | case "emoji":
227 | emojishow ? fullClear() : fullRestore();
228 | break;
229 | default:
230 | break;
231 | }
232 | });
233 | })();
234 |
--------------------------------------------------------------------------------
/src/ff_manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Nirvana for YouTube",
3 | "manifest_version": 2,
4 | "name": "ZYT",
5 | "version": "1.1.5",
6 | "author": "surajsharma",
7 | "homepage_url": "https://evenzero.in/experiments",
8 | "icons": {
9 | "144": "icons/favicon4_144.png"
10 | },
11 |
12 | "background": {
13 | "scripts": ["background.js"]
14 | },
15 |
16 | "content_scripts": [
17 | {
18 | "matches": ["*://*.youtube.com/*"],
19 | "js": [
20 | "browser-polyfill.js",
21 | "defaultSettings.js",
22 | "emoji.js",
23 | "contentscript.js"
24 | ],
25 | "run_at": "document_start"
26 | }
27 | ],
28 |
29 | "browser_action": {
30 | "default_icon": "icons/favicon4_144.png",
31 | "default_title": "Nirvana for YouTube",
32 | "default_popup": "options/options.html"
33 | },
34 |
35 | "permissions": ["storage", "tabs", "scripting", "activeTab"]
36 | }
37 |
--------------------------------------------------------------------------------
/src/icons/favicon2_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon2_144.png
--------------------------------------------------------------------------------
/src/icons/favicon3_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon3_144.png
--------------------------------------------------------------------------------
/src/icons/favicon4_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon4_128.png
--------------------------------------------------------------------------------
/src/icons/favicon4_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon4_144.png
--------------------------------------------------------------------------------
/src/icons/favicon4_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon4_64.png
--------------------------------------------------------------------------------
/src/icons/favicon_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/favicon_144.png
--------------------------------------------------------------------------------
/src/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/logo.png
--------------------------------------------------------------------------------
/src/icons/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/icons/logo2.png
--------------------------------------------------------------------------------
/src/icons/off.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/icons/on.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Nirvana for YouTube",
3 | "manifest_version": 3,
4 | "name": "ZYT",
5 | "version": "1.1.5",
6 | "author": "surajsharma",
7 | "homepage_url": "https://evenzero.in/experiments",
8 | "icons": {
9 | "144": "icons/favicon4_144.png"
10 | },
11 |
12 | "background": {
13 | "service_worker": "background.js"
14 | },
15 |
16 | "content_scripts": [
17 | {
18 | "matches": ["*://*.youtube.com/*"],
19 | "js": [
20 | "browser-polyfill.js",
21 | "defaultSettings.js",
22 | "emoji.js",
23 | "contentscript.js"
24 | ],
25 | "run_at": "document_start"
26 | }
27 | ],
28 |
29 | "web_accessible_resources": [
30 | {
31 | "resources": [
32 | "wasm/emoji.wasm",
33 | "defaultSettings.js",
34 | "emoji.js",
35 | "contentscript.js"
36 | ],
37 | "matches": ["*://*.youtube.com/*"]
38 | }
39 | ],
40 |
41 | "action": {
42 | "default_icon": "icons/favicon4_144.png",
43 | "default_title": "Nirvana for YouTube",
44 | "default_popup": "options/options.html"
45 | },
46 |
47 | "permissions": ["storage", "tabs", "scripting", "activeTab"],
48 | "host_permissions": ["*://*.youtube.com/*"]
49 | }
50 |
--------------------------------------------------------------------------------
/src/options/options.css:
--------------------------------------------------------------------------------
1 | body {
2 | width: 20em;
3 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", sans-serif;
4 | font-size: 0.9em;
5 | font-weight: 300;
6 | background: rgb(17,17,17);
7 | background: linear-gradient(0deg, rgba(17, 17, 17, 1) 0%, rgba(5,5,5,1) 100%);
8 | color: white;
9 | -webkit-user-select: none important!;
10 | /* Safari */
11 | -webkit-user-drag: none important!;
12 | -ms-user-select: none important!;
13 | /* IE 10 and IE 11 */
14 | user-select: none important!;
15 | /* Standard syntax */
16 | overflow: hidden;
17 | }
18 | section.clear-options {
19 | padding: 0.5em 0;
20 | margin: 1em 0;
21 | }
22 | #clear-button {
23 | margin: 0 1.3em 1em 0;
24 | }
25 | section.clear-options input,section.clear-options > select,#clear-button {
26 | float: right;
27 | }
28 | label {
29 | display: block;
30 | padding: 0.2em 0;
31 | }
32 | label:hover {
33 | cursor: pointer;
34 | }
35 | .logo {
36 | margin-top: -10px;
37 | display: flex;
38 | justify-content: space-between;
39 | align-items: center;
40 | justify-items: center;
41 | }
42 | .icon {
43 | margin-top: -5px;
44 | }
45 | .title {
46 | font-size: 1.5rem;
47 | margin-bottom: 0.5rem;
48 | color: lightgray;
49 | font-weight: 100;
50 | background-color: #000;
51 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", sans-serif;
52 | }
53 | p {
54 | color: lightgray;
55 | }
56 |
57 | /* Switch */
58 | .button-12 {
59 | display: flex;
60 | flex-direction: column;
61 | align-items: center;
62 | padding: 6px 14px;
63 | font-family: -apple-system, BlinkMacSystemFont, "Roboto", sans-serif;
64 | border-radius: 6px;
65 | border: 1px solid rgba(250, 50, 50, 0.1);
66 | background: rgba(94, 255, 0, 0.2);
67 | box-shadow: 0px 0.5px 1px rgba(0, 0, 0, 0.5),inset 0px 0.5px 0.5px rgba(255, 255, 255, 0.5),0px 0px 0px 0.5px rgba(0, 0, 0, 0.12);
68 | color: #dfdedf;
69 | user-select: none;
70 | -webkit-user-select: none;
71 | touch-action: manipulation;
72 | }
73 | .button-12:focus {
74 | box-shadow: inset 0px 0.8px 0px -0.25px rgba(255, 255, 255, 0.2),0px 0.5px 1px rgba(0, 0, 0, 0.1),0px 0px 0px 3.5px rgba(58, 108, 217, 0.5);
75 | outline: 0;
76 | }
77 | .button-12:hover {
78 | background: rgba(250, 50, 50, 0.5);
79 | transition: 0.2s;
80 | cursor: pointer;
81 | }
82 | .toggle {
83 | display: flex;
84 | justify-content: space-between;
85 | align-items: center;
86 | /* Vertical */
87 | font-size: 1rem;
88 | padding: 2px;
89 | font-weight: 300;
90 | border-radius: 10px;
91 | }
92 | .switch input {
93 | display: none;
94 | }
95 | .switch {
96 | display: inline-block;
97 | width: 60px;
98 | /*=w*/
99 | height: 15px;
100 | /*=h*/
101 | margin: 4px;
102 | /* transform: translateY(50%);
103 | */
104 | position: relative;
105 | }
106 | .slider {
107 | position: absolute;
108 | top: 0;
109 | bottom: 0;
110 | left: 0;
111 | right: 0;
112 | border-radius: 30px;
113 | box-shadow: 0 0 0 2px #777, 0 0 4px #777;
114 | cursor: pointer;
115 | border: 4px solid transparent;
116 | overflow: hidden;
117 | transition: 0.1s;
118 | }
119 | .slider:before {
120 | position: absolute;
121 | content: "";
122 | width: 100%;
123 | height: 100%;
124 | background-color: #777;
125 | border-radius: 30px;
126 | transform: translateX(-30px);
127 | opacity: 0.85;
128 | /*translateX(-(w-h))*/
129 | transition: 0.1s;
130 | }
131 | input:checked + .slider:before {
132 | transform: translateX(30px);
133 | /*translateX(w-h)*/
134 | }
135 | .switch200 .slider:before {
136 | /* width: 200%;
137 | */
138 | transform: translateX(-30px);
139 | /*translateX(-(w-h))*/
140 | }
141 | .switch200 input:checked + .slider:before {
142 | background-color: red;
143 | }
144 | .switch200 input:checked + .slider {
145 | box-shadow: 0 0 0 2px red, 0 0 8px red;
146 | }
147 | #popup{
148 | margin-bottom: 10px;
149 | }
150 | #popup > div:nth-child(odd) {
151 | background-color: rgba(30, 30, 30, 0.1);
152 | color:gray;
153 | }
154 |
155 | /* to color even children in parent-container with children of type div */
156 | #popup > div:nth-child(even) {
157 | background-color: rgba(0, 0, 0, 0.1);
158 | margin-top: 1px;
159 | color: gray;
160 | }
161 | #popup > div > span:hover{
162 | cursor: pointer;
163 | color: red;
164 | }
165 |
166 | /* footer */
167 | .footer {
168 | height : 15px;
169 | }
170 |
171 | .footer_label {
172 | display: flex;
173 | flex-direction: column;
174 | justify-content: left;
175 | align-items: left;
176 | font-size: 0.8em;
177 | font-style: italic;
178 | line-height: 0 ;
179 | }
180 |
181 | #brand {
182 | color: #555;
183 | margin-top: -1px;
184 | }
185 |
186 | #version {
187 | color: #555;
188 | }
189 |
190 | #brand:hover {
191 | color: red;
192 | margin-top: -1px;
193 | cursor: pointer;
194 | }
195 |
196 | #version:hover {
197 | color: #3cff00;;
198 | cursor: pointer;
199 | }
200 |
201 |
202 | /* Custom Select */
203 | .custom-select {
204 | position: relative;
205 | margin-top: 5px;
206 | margin-bottom: 10px;
207 | border: 1px solid rgba(124, 11, 9, 1);
208 | border-radius:5px;
209 | }
210 |
211 | .custom-select select {
212 | display: none;
213 | /*hide original SELECT element: */
214 | }
215 |
216 | .select-selected {
217 | background-color: rgba(124, 11, 9, 0.2);
218 | border-radius:5px;
219 | }
220 |
221 | /* Style the arrow inside the select element: */
222 |
223 | .select-selected:after {
224 | position: absolute;
225 | content: "";
226 | top: 14px;
227 | right: 10px;
228 | width: 0;
229 | height: 0;
230 | border: 6px solid transparent;
231 | border-color: #fff transparent transparent transparent;
232 | }
233 |
234 | /* Point the arrow upwards when the select box is open (active): */
235 |
236 | .select-selected.select-arrow-active:after {
237 | border-color: transparent transparent #fff transparent;
238 | top: 7px;
239 | }
240 |
241 | /* style the items (options), including the selected item: */
242 |
243 | .select-items div,.select-selected {
244 | color: #ffffff;
245 | padding: 8px 16px;
246 | /* border-color: transparent transparent rgba(0, 0, 0, 0.1) transparent;
247 | */
248 | cursor: pointer;
249 | }
250 |
251 | /* Style items (options): */
252 |
253 | .select-items {
254 | position: absolute;
255 | background-color: rgba(124, 11, 9, 1);
256 | top: 100%;
257 | left: 0;
258 | right: 0;
259 | z-index: 99;
260 | border-bottom-right-radius: 5px;
261 | border-bottom-left-radius:5px;
262 | }
263 |
264 | /* Hide the items when the select box is closed: */
265 |
266 | .select-hide {
267 | display: none;
268 | }
269 |
270 | .select-items div:hover, .same-as-selected {
271 | background-color: rgba(0, 0, 0, 0.2);
272 | }
273 |
274 | .unselectable{
275 | -moz-user-select: none;
276 | -webkit-user-select: none;
277 | -ms-user-select:none;
278 | user-select:none;
279 | -o-user-select:none;
280 | }
281 |
--------------------------------------------------------------------------------
/src/options/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | NIRT
9 |
10 |
11 |
12 |
13 |
18 |
19 |
20 |

21 |
ZYT
22 |
23 |
24 |
25 |
32 |
33 |
34 |
37 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/options/options.js:
--------------------------------------------------------------------------------
1 | /* Popup handlers */
2 | async function updateUI(restoredSettings) {
3 | /* Update the options UI with the settings values retrieved from storage,
4 | or the default settings if the stored settings are empty. */
5 |
6 | if (!Object.keys(restoredSettings).length) {
7 | // there's nothing in the local storage
8 | // create default popup and store default settings
9 | await browser.storage.local.set({ settings: defaultSettings });
10 | const { options, currentPage } = defaultSettings;
11 | repopulatePopup(options, currentPage);
12 | setDropdownSelect(currentPage);
13 | }
14 |
15 | // set UI according to local storage
16 | const { options, currentPage } = restoredSettings;
17 | repopulatePopup(options, currentPage);
18 | setDropdownSelect(currentPage);
19 | }
20 |
21 | function repopulatePopup(options, cp) {
22 | if (!options) return;
23 |
24 | const popup = document.getElementById("popup");
25 |
26 | //clear old fields
27 | while (popup.firstChild) {
28 | popup.removeChild(popup.lastChild);
29 | }
30 |
31 | //add new fields
32 | for (page of Object.keys(options)) {
33 | if (page === cp) {
34 | Object.keys(options[page]).forEach((item) => {
35 | // insert toggle field
36 | const togg = options[page][item];
37 |
38 | const field = togg.show
39 | ? `
40 | ${togg.label}
41 |
45 |
`
46 | : `
47 | ${togg.label}
48 |
52 |
`;
53 |
54 | //TODO: web-ext warns of potential security risk, find another way
55 | popup.insertAdjacentHTML("afterbegin", field);
56 |
57 | //add event listener
58 | const el = document.getElementById(item);
59 |
60 | el &&
61 | el.addEventListener("click", async (evt) => {
62 | messagePageScript({
63 | element: item,
64 | event: evt,
65 | settings: await storeChangedSettings(item)
66 | });
67 | });
68 |
69 | const te = document.getElementById(page + item);
70 |
71 | te &&
72 | te.addEventListener("click", async (evt) => {
73 | const e = document.getElementById(item);
74 | e && e.click();
75 |
76 | messagePageScript({
77 | element: item,
78 | event: evt,
79 | settings: await storeChangedSettings(item)
80 | });
81 | });
82 | });
83 | }
84 | }
85 | }
86 |
87 | function setDropdownSelect(page) {
88 | if (!page) return;
89 | document.querySelector(".select-selected").innerText = page;
90 | }
91 |
92 | async function storeChangedSettings(changed) {
93 | /*fires when a toggle is clicked, syncs local storage */
94 |
95 | let { settings } = await browser.storage.local.get();
96 |
97 | const { currentPage } = settings;
98 |
99 | function getChangedOptions() {
100 | let changedOptions = settings.options;
101 |
102 | const checkboxes = document.querySelectorAll(".data-types [type=checkbox]");
103 |
104 | if (!checkboxes) return;
105 |
106 | for (let item of checkboxes) {
107 | if (item.id === changed) {
108 | changedOptions[currentPage][changed]["show"] = item.checked;
109 | }
110 | }
111 |
112 | return changedOptions;
113 | }
114 |
115 | const newOptions = getChangedOptions();
116 |
117 | settings.options = newOptions;
118 | await browser.storage.local.set({ settings });
119 | return settings;
120 | }
121 |
122 | async function selectionChanged(value) {
123 | /* called in select.js, fired when select dropdown changes,
124 | syncs selected value to local storage
125 | */
126 |
127 | if (value == "Select Page") return;
128 | let { settings } = await browser.storage.local.get();
129 |
130 | if (value != settings.currentPage) {
131 | let dropdown = document.getElementById("dropdown");
132 |
133 | if (settings) {
134 | for (let i = 0; i < dropdown.length; i++) {
135 | if (dropdown[i].innerText == value) {
136 | settings.currentPage = value;
137 | await browser.storage.local.set({ settings });
138 | }
139 | }
140 | updateUI(settings);
141 | }
142 | }
143 | }
144 |
145 | /* Content Script handlers */
146 | async function sendMessageToTabs(tabs, msg) {
147 | for (const tab of tabs) {
148 | try {
149 | await browser.tabs.sendMessage(tab.id, JSON.stringify(msg));
150 | console.log(`🪛 ${msg.element} sent to ${tab.id}`);
151 | } catch (err) {
152 | onError(err);
153 | }
154 | }
155 | return true;
156 | }
157 |
158 | async function messagePageScript(msg) {
159 | /*Find all tabs, send a message to the page script.*/
160 | try {
161 | let tabs = await browser.tabs.query({ url: "*://*.youtube.com/*" });
162 | let res = await sendMessageToTabs(tabs, msg);
163 | } catch (err) {
164 | onError(err);
165 | }
166 | }
167 |
168 | async function injectScript() {
169 | /* inject content script into all yt tabs*/
170 | try {
171 | let tabs = await browser.tabs.query({ url: "*://*.youtube.com/*" });
172 | for await (const t of tabs) {
173 | const injection = await browser.scripting.executeScript({
174 | target: { tabId: t.id },
175 | files: ["defaultSettings.js", "emoji.js", "contentscript.js"]
176 | });
177 | }
178 | } catch (error) {
179 | onError(error);
180 | }
181 | return true;
182 | }
183 |
184 | (async () => {
185 | /* On opening the options page, fetch stored settings and update the UI with them.*/
186 |
187 | try {
188 | /* ip link */
189 | let icon = document.getElementById("brand");
190 | icon.addEventListener("click", () => {
191 | browser.tabs.create({ active: true, url: "https://ko-fi.com/evenzero" });
192 | });
193 |
194 | /* version display*/
195 | let ver = document.getElementById("version");
196 | ver.innerText = "Ver: " + browser.runtime.getManifest().version;
197 |
198 | /* version link*/
199 | ver.addEventListener("click", () => {
200 | browser.tabs.create({
201 | active: true,
202 | url: "https://github.com/inversepolarity/ZenYT"
203 | });
204 | });
205 |
206 | const { settings } = await browser.storage.local.get();
207 |
208 | if (settings) {
209 | updateUI(settings);
210 | }
211 |
212 | /* inject contentscript */
213 | await injectScript();
214 | } catch (err) {
215 | onError(err);
216 | }
217 | })();
218 |
219 | function onError(e) {
220 | console.error(e);
221 | }
222 |
--------------------------------------------------------------------------------
/src/options/select.js:
--------------------------------------------------------------------------------
1 | let x, i, j, l, ll, selElmnt, a, b, c;
2 | /* Look for any elements with the class "custom-select": */
3 | x = document.getElementsByClassName("custom-select");
4 | l = x.length;
5 | for (i = 0; i < l; i++) {
6 | selElmnt = x[i].getElementsByTagName("select")[0];
7 | ll = selElmnt.length;
8 | /* For each element, create a new DIV that will act as the selected item: */
9 | a = document.createElement("DIV");
10 | a.setAttribute("class", "select-selected");
11 | a.innerText = selElmnt.options[selElmnt.selectedIndex].innerText;
12 | x[i].appendChild(a);
13 | /* For each element, create a new DIV that will contain the option list: */
14 | b = document.createElement("DIV");
15 | b.setAttribute("class", "select-items select-hide");
16 | for (j = 1; j < ll; j++) {
17 | /* For each option in the original select element,
18 | create a new DIV that will act as an option item: */
19 | c = document.createElement("DIV");
20 | c.innerText = selElmnt.options[j].innerText;
21 | c.addEventListener("click", function (e) {
22 | /* When an item is clicked, update the original select box,
23 | and the selected item: */
24 | let y, i, k, s, h, sl, yl;
25 | s = this.parentNode.parentNode.getElementsByTagName("select")[0];
26 | sl = s.length;
27 | h = this.parentNode.previousSibling;
28 | for (i = 0; i < sl; i++) {
29 | if (s.options[i].innerText == this.innerText) {
30 | s.selectedIndex = i;
31 | h.innerText = this.innerText;
32 | y = this.parentNode.getElementsByClassName("same-as-selected");
33 | yl = y.length;
34 | for (k = 0; k < yl; k++) {
35 | y[k].removeAttribute("class");
36 | }
37 | this.setAttribute("class", "same-as-selected");
38 | break;
39 | }
40 | }
41 | h.click();
42 | });
43 | b.appendChild(c);
44 | }
45 | x[i].appendChild(b);
46 | a.addEventListener("click", function (e) {
47 | /* When the select box is clicked, close any other select boxes,
48 | and open/close the current select box: */
49 | e.stopPropagation();
50 | closeAllSelect(this);
51 | this.nextSibling.classList.toggle("select-hide");
52 | this.classList.toggle("select-arrow-active");
53 | selectionChanged(e.target.innerText);
54 | });
55 | }
56 |
57 | function closeAllSelect(elmnt) {
58 | /* A function that will close all select boxes in the document,
59 | except the current select box: */
60 | let x,
61 | y,
62 | i,
63 | xl,
64 | yl,
65 | arrNo = [];
66 | x = document.getElementsByClassName("select-items");
67 | y = document.getElementsByClassName("select-selected");
68 | xl = x.length;
69 | yl = y.length;
70 | for (i = 0; i < yl; i++) {
71 | if (elmnt == y[i]) {
72 | arrNo.push(i);
73 | } else {
74 | y[i].classList.remove("select-arrow-active");
75 | }
76 | }
77 | for (i = 0; i < xl; i++) {
78 | if (arrNo.indexOf(i)) {
79 | x[i].classList.add("select-hide");
80 | }
81 | }
82 | }
83 |
84 | /* If the user clicks anywhere outside the select box,
85 | then close all select boxes: */
86 | document.addEventListener("click", closeAllSelect);
87 |
--------------------------------------------------------------------------------
/src/promo/ZYT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/promo/ZYT.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "1024x1024",
5 | "idiom" : "universal",
6 | "filename" : "universal-icon-1024@1x.png",
7 | "platform" : "ios"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "mac-icon-16@1x.png",
13 | "scale" : "1x"
14 | },
15 | {
16 | "size" : "16x16",
17 | "idiom" : "mac",
18 | "filename" : "mac-icon-16@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "mac-icon-32@1x.png",
25 | "scale" : "1x"
26 | },
27 | {
28 | "size" : "32x32",
29 | "idiom" : "mac",
30 | "filename" : "mac-icon-32@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "mac-icon-128@1x.png",
37 | "scale" : "1x"
38 | },
39 | {
40 | "size" : "128x128",
41 | "idiom" : "mac",
42 | "filename" : "mac-icon-128@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "mac-icon-256@1x.png",
49 | "scale" : "1x"
50 | },
51 | {
52 | "size" : "256x256",
53 | "idiom" : "mac",
54 | "filename" : "mac-icon-256@2x.png",
55 | "scale" : "2x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "mac-icon-512@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "512x512",
65 | "idiom" : "mac",
66 | "filename" : "mac-icon-512@2x.png",
67 | "scale" : "2x"
68 | }
69 | ],
70 | "info" : {
71 | "version" : 1,
72 | "author" : "xcode"
73 | }
74 | }
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "favicon4_144.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/favicon4_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/favicon4_144.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Base.lproj/Main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | You can turn on ZenTube’s Safari extension in Settings.
15 | You can turn on ZenTube’s extension in Safari Extensions preferences.
16 | ZenTube’s extension is currently on. You can turn it off in Safari Extensions preferences.
17 | ZenTube’s extension is currently off. You can turn it on in Safari Extensions preferences.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/Shared (App)/Resources/Icon.png
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Resources/Script.js:
--------------------------------------------------------------------------------
1 | function show(platform, enabled, useSettingsInsteadOfPreferences) {
2 | document.body.classList.add(`platform-${platform}`);
3 |
4 | if (useSettingsInsteadOfPreferences) {
5 | document.getElementsByClassName('platform-mac state-on')[0].innerText = "ZenTube’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
6 | document.getElementsByClassName('platform-mac state-off')[0].innerText = "ZenTube’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
7 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on ZenTube’s extension in the Extensions section of Safari Settings.";
8 | document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
9 | }
10 |
11 | if (typeof enabled === "boolean") {
12 | document.body.classList.toggle(`state-on`, enabled);
13 | document.body.classList.toggle(`state-off`, !enabled);
14 | } else {
15 | document.body.classList.remove(`state-on`);
16 | document.body.classList.remove(`state-off`);
17 | }
18 | }
19 |
20 | function openPreferences() {
21 | webkit.messageHandlers.controller.postMessage("open-preferences");
22 | }
23 |
24 | document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
25 |
--------------------------------------------------------------------------------
/src/safari/Shared (App)/Resources/Style.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-user-select: none;
3 | -webkit-user-drag: none;
4 | cursor: default;
5 | }
6 |
7 | :root {
8 | color-scheme: light dark;
9 |
10 | --spacing: 20px;
11 | }
12 |
13 | html {
14 | height: 100%;
15 | }
16 |
17 | body {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | flex-direction: column;
22 |
23 | gap: var(--spacing);
24 | margin: 0 calc(var(--spacing) * 2);
25 | height: 100%;
26 |
27 | font: -apple-system-short-body;
28 | text-align: center;
29 | }
30 |
31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
32 | display: none;
33 | }
34 |
35 | body.platform-ios .platform-mac {
36 | display: none;
37 | }
38 |
39 | body.platform-mac .platform-ios {
40 | display: none;
41 | }
42 |
43 | body.platform-ios .platform-mac {
44 | display: none;
45 | }
46 |
47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) {
48 | display: none;
49 | }
50 |
51 | body.state-on :is(.state-off, .state-unknown) {
52 | display: none;
53 | }
54 |
55 | body.state-off :is(.state-on, .state-unknown) {
56 | display: none;
57 | }
58 |
59 | button {
60 | font-size: 1em;
61 | }
62 |
--------------------------------------------------------------------------------
/src/safari/Shared (App)/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Shared (App)
4 | //
5 | // Created by admin on 28/11/22.
6 | //
7 |
8 | import WebKit
9 |
10 | #if os(iOS)
11 | import UIKit
12 | typealias PlatformViewController = UIViewController
13 | #elseif os(macOS)
14 | import Cocoa
15 | import SafariServices
16 | typealias PlatformViewController = NSViewController
17 | #endif
18 |
19 | let extensionBundleIdentifier = "in.evenzero.ZenTube.Extension"
20 |
21 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler {
22 |
23 | @IBOutlet var webView: WKWebView!
24 |
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | self.webView.navigationDelegate = self
29 |
30 | #if os(iOS)
31 | self.webView.scrollView.isScrollEnabled = false
32 | #endif
33 |
34 | self.webView.configuration.userContentController.add(self, name: "controller")
35 |
36 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
37 | }
38 |
39 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
40 | #if os(iOS)
41 | webView.evaluateJavaScript("show('ios')")
42 | #elseif os(macOS)
43 | webView.evaluateJavaScript("show('mac')")
44 |
45 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
46 | guard let state = state, error == nil else {
47 | // Insert code to inform the user that something went wrong.
48 | return
49 | }
50 |
51 | DispatchQueue.main.async {
52 | if #available(macOS 13, *) {
53 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)")
54 | } else {
55 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)")
56 | }
57 | }
58 | }
59 | #endif
60 | }
61 |
62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
63 | #if os(macOS)
64 | if (message.body as! String != "open-preferences") {
65 | return;
66 | }
67 |
68 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
69 | guard error == nil else {
70 | // Insert code to inform the user that something went wrong.
71 | return
72 | }
73 |
74 | DispatchQueue.main.async {
75 | NSApplication.shared.terminate(nil)
76 | }
77 | }
78 | #endif
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/safari/Shared (Extension)/SafariWebExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariWebExtensionHandler.swift
3 | // Shared (Extension)
4 | //
5 | // Created by admin on 28/11/22.
6 | //
7 |
8 | import SafariServices
9 | import os.log
10 |
11 | let SFExtensionMessageKey = "message"
12 |
13 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
14 |
15 | func beginRequest(with context: NSExtensionContext) {
16 | let item = context.inputItems[0] as! NSExtensionItem
17 | let message = item.userInfo?[SFExtensionMessageKey]
18 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
19 |
20 | let response = NSExtensionItem()
21 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
22 |
23 | context.completeRequest(returningItems: [response], completionHandler: nil)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/safari/ZenTube.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/safari/ZenTube.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/safari/ZenTube.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/src/safari/ZenTube.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ZenTube (iOS).xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 1
11 |
12 | ZenTube (macOS).xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "1024x1024",
5 | "idiom" : "universal",
6 | "filename" : "universal-icon-1024@1x.png",
7 | "platform" : "ios"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "mac-icon-16@1x.png",
13 | "scale" : "1x"
14 | },
15 | {
16 | "size" : "16x16",
17 | "idiom" : "mac",
18 | "filename" : "mac-icon-16@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "mac-icon-32@1x.png",
25 | "scale" : "1x"
26 | },
27 | {
28 | "size" : "32x32",
29 | "idiom" : "mac",
30 | "filename" : "mac-icon-32@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "mac-icon-128@1x.png",
37 | "scale" : "1x"
38 | },
39 | {
40 | "size" : "128x128",
41 | "idiom" : "mac",
42 | "filename" : "mac-icon-128@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "mac-icon-256@1x.png",
49 | "scale" : "1x"
50 | },
51 | {
52 | "size" : "256x256",
53 | "idiom" : "mac",
54 | "filename" : "mac-icon-256@2x.png",
55 | "scale" : "2x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "mac-icon-512@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "512x512",
65 | "idiom" : "mac",
66 | "filename" : "mac-icon-512@2x.png",
67 | "scale" : "2x"
68 | }
69 | ],
70 | "info" : {
71 | "version" : 1,
72 | "author" : "xcode"
73 | }
74 | }
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "favicon4_144.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Assets.xcassets/LargeIcon.imageset/favicon4_144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Assets.xcassets/LargeIcon.imageset/favicon4_144.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Base.lproj/Main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | You can turn on ZenTube’s Safari extension in Settings.
15 | You can turn on ZenTube’s extension in Safari Extensions preferences.
16 | ZenTube’s extension is currently on. You can turn it off in Safari Extensions preferences.
17 | ZenTube’s extension is currently off. You can turn it on in Safari Extensions preferences.
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/Shared (App)/Resources/Icon.png
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Resources/Script.js:
--------------------------------------------------------------------------------
1 | function show(platform, enabled, useSettingsInsteadOfPreferences) {
2 | document.body.classList.add(`platform-${platform}`);
3 |
4 | if (useSettingsInsteadOfPreferences) {
5 | document.getElementsByClassName('platform-mac state-on')[0].innerText = "ZenTube’s extension is currently on. You can turn it off in the Extensions section of Safari Settings.";
6 | document.getElementsByClassName('platform-mac state-off')[0].innerText = "ZenTube’s extension is currently off. You can turn it on in the Extensions section of Safari Settings.";
7 | document.getElementsByClassName('platform-mac state-unknown')[0].innerText = "You can turn on ZenTube’s extension in the Extensions section of Safari Settings.";
8 | document.getElementsByClassName('platform-mac open-preferences')[0].innerText = "Quit and Open Safari Settings…";
9 | }
10 |
11 | if (typeof enabled === "boolean") {
12 | document.body.classList.toggle(`state-on`, enabled);
13 | document.body.classList.toggle(`state-off`, !enabled);
14 | } else {
15 | document.body.classList.remove(`state-on`);
16 | document.body.classList.remove(`state-off`);
17 | }
18 | }
19 |
20 | function openPreferences() {
21 | webkit.messageHandlers.controller.postMessage("open-preferences");
22 | }
23 |
24 | document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
25 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/Resources/Style.css:
--------------------------------------------------------------------------------
1 | * {
2 | -webkit-user-select: none;
3 | -webkit-user-drag: none;
4 | cursor: default;
5 | }
6 |
7 | :root {
8 | color-scheme: light dark;
9 |
10 | --spacing: 20px;
11 | }
12 |
13 | html {
14 | height: 100%;
15 | }
16 |
17 | body {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | flex-direction: column;
22 |
23 | gap: var(--spacing);
24 | margin: 0 calc(var(--spacing) * 2);
25 | height: 100%;
26 |
27 | font: -apple-system-short-body;
28 | text-align: center;
29 | }
30 |
31 | body:not(.platform-mac, .platform-ios) :is(.platform-mac, .platform-ios) {
32 | display: none;
33 | }
34 |
35 | body.platform-ios .platform-mac {
36 | display: none;
37 | }
38 |
39 | body.platform-mac .platform-ios {
40 | display: none;
41 | }
42 |
43 | body.platform-ios .platform-mac {
44 | display: none;
45 | }
46 |
47 | body:not(.state-on, .state-off) :is(.state-on, .state-off) {
48 | display: none;
49 | }
50 |
51 | body.state-on :is(.state-off, .state-unknown) {
52 | display: none;
53 | }
54 |
55 | body.state-off :is(.state-on, .state-unknown) {
56 | display: none;
57 | }
58 |
59 | button {
60 | font-size: 1em;
61 | }
62 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (App)/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Shared (App)
4 | //
5 | // Created by admin on 19/04/23.
6 | //
7 |
8 | import WebKit
9 |
10 | #if os(iOS)
11 | import UIKit
12 | typealias PlatformViewController = UIViewController
13 | #elseif os(macOS)
14 | import Cocoa
15 | import SafariServices
16 | typealias PlatformViewController = NSViewController
17 | #endif
18 |
19 | let extensionBundleIdentifier = "in.evenzero.ZenTube.Extension"
20 |
21 | class ViewController: PlatformViewController, WKNavigationDelegate, WKScriptMessageHandler {
22 |
23 | @IBOutlet var webView: WKWebView!
24 |
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | self.webView.navigationDelegate = self
29 |
30 | #if os(iOS)
31 | self.webView.scrollView.isScrollEnabled = false
32 | #endif
33 |
34 | self.webView.configuration.userContentController.add(self, name: "controller")
35 |
36 | self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
37 | }
38 |
39 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
40 | #if os(iOS)
41 | webView.evaluateJavaScript("show('ios')")
42 | #elseif os(macOS)
43 | webView.evaluateJavaScript("show('mac')")
44 |
45 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
46 | guard let state = state, error == nil else {
47 | // Insert code to inform the user that something went wrong.
48 | return
49 | }
50 |
51 | DispatchQueue.main.async {
52 | if #available(macOS 13, *) {
53 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), true)")
54 | } else {
55 | webView.evaluateJavaScript("show('mac', \(state.isEnabled), false)")
56 | }
57 | }
58 | }
59 | #endif
60 | }
61 |
62 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
63 | #if os(macOS)
64 | if (message.body as! String != "open-preferences") {
65 | return;
66 | }
67 |
68 | SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
69 | guard error == nil else {
70 | // Insert code to inform the user that something went wrong.
71 | return
72 | }
73 |
74 | DispatchQueue.main.async {
75 | NSApplication.shared.terminate(nil)
76 | }
77 | }
78 | #endif
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/Shared (Extension)/SafariWebExtensionHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SafariWebExtensionHandler.swift
3 | // Shared (Extension)
4 | //
5 | // Created by admin on 19/04/23.
6 | //
7 |
8 | import SafariServices
9 | import os.log
10 |
11 | let SFExtensionMessageKey = "message"
12 |
13 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
14 |
15 | func beginRequest(with context: NSExtensionContext) {
16 | let item = context.inputItems[0] as! NSExtensionItem
17 | let message = item.userInfo?[SFExtensionMessageKey]
18 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
19 |
20 | let response = NSExtensionItem()
21 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
22 |
23 | context.completeRequest(returningItems: [response], completionHandler: nil)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/ZenTube.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/ZenTube.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/ZenTube.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/ZenTube/ZenTube.xcodeproj/project.xcworkspace/xcuserdata/admin.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/src/safari/ZenTube/ZenTube.xcodeproj/xcuserdata/admin.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ZenTube (iOS).xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 | ZenTube (macOS).xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 1
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by admin on 19/04/23.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (App)/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (App)/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 14.3
7 | UIApplicationSceneManifest
8 |
9 | UIApplicationSupportsMultipleScenes
10 |
11 | UISceneConfigurations
12 |
13 | UIWindowSceneSessionRoleApplication
14 |
15 |
16 | UISceneConfigurationName
17 | Default Configuration
18 | UISceneDelegateClassName
19 | $(PRODUCT_MODULE_NAME).SceneDelegate
20 | UISceneStoryboardFile
21 | Main
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (App)/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by admin on 19/04/23.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
15 | guard let _ = (scene as? UIWindowScene) else { return }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/iOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // macOS (App)
4 | //
5 | // Created by admin on 19/04/23.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ notification: Notification) {
14 | // Override point for customization after application launch.
15 | }
16 |
17 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
18 | return true
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (App)/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 14.3
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (App)/ZenTube.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/safari/ZenTube/macOS (Extension)/ZenTube.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/safari/build/sa_zentube-latest.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/safari/build/sa_zentube-latest.zip
--------------------------------------------------------------------------------
/src/safari/iOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by admin on 28/11/22.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
21 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/safari/iOS (App)/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/safari/iOS (App)/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/safari/iOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 14.1
7 | UIApplicationSceneManifest
8 |
9 | UIApplicationSupportsMultipleScenes
10 |
11 | UISceneConfigurations
12 |
13 | UIWindowSceneSessionRoleApplication
14 |
15 |
16 | UISceneConfigurationName
17 | Default Configuration
18 | UISceneDelegateClassName
19 | $(PRODUCT_MODULE_NAME).SceneDelegate
20 | UISceneStoryboardFile
21 | Main
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/safari/iOS (App)/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // iOS (App)
4 | //
5 | // Created by admin on 28/11/22.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
15 | guard let _ = (scene as? UIWindowScene) else { return }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/safari/iOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/safari/macOS (App)/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // macOS (App)
4 | //
5 | // Created by admin on 28/11/22.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ notification: Notification) {
14 | // Override point for customization after application launch.
15 | }
16 |
17 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
18 | return true
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/safari/macOS (App)/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/src/safari/macOS (App)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SFSafariWebExtensionConverterVersion
6 | 14.1
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/safari/macOS (App)/ZenTube.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/safari/macOS (Extension)/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.Safari.web-extension
9 | NSExtensionPrincipalClass
10 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/safari/macOS (Extension)/ZenTube.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/wasm/emoji.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inversepolarity/ZYT/d384b35de81474abbd45855a830b5e03b77ee1fb/src/wasm/emoji.wasm
--------------------------------------------------------------------------------
/src/wasm/emoji.wat:
--------------------------------------------------------------------------------
1 | (module
2 | ;; actions
3 | (import "env" "restore" (func $restore (result i32)))
4 |
5 |
6 | ;; ref creators
7 | (import "env" "createRef" (func $createRef (result i32)))
8 | (import "env" "createMapRef" (func $createMapRef (result i32)))
9 | (import "env" "createNodeRef" (func $createNodeRef (result i32)))
10 |
11 | ;; utils
12 | (import "env" "length" (func $length (result i32)))
13 | (import "env" "logInt" (func $logInt (result i32)))
14 | (import "env" "logRef" (func $logRef (result i32)))
15 | (import "env" "free" (func $free (result i32)))
16 |
17 | ;; from: https://github.com/WebAssembly/interface-types/issues/18#issuecomment-430605795
18 |
19 | ;; exports
20 | (func (export "put_back")
21 | (local $hashmap i32)
22 | (local $node i32)
23 |
24 | (local.set $hashmap (call $createMapRef))
25 | (local.set $node (call $createNodeRef))
26 |
27 | (call $restore (local.get $hashmap (local.get $node)))
28 |
29 | (call $logInt (call $length (local.get $hashmap)))
30 | (call $logRef (local.get $node))
31 | (call $free (local.get $node))
32 | (call $free (local.get $hashmap))
33 | )
34 | )
--------------------------------------------------------------------------------
/tests/bootstrap.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require("puppeteer");
2 |
3 | async function bootstrap(options = {}) {
4 | const { devtools = false, slowMo = false, appUrl } = options;
5 |
6 | const browser = await puppeteer.launch({
7 | headless: false,
8 | devtools,
9 | args: ["--disable-extensions-except=./src", "--load-extension=./src"],
10 | ...(slowMo && { slowMo })
11 | });
12 |
13 | const appPage = await browser.newPage();
14 | await appPage.goto(appUrl, { waitUntil: "load" });
15 |
16 | const targets = await browser.targets();
17 | const extensionTarget = targets.find(
18 | (target) => target.type() === "service_worker"
19 | );
20 | const partialExtensionUrl = extensionTarget.url() || "";
21 | const [, , extensionId] = partialExtensionUrl.split("/");
22 |
23 | const extPage = await browser.newPage();
24 | const extensionUrl = `chrome-extension://${extensionId}/options/options.html`;
25 | await extPage.goto(extensionUrl, { waitUntil: "load" });
26 |
27 | return {
28 | appPage,
29 | browser,
30 | extensionUrl,
31 | extPage
32 | };
33 | }
34 |
35 | module.exports = { bootstrap };
36 |
--------------------------------------------------------------------------------
/tests/jest.setup.js:
--------------------------------------------------------------------------------
1 | Object.assign(global, require("jest-chrome"));
2 |
--------------------------------------------------------------------------------
/tests/options.spec.js:
--------------------------------------------------------------------------------
1 | const { bootstrap } = require("./bootstrap");
2 | const { chrome } = require("jest-chrome");
3 |
4 | const { defaultSettings, getNewBrowserTab, sleep } = require("./putils");
5 |
6 | const manifest = require("../src/manifest.json");
7 |
8 | describe("test suite for options popup", () => {
9 | let extPage, appPage, browser;
10 |
11 | beforeAll(async () => {
12 | const context = await bootstrap({ appUrl: "https://www.youtube.com/" });
13 | extPage = context.extPage;
14 | appPage = context.appPage;
15 | browser = context.browser;
16 |
17 | /* Mock Implementations */
18 |
19 | const injectScript = () => jest.fn();
20 | const sendMessageToTabs = (tabs, msg) => jest.fn();
21 | const messagePageScript = () => jest.fn();
22 |
23 | const selectionChanged = () => jest.fn();
24 | const storeSettings = () => jest.fn();
25 | const setDropdownSelect = () => jest.fn();
26 |
27 | chrome.runtime.getManifest.mockImplementation(() => manifest);
28 | global.chrome = {
29 | storage: {
30 | local: {
31 | set: jest.fn(() => defaultSettings),
32 | get: jest.fn(() => defaultSettings)
33 | }
34 | }
35 | };
36 | });
37 |
38 | it("manifest version", async () => {
39 | const ver = await extPage.$("#version");
40 | const verText = await ver.evaluate((e) => e.innerText);
41 | expect(verText).toEqual("Ver: " + chrome.runtime.getManifest().version);
42 | expect(chrome.runtime.getManifest).toBeCalled();
43 | });
44 |
45 | it("ip link event listener", async () => {
46 | await extPage.evaluate(() => document.querySelector("#brand").click());
47 | const donationPage = await getNewBrowserTab(browser);
48 | expect(donationPage.url()).toBe("https://ko-fi.com/evenzero");
49 | donationPage.close();
50 | });
51 |
52 | it("transitions css injected", async () => {
53 | const transitions = await appPage.$("#zentubeTransitions");
54 | expect(transitions).toBeTruthy();
55 | });
56 |
57 | it("toggle css injected", async () => {
58 | const zentube = await appPage.$("#zentube");
59 | expect(zentube).toBeTruthy();
60 | });
61 |
62 | it("settings stored", async () => {
63 | const { settings } = global.chrome.storage.local.get();
64 |
65 | expect(Object.keys(defaultSettings).length).toBeTruthy();
66 | expect(Object.keys(settings).length).toBeTruthy();
67 | expect(settings).toBeTruthy();
68 | expect(settings).toEqual(defaultSettings);
69 | });
70 |
71 | it("repopulatePopup", async () => {
72 | // Popup populated
73 | let popupChildren = await extPage.evaluate(() => {
74 | return document.querySelector("#popup").children.length;
75 | });
76 |
77 | expect(popupChildren).toEqual(
78 | Object.keys(defaultSettings.options[defaultSettings.currentPage]).length
79 | );
80 | });
81 |
82 | it("setDropdownSelect", async () => {
83 | let dropdownLabel = await extPage.evaluate(() => {
84 | return document.querySelector(".select-selected").innerText;
85 | });
86 |
87 | expect(dropdownLabel).toEqual(defaultSettings.currentPage);
88 | });
89 |
90 | // TODO selectionChanged
91 | // TODO TEST DROPDOWN ENTRIES
92 | // TODO TEST EACH BUTTON FOR EACH DROPDOWN SELECTION
93 | afterAll(async () => {
94 | await sleep(1000);
95 | extPage.close();
96 | await sleep(1000);
97 | appPage.close();
98 | await sleep(1000);
99 | browser.close();
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/tests/putils.js:
--------------------------------------------------------------------------------
1 | async function getNewBrowserTab(browser) {
2 | let resultPromise;
3 |
4 | async function onTargetcreatedHandler(target) {
5 | if (target.type() === "other" || "page") {
6 | let newPage = await browser.pages();
7 | newPage = newPage.slice(-1)[0];
8 | const newPagePromise = new Promise((y) =>
9 | newPage.once("domcontentloaded", () => y(newPage))
10 | );
11 |
12 | const isPageLoaded = await newPage.evaluate(() => document.readyState);
13 |
14 | browser.off("targetcreated", onTargetcreatedHandler); // unsubscribing
15 |
16 | return isPageLoaded.match("complete|interactive")
17 | ? resultPromise(newPage)
18 | : resultPromise(newPagePromise);
19 | }
20 | }
21 |
22 | return new Promise((resolve) => {
23 | resultPromise = resolve;
24 | browser.on("targetcreated", onTargetcreatedHandler);
25 | });
26 | }
27 |
28 | const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29 |
30 | const defaultSettings = {
31 | currentPage: "Home",
32 | options: {
33 | Home: {
34 | preview: {
35 | label: "Preview on hover",
36 | classes: [
37 | ".ytd-video-preview",
38 | "#mouseover-overlay",
39 | ".ytd-thumbnail-overlay-loading-preview-renderer"
40 | ],
41 | show: true
42 | },
43 | communityPosts: {
44 | label: "Latest posts",
45 | classes: ["ytd-rich-shelf-renderer"],
46 | id: "communityPosts",
47 | show: true
48 | },
49 | adThumbs: {
50 | label: "Ad Thumbnails",
51 | classes: [".ytd-display-ad-renderer", ".ytd-ad-slot-renderer"],
52 | show: true
53 | },
54 | chipBar: {
55 | label: "Feed Filter Chip Bar",
56 | classes: [".ytd-feed-filter-chip-bar-renderer"],
57 | show: true
58 | },
59 | title: {
60 | label: "Video title",
61 | classes: ["yt-formatted-string.style-scope.ytd-rich-grid-media"],
62 | show: true
63 | }
64 | },
65 | Video: {
66 | sidebar: {
67 | label: "Video Sidebar",
68 | classes: [".ytd-watch-next-secondary-results-renderer"],
69 | show: true
70 | },
71 | nextvideos: {
72 | label: "End Recs (Default)",
73 | classes: [".ytp-ce-video .ytp-ce-channel .ytp-ce-covering-overlay"],
74 | show: true
75 | },
76 | endvideos: {
77 | label: "End Recs (Channel)",
78 | classes: [".ytp-endscreen-content"],
79 | show: true
80 | },
81 | chat: {
82 | label: "Chat",
83 | classes: ["#chat"],
84 | show: true
85 | },
86 | likes: {
87 | label: "Likes",
88 | classes: [
89 | "ytd-menu-renderer.style-scope.ytd-watch-metadata .yt-core-attributed-string"
90 | ],
91 | show: true
92 | },
93 | comments: {
94 | label: "Comments",
95 | classes: [".ytd-comments"],
96 | show: true
97 | },
98 | playlist: {
99 | label: "Playlist",
100 | classes: ["div.style-scope.ytd-playlist-panel-renderer"],
101 | show: true
102 | },
103 | chapters: {
104 | label: "Chapters",
105 | classes: [
106 | "ytd-engagement-panel-section-list-renderer.style-scope.ytd-watch-flexy"
107 | ],
108 | show: true
109 | },
110 | subscribe: {
111 | label: "Subscribe Button",
112 | classes: ["yt-button-shape.style-scope.ytd-subscribe-button-renderer"],
113 | show: true
114 | },
115 | title: {
116 | label: "Video Title",
117 | classes: ["yt-formatted-string.style-scope.ytd-watch-metadata"],
118 | show: true
119 | },
120 | sub_count: {
121 | label: "Subscriber count",
122 | classes: ["yt-formatted-string.style-scope.ytd-video-owner-renderer"],
123 | show: true
124 | },
125 | description: {
126 | label: "Description Box",
127 | classes: ["ytd-text-inline-expander.style-scope.ytd-watch-metadata"],
128 | show: true
129 | },
130 | merch: {
131 | label: "Merchandise Box",
132 | classes: ["ytd-merch-shelf-renderer.style-scope.ytd-watch-flexy"],
133 | show: true
134 | }
135 | },
136 | Everywhere: {
137 | metadata: {
138 | label: "Video Metadata",
139 | classes: [
140 | "span.inline-metadata-item.style-scope.ytd-video-meta-block",
141 | "yt-formatted-string.style-scope.ytd-channel-name",
142 | "yt-icon.style-scope.ytd-badge-supported-renderer",
143 | "div.badge.badge-style-type-live-now-alternate.style-scope.ytd-badge-supported-renderer",
144 | "#metadata-line",
145 | "#byline-container"
146 | ],
147 | show: true
148 | },
149 | duration: {
150 | label: "Video Duration",
151 | classes: [
152 | "span.style-scope.ytd-thumbnail-overlay-time-status-renderer"
153 | ],
154 | show: true
155 | },
156 | thumbnails: {
157 | label: "Video Thumbnails",
158 | classes: [
159 | ".ytd-macro-markers-list-item-renderer>img",
160 | ".thumbnail-container.style-scope.ytd-notification-renderer",
161 | ".yt-core-image--loaded"
162 | ],
163 | show: true
164 | },
165 | resume: {
166 | label: "Resume bar",
167 | classes: [
168 | "div.style-scope.ytd-thumbnail-overlay-resume-playback-renderer"
169 | ],
170 | show: true
171 | },
172 | logo: {
173 | label: "YouTube Logo",
174 | classes: ["#logo .ytd-topbar-logo-renderer"],
175 | show: true
176 | },
177 | channelThumb: {
178 | label: "Channel Avatar",
179 | classes: [
180 | "#avatar",
181 | "#channel-thumbnail",
182 | "tp-yt-paper-item.style-scope.ytd-guide-entry-renderer > yt-img-shadow"
183 | ],
184 | show: true
185 | }
186 | }
187 | }
188 | };
189 |
190 | module.exports = { defaultSettings, getNewBrowserTab, sleep };
191 |
--------------------------------------------------------------------------------