Skip to content

Commit c50f571

Browse files
committed
refactor: complete refactor of the system to be more stable and asynchronous.
Changes: * Functions are now asynchronous meaning it should be a lot faster at handling multiple posts * It will fail silently if it cannot find the rootnode on the first run, and will automatically retry. * When scanning images it will handle image urls not correctly loading which caused the script to stop running previously.
1 parent 3a7b41a commit c50f571

File tree

2 files changed

+120
-98
lines changed

2 files changed

+120
-98
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Userscripts",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "A collection of userscript experiments that are too small to be in an extension. From minor website adjustments such as removing spoilers to improving the media viewer.",
55
"author": "quantix-dev",
66
"license": "MIT",

src/Spectacles/index.js

Lines changed: 119 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ function createPostTreeWalker(post) {
2929
// Validating
3030
if (node.nodeName === 'IMG') {
3131
// Constructing URL
32-
const url = new URL(node.src);
32+
let url;
33+
try {
34+
url = new URL(node.src);
35+
} catch {
36+
return NodeFilter.FILTER_SKIP;
37+
}
3338

3439
// Checking to see if it's a valid post
3540
if (mediaLinks.includes(url.hostname) && url.searchParams.has('blur')) {
@@ -49,49 +54,51 @@ function createPostTreeWalker(post) {
4954
* @param {Node} mediaNode The media node, often an image, to find the thread to
5055
* @returns {string} The source.
5156
*/
52-
function findExternalNode(mediaNode) {
53-
let depth = 0;
54-
let par = mediaNode;
55-
while (par && par.classList) {
56-
if (par.classList.contains('scrollerItem') || par.nodeName == 'A' || depth >= 15) break;
57-
par = par.parentNode;
58-
depth++;
59-
}
57+
async function findExternalNode(mediaNode) {
58+
return new Promise((resolve, reject) => {
59+
let depth = 0;
60+
let par = mediaNode;
61+
while (par && par.classList) {
62+
if (par.classList.contains('scrollerItem') || par.nodeName == 'A' || depth >= 15) break;
63+
par = par.parentNode;
64+
depth++;
65+
}
6066

61-
// Handling the gathered node
62-
if (!par) return null;
63-
if (par.nodeName == 'A') {
64-
return par;
65-
} else {
66-
// i forgot what this entire section is even for..
67-
// const treeWalker = document.createTreeWalker(par, NodeFilter.SHOW_ELEMENT, (node) => {
68-
// // Validating
69-
// if (node.nodeName == 'A') {
70-
// if (invalidSourceLinks.filter((link) => node.href.match(link) != null).length <= 0)
71-
// return NodeFilter.FILTER_ACCEPT;
72-
// }
73-
74-
// return NodeFilter.FILTER_SKIP;
75-
// });
76-
77-
78-
// while (treeWalker.nextNode()) {
79-
// // Prevent duplicate
80-
// let flag = true;
81-
// const postWalker = createPostTreeWalker(treeWalker.currentNode);
82-
// while (postWalker.nextNode()) {
83-
// console.log(`node ${postWalker.currentNode} .. ${postWalker.currentNode.src}`)
84-
// if (postWalker.currentNode.src == mediaNode.src) {
85-
// flag = false;
86-
// console.log('found duplicate');
87-
// break;
88-
// }
89-
// }
90-
91-
// if (!flag) continue;
92-
// return treeWalker.currentNode;
93-
// }
94-
}
67+
// Handling the gathered node
68+
if (!par) return null;
69+
if (par.nodeName == 'A') {
70+
resolve(par);
71+
} else {
72+
// i forgot what this entire section is even for..
73+
// const treeWalker = document.createTreeWalker(par, NodeFilter.SHOW_ELEMENT, (node) => {
74+
// // Validating
75+
// if (node.nodeName == 'A') {
76+
// if (invalidSourceLinks.filter((link) => node.href.match(link) != null).length <= 0)
77+
// return NodeFilter.FILTER_ACCEPT;
78+
// }
79+
80+
// return NodeFilter.FILTER_SKIP;
81+
// });
82+
83+
84+
// while (treeWalker.nextNode()) {
85+
// // Prevent duplicate
86+
// let flag = true;
87+
// const postWalker = createPostTreeWalker(treeWalker.currentNode);
88+
// while (postWalker.nextNode()) {
89+
// console.log(`node ${postWalker.currentNode} .. ${postWalker.currentNode.src}`)
90+
// if (postWalker.currentNode.src == mediaNode.src) {
91+
// flag = false;
92+
// console.log('found duplicate');
93+
// break;
94+
// }
95+
// }
96+
97+
// if (!flag) continue;
98+
// return treeWalker.currentNode;
99+
// }
100+
}
101+
});
95102
}
96103

97104
/**
@@ -138,19 +145,20 @@ function removeMediaSpoiler(mediaNode, source) {
138145
}
139146

140147
// Fix the post
141-
let linkNode = findExternalNode(mediaNode);
142-
for (let child of linkNode.children) {
143-
if (child !== mediaNode && child.nodeName == "DIV") {
144-
let children = Array.from(child.children);
145-
children = children.filter((x) => {
146-
return x.nodeName === "BUTTON" && x.innerHTML.includes('spoiler');
147-
});
148-
149-
if (children[0]) {
150-
children[0].style.display = "none";
148+
findExternalNode(mediaNode).then((linkNode) => {
149+
for (let child of linkNode.children) {
150+
if (child !== mediaNode && child.nodeName == "DIV") {
151+
let children = Array.from(child.children);
152+
children = children.filter((x) => {
153+
return x.nodeName === "BUTTON" && x.innerHTML.includes('spoiler');
154+
});
155+
156+
if (children[0]) {
157+
children[0].style.display = "none";
158+
}
151159
}
152160
}
153-
}
161+
});
154162
}
155163

156164
/**
@@ -159,7 +167,7 @@ function removeMediaSpoiler(mediaNode, source) {
159167
* @param {string} externalSrc The link of the external media source to scan.
160168
* @returns {string} The unspoilered source link to the media.
161169
*/
162-
function grabSourceFromImage(blurredSrc, externalSrc) {
170+
async function grabSourceFromImage(blurredSrc, externalSrc) {
163171
return new Promise((resolve, reject) => {
164172
// Checking to see if the link isn't already unblurred
165173
const splitURL = externalSrc.split('?')[1];
@@ -217,74 +225,88 @@ function grabSourceFromImage(blurredSrc, externalSrc) {
217225
/**
218226
* Attempts to get all images from the reddit post.
219227
* @param {Node} post the post to search for images in.
220-
* @param {(mediaNode: Node, source: string)} callback the function to call each time an image is found.
221228
*/
222-
function getImagesFromPost(post, callback) {
223-
// Creating tree walker from post
224-
const treeWalker = createPostTreeWalker(post);
225-
let currentNode = treeWalker.currentNode;
226-
227-
while (currentNode) {
228-
let source = findExternalNode(treeWalker.currentNode);
229-
if (source) {
230-
callback(treeWalker.currentNode, source.href)
229+
async function getImagesFromPost(post) {
230+
return new Promise((resolve, reject) => {
231+
// Creating tree walker from post
232+
const treeWalker = createPostTreeWalker(post);
233+
let currentNode = treeWalker.currentNode;
234+
let promises = [];
235+
236+
while (currentNode) {
237+
promises.push(findExternalNode(treeWalker.currentNode));
238+
currentNode = treeWalker.nextNode();
231239
}
232240

233-
currentNode = treeWalker.nextNode();
234-
}
241+
// getting promises
242+
Promise.race(promises).then((source) => {
243+
resolve({ mediaNode: treeWalker.currentNode, source: source.href });
244+
})
245+
})
235246
}
236247

237248
/**
238249
* Main function that will remove spoilers from posts, and check against the whitelist or blacklist
239250
* @param {Node} post The post to remove spoilered media from.
240251
*/
241-
function removePostSpoiler(post, pageType) {
252+
async function removePostSpoiler(post, pageType) {
242253
// Stopping unnecessary posts from continuing
243254
//if (post.nodeName !== "IMG" && !post.classList.contains('Post')) return;
244255

245256
// Managing different types
246257
let postType;
247258
if (pageType == 'thread') {
248-
getImagesFromPost(post, removeMediaSpoiler);
259+
getImagesFromPost(post).then(({ mediaNode, source }) => {
260+
removeMediaSpoiler(mediaNode, source);
261+
});
262+
249263
post.click();
250264
} else {
251-
getImagesFromPost(post, (mediaNode, source) => {
265+
getImagesFromPost(post).then(({ mediaNode, source }) => {
266+
if (!mediaNode || !source) return;
252267
grabSourceFromImage(mediaNode.src, source).then((content) => {
253268
removeMediaSpoiler(mediaNode, content);
254269
})
255-
});
270+
})
256271
}
257272
}
258273

259274
/* ---------------------- */
260275

261-
// Grabbing the rootNode depending on the post type
262-
let rootNode, postType;
263-
if (window.location.href.split('/')[5] !== 'comments') {
264-
// Dynamically grabbing for homepage type
265-
let depth = 0;
266-
rootNode = document.getElementsByClassName('scrollerItem Post')[0];
267-
while (rootNode.parentNode !== null && depth < 10) {
268-
if (rootNode.childNodes.length > 5) break;
269-
270-
rootNode = rootNode.parentNode;
271-
depth++;
276+
(() => {
277+
// Grabbing the rootNode depending on the post type
278+
let rootNode, postType;
279+
if (window.location.href.split('/')[5] !== 'comments') {
280+
// Dynamically grabbing for homepage type
281+
let depth = 0;
282+
rootNode = document.getElementsByClassName('scrollerItem Post')[0];
283+
while (rootNode && depth < 10) {
284+
rootNode = rootNode.parentNode;
285+
if (rootNode.childNodes.length > 5) break;
286+
287+
depth++;
288+
}
289+
} else {
290+
rootNode = document.getElementsByClassName('Post')[0];
291+
postType = 'thread';
272292
}
273293

274-
// Call initial function on all child nodes
275-
rootNode.childNodes.forEach((x) => { removePostSpoiler(x, postType) });
276-
} else {
277-
rootNode = document.getElementsByClassName('Post')[0];
278-
postType = 'thread';
294+
// Handling rootnode missing
295+
if (!rootNode) return;
296+
console.log("Spectacles loaded successfully!")
279297

280-
// Remove spoiler on 'root' because this should be the only post
281-
removePostSpoiler(rootNode)
282-
}
298+
// Handling spoiler removal on load
299+
if (postType === 'thread') {
300+
removePostSpoiler(rootNode)
301+
} else {
302+
rootNode.childNodes.forEach((x) => { removePostSpoiler(x, postType) });
303+
}
283304

284-
// Setting up a listener to detect when new posts are added
285-
VM.observe(rootNode, (mutList) => {
286-
mutList.filter((mut) => mut.type === 'childList').map((mut) => mut.addedNodes[0]).forEach((x) => {
287-
if (!x) return false;
288-
removePostSpoiler(x, postType)
289-
});
290-
}, { childList: true });
305+
// Setting up a listener to detect when new posts are added
306+
VM.observe(rootNode, (mutList) => {
307+
mutList.filter((mut) => mut.type === 'childList').map((mut) => mut.addedNodes[0]).forEach((x) => {
308+
if (!x) return false;
309+
removePostSpoiler(x, postType)
310+
});
311+
}, { childList: true });
312+
})();

0 commit comments

Comments
 (0)