summaryrefslogtreecommitdiff
path: root/crocoite/data/click.js
blob: b098810ce205504ea38ea592e4e285f73812f2cb (plain)
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
40
41
42
43
44
45
46
47
48
49
50
51
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*	Generic clicking
 *
 *  We can’t just click every clickable object, since there may be side-effects
 *  like navigating to a different location. Thus whitelist known elements.
 */

(function(){
const selectorFlag = Object.freeze ({
	none: 0,
	multi: 1, /* click item multiple times */
});
const defaultClickThrottle = 50; /* in ms */
const discoverInterval = 1000; /* 1 second */
const sites = Object.freeze ([
	{
		hostname: /^www\.facebook\.com$/i,
		selector: [
			/* show more comments */
			{s: 'a.UFIPagerLink[role=button]', flags: selectorFlag.none},
			/* show nested comments*/
			{s: 'a.UFICommentLink[role=button]', flags: selectorFlag.none},
			/* initially show comments below a single post/video, i.e. /user/post/123 */
			{s: 'form.commentable_item a[data-comment-prelude-ref=action_link_bling][rel=ignore]', flags: selectorFlag.none},
			/* close the “register now” nag screen. for better screen shots */
			{s: 'div#headerArea a#expanding_cta_close_button[role=button]', flags: selectorFlag.none},
			],
	}, {
		hostname: /^twitter\.com$/i,
		selector: [
			/* expand threads */
			{s: 'a.ThreadedConversation-moreRepliesLink', flags: selectorFlag.none},
			/* show hidden profiles */
			{s: 'button.ProfileWarningTimeline-button', flags: selectorFlag.none},
			/* show hidden/sensitive media */
			{s: 'button.Tombstone-action.js-display-this-media', flags: selectorFlag.none},
			],
	}, {
		hostname: /^disqus\.com$/i,
		selector: [
			/* load more comments */
			{s: 'a.load-more__button', flags: selectorFlag.multi},
			],
	}, {
		hostname: /^(www|np)\.reddit\.com$/i,
		selector: [
			/* show more comments, reddit’s javascript ignores events if too
			 * frequent */
			{s: 'span.morecomments a', flags: selectorFlag.none, throttle: 500},
			],
	}, {
		hostname: /^www\.instagram\.com$/i,
		selector: [
			/* posts may have multiple images that load dynamically, click the arrow */
			{s: 'a[role=button].coreSpriteRightChevron', flags: selectorFlag.multi, throttle: 500},
			/* load more comments */
			{s: 'article div ul li a[role=button]', flags: selectorFlag.multi},
			],
	}, {
		hostname: /^www\.youtube\.com$/i,
		selector: [
			/* expand comment thread */
			{s: 'ytd-comment-thread-renderer div.more-button', flags: selectorFlag.none},
			],
	}, {
		hostname: /^www\.patreon\.com$/i,
		selector: [
			/* load more content */
			{s: 'div[display=flex] div[display=block] button[color=gray][type=button]', flags: selectorFlag.multi},
			/* load more comments */
			{s: 'div.stackable[display=block] > div  > div  > a[color=dark][target=_self]', flags: selectorFlag.none},
			/* load more replies */
			{s: 'div > a[scale="0"][color=blue][size="1"]', flags: selectorFlag.none},
			],
	}, {
		hostname: /^(www\.)?gab\.ai$/i,
		selector: [
			/* post comments */
			{s: 'post-detail post-comment .post-comment__replies__count a', flags: selectorFlag.none},
			/* more comments */
			{s: 'post-detail .post-comment-list__loading a', flags: selectorFlag.none},
			/* more posts */
			{s: 'post-list a.post-list__load-more', flags: selectorFlag.multi},
			],
	}
	]);

/* pick selectors matching current location */
let hostname = document.location.hostname;
let selector = [];
for (let s of sites) {
	if (s.hostname.test (hostname)) {
		selector = selector.concat (s.selector);
	}
}

function makeClickEvent () {
	return new MouseEvent('click', {
				view: window,
				bubbles: true,
				cancelable: true
				});
}

/* throttle clicking */
let queue = [];
let clickTimeout = null;
function click () {
	if (queue.length > 0) {
		const item = queue.shift ();
		const o = item.o;
		const selector = item.selector;
		o.dispatchEvent (makeClickEvent ());

		if (queue.length > 0) {
			const nextTimeout = 'throttle' in selector ?
					selector.throttle : defaultClickThrottle;
			clickTimeout = window.setTimeout (click, nextTimeout);
		} else {
			clickTimeout = null;
		}
	}
}

/*	Element is visible if itself and all of its parents are
 */
function isVisible (o) {
	if (o === null || !(o instanceof Element)) {
		return true;
	}
	let style = window.getComputedStyle (o);
	if ('parentNode' in o) {
		return style.display !== 'none' && isVisible (o.parentNode);
	} else {
		return style.display !== 'none';
	}
}

/*	Elements are considered clickable if they are a) visible and b) not
 *	disabled
 */
function isClickable (o) {
	return !o.hasAttribute ('disabled') && isVisible (o);
}

/* some sites don’t remove/replace the element immediately, so keep track of
 * which ones we already clicked */
let have = new Set ();
function discover () {
	for (let s of selector) {
		let obj = document.querySelectorAll (s.s);
		for (let o of obj) {
			if (!have.has (o) && isClickable (o)) {
				queue.push ({o: o, selector: s});
				if (!(s.flags & selectorFlag.multi)) {
					have.add (o);
				}
			}
		}
	}
	if (queue.length > 0 && clickTimeout === null) {
		/* start clicking immediately */
		clickTimeout = window.setTimeout (click, 0);
	}
	return true;
}

/* XXX: can we use a mutation observer instead? */
let interval = window.setInterval (discover, discoverInterval);

function stop() {
	window.clearInterval (interval);
}
return {'stop': stop};
}())