Skip to content

Commit b480873

Browse files
authored
🔀 Merge pull request #334 from richardfrost/small_fixes
Add Off Filter Method and Fix for Audio Muting (Cue mode)
2 parents f34356c + e919527 commit b480873

File tree

8 files changed

+84
-19
lines changed

8 files changed

+84
-19
lines changed

src/script/bookmarkletFilter.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default class BookmarkletFilter extends Filter {
2424
cfg: WebConfig;
2525
domain: Domain;
2626
extension: boolean;
27+
filterText: boolean;
2728
hostname: string;
2829
iframe: Location;
2930
location: Location | URL;
@@ -37,6 +38,7 @@ export default class BookmarkletFilter extends Filter {
3738
constructor() {
3839
super();
3940
this.extension = false;
41+
this.filterText = true;
4042
this.audioWordlistId = Constants.ALL_WORDS_WORDLIST_ID;
4143
this.mutePage = false;
4244
this.processMutationTarget = false;
@@ -155,12 +157,12 @@ export default class BookmarkletFilter extends Filter {
155157
if (node.nodeName) {
156158
if (node.textContent && node.textContent.trim() != '') {
157159
const result = this.replaceTextResult(node.textContent, wordlistId, statsType);
158-
if (result.modified) {
160+
if (result.modified && this.filterText) {
159161
node.textContent = result.filtered;
160162
}
161163
} else if (node.nodeName == 'IMG') {
162-
if (node.alt != '') { node.alt = this.replaceText(node.alt, wordlistId, statsType); }
163-
if (node.title != '') { node.title = this.replaceText(node.title, wordlistId, statsType); }
164+
this.cleanNodeAttribute(node, 'alt', wordlistId, statsType);
165+
this.cleanNodeAttribute(node, 'title', wordlistId, statsType);
164166
} else if (node.shadowRoot) {
165167
this.filterShadowRoot(node.shadowRoot, wordlistId, statsType);
166168
}
@@ -179,8 +181,18 @@ export default class BookmarkletFilter extends Filter {
179181
}
180182
}
181183

184+
cleanNodeAttribute(node, attribute: string, wordlistId: number, statsType: string | null = Constants.STATS_TYPE_TEXT) {
185+
if (node[attribute] != '') {
186+
const result = this.replaceTextResult(node[attribute], wordlistId, statsType);
187+
if (result.modified && this.filterText) {
188+
node[attribute] = result.filtered;
189+
}
190+
}
191+
}
192+
182193
cleanPage() {
183194
this.cfg = new WebConfig(config);
195+
this.filterText = this.cfg.filterMethod !== Constants.FILTER_METHODS.OFF;
184196
this.domain = Domain.byHostname(this.hostname, this.cfg.domains);
185197
this.cfg.muteMethod = Constants.MUTE_METHODS.VIDEO; // Bookmarklet: Force video volume mute method
186198

src/script/lib/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default class Constants {
66
static readonly ALL_WORDS_WORDLIST_ID = 0;
77
static readonly DOMAIN_MODES = { NORMAL: 0, ADVANCED: 1, DEEP: 2 };
88
static readonly FALSE = 0;
9-
static readonly FILTER_METHODS = { CENSOR: 0, SUBSTITUTE: 1, REMOVE: 2 };
9+
static readonly FILTER_METHODS = { CENSOR: 0, SUBSTITUTE: 1, REMOVE: 2, OFF: 3 };
1010
static readonly MATCH_METHODS = { EXACT: 0, PARTIAL: 1, WHOLE: 2, REGEX: 3 };
1111
static readonly MUTE_METHODS = { TAB: 0, VIDEO: 1, NONE: 2 };
1212
static readonly SHOW_SUBTITLES = { ALL: 0, FILTERED: 1, UNFILTERED: 2, NONE: 3 };

src/script/lib/filter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default class Filter {
107107
const wordlist = this.wordlists[wordlistId];
108108

109109
switch(this.cfg.filterMethod) {
110+
case Constants.FILTER_METHODS.OFF:
110111
case Constants.FILTER_METHODS.CENSOR:
111112
wordlist.regExps.forEach((regExp, index) => {
112113
str = str.replace(regExp, (originalMatch, ...args): string => {

src/script/optionPage.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,11 @@ export default class OptionPage {
812812
if (testText.value === '') {
813813
filteredTestText.textContent = 'Enter some text above to test the filter...';
814814
} else {
815-
filteredTestText.textContent = filter.replaceText(testText.value, filter.cfg.wordlistId, null);
815+
if (option.cfg.filterMethod === Constants.FILTER_METHODS.OFF) {
816+
filteredTestText.textContent = testText.value;
817+
} else {
818+
filteredTestText.textContent = filter.replaceText(testText.value, filter.cfg.wordlistId, null);
819+
}
816820
}
817821
}
818822

@@ -1540,6 +1544,7 @@ export default class OptionPage {
15401544
OptionPage.show(document.getElementById('substitutionSettings'));
15411545
OptionPage.show(document.getElementById('wordSubstitution'));
15421546
break;
1547+
case Constants.FILTER_METHODS.OFF:
15431548
case Constants.FILTER_METHODS.REMOVE:
15441549
OptionPage.hide(document.getElementById('censorSettings'));
15451550
OptionPage.hide(document.getElementById('substitutionSettings'));

src/script/webAudio.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export default class WebAudio {
180180

181181
const result = this.replaceTextResult(node.textContent);
182182
if (result.modified) {
183-
node.textContent = result.filtered;
183+
if (this.youTubeAutoSubsRule.filterSubtitles) { node.textContent = result.filtered; }
184184
this.mute(this.youTubeAutoSubsRule);
185185
this.youTubeAutoSubsUnmuteDelay = null;
186186
this.filter.updateCounterBadge();
@@ -399,6 +399,7 @@ export default class WebAudio {
399399
if (!rule.disabled) {
400400
// Setup rule defaults
401401
if (rule.filterSubtitles == null) { rule.filterSubtitles = true; }
402+
if (this.filter.filterText == false) { rule.filterSubtitles = false; }
402403
this.initDisplaySelector(rule);
403404

404405
// Allow rules to override global settings
@@ -470,7 +471,7 @@ export default class WebAudio {
470471
this.filter.cfg.addWord(youTubeAutoCensor, youTubeAutoCensorOptions);
471472

472473
// Setup rule for YouTube Auto Subs
473-
this.youTubeAutoSubsRule = { mode: 'ytauto', muteMethod: this.filter.cfg.muteMethod } as AudioRule;
474+
this.youTubeAutoSubsRule = { filterSubtitles: true, mode: 'ytauto', muteMethod: this.filter.cfg.muteMethod } as AudioRule;
474475
}
475476
}
476477

@@ -679,7 +680,7 @@ export default class WebAudio {
679680
cue.originalText = cue.text;
680681
if (result.modified) {
681682
cue.filtered = true;
682-
cue.text = result.filtered;
683+
if (rule.filterSubtitles) { cue.text = result.filtered; }
683684
} else {
684685
cue.filtered = false;
685686
}
@@ -985,8 +986,13 @@ export default class WebAudio {
985986
const activeCues = Array.from(textTrack.activeCues as any as FilteredVTTCue[]);
986987
const apfLines = [];
987988

989+
// Process cues
988990
const processed = activeCues.some((activeCue) => activeCue.hasOwnProperty('filtered'));
989-
if (!processed) { instance.processCues(activeCues, rule); }
991+
if (!processed) {
992+
const allCues = Array.from(textTrack.cues as any as FilteredVTTCue[]);
993+
instance.processCues(allCues, rule);
994+
}
995+
990996
const filtered = activeCues.some((activeCue) => activeCue.filtered);
991997
filtered ? instance.mute(rule, video) : instance.unmute(rule, video);
992998
const shouldBeShown = instance.subtitlesShouldBeShown(rule, filtered);
@@ -1019,6 +1025,10 @@ export default class WebAudio {
10191025
instance.unmute(rule, video);
10201026
}
10211027
};
1028+
1029+
// Pre-process all cues after setting oncuechange
1030+
const allCues = Array.from(textTrack.cues as any as FilteredVTTCue[]);
1031+
instance.processCues(allCues, rule);
10221032
}
10231033
}
10241034
}

src/script/webAudioSites.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,17 @@ export default class WebAudioSites {
4646
rootNode: true,
4747
subtitleSelector: 'div > div > div > div > div',
4848
tagName: 'DIV',
49-
}
49+
},
50+
{
51+
displaySelector: 'div.video-container > div > div > div',
52+
mode: 'elementChild',
53+
muteMethod: Constants.MUTE_METHODS.TAB,
54+
parentSelector: 'div.video-container',
55+
preserveWhiteSpace: true,
56+
rootNode: true,
57+
subtitleSelector: 'div > div > div > div > span',
58+
tagName: 'DIV',
59+
},
5060
],
5161
'www.att.tv': [{ mode: 'cue', videoSelector: 'video#quickplayPlayer' }],
5262
'www.attwatchtv.com': [{ mode: 'cue', videoSelector: 'video#quickplayPlayer' }],

src/script/webFilter.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default class WebFilter extends Filter {
1717
cfg: WebConfig;
1818
domain: Domain;
1919
extension: boolean;
20+
filterText: boolean;
2021
hostname: string;
2122
iframe: Location;
2223
location: Location | URL;
@@ -32,6 +33,7 @@ export default class WebFilter extends Filter {
3233
super();
3334
this.audioWordlistId = Constants.ALL_WORDS_WORDLIST_ID;
3435
this.extension = true;
36+
this.filterText = true;
3537
this.mutePage = false;
3638
this.processMutationTarget = false;
3739
this.stats = { mutes: 0, words: {} };
@@ -164,13 +166,13 @@ export default class WebFilter extends Filter {
164166
if (node.nodeName) {
165167
if (node.textContent && node.textContent.trim() != '') {
166168
const result = this.replaceTextResult(node.textContent, wordlistId, statsType);
167-
if (result.modified) {
169+
if (result.modified && this.filterText) {
168170
// logger.debug(`Normal node text changed: '${result.original}' to '${result.filtered}'.`);
169171
node.textContent = result.filtered;
170172
}
171173
} else if (node.nodeName == 'IMG') {
172-
if (node.alt != '') { node.alt = this.replaceText(node.alt, wordlistId, statsType); }
173-
if (node.title != '') { node.title = this.replaceText(node.title, wordlistId, statsType); }
174+
this.cleanNodeAttribute(node, 'alt', wordlistId, statsType);
175+
this.cleanNodeAttribute(node, 'title', wordlistId, statsType);
174176
} else if (node.shadowRoot) {
175177
this.filterShadowRoot(node.shadowRoot, wordlistId, statsType);
176178
}
@@ -190,8 +192,18 @@ export default class WebFilter extends Filter {
190192
}
191193
}
192194

195+
cleanNodeAttribute(node, attribute: string, wordlistId: number, statsType: string | null = Constants.STATS_TYPE_TEXT) {
196+
if (node[attribute] != '') {
197+
const result = this.replaceTextResult(node[attribute], wordlistId, statsType);
198+
if (result.modified && this.filterText) {
199+
node[attribute] = result.filtered;
200+
}
201+
}
202+
}
203+
193204
async cleanPage() {
194205
this.cfg = await WebConfig.build();
206+
this.filterText = this.cfg.filterMethod !== Constants.FILTER_METHODS.OFF;
195207
this.domain = Domain.byHostname(this.hostname, this.cfg.domains);
196208
logger.info('Config loaded', this.cfg);
197209

@@ -284,7 +296,11 @@ export default class WebFilter extends Filter {
284296

285297
if (this.cfg.showSummary) {
286298
if (this.summary[word.value]) {
287-
this.summary[word.value].count += 1;
299+
if (this.filterText) {
300+
this.summary[word.value].count += 1;
301+
} else {
302+
this.counter--; // Remove count if we've already found a match for this word when the filter is 'OFF'
303+
}
288304
} else {
289305
let result;
290306
if (word.matchMethod === Constants.MATCH_METHODS.REGEX) {
@@ -303,9 +319,11 @@ export default class WebFilter extends Filter {
303319
wordStats[word.value] = { [ Constants.STATS_TYPE_AUDIO ]: 0, [ Constants.STATS_TYPE_TEXT ]: 0 };
304320
}
305321

306-
switch(statsType) {
307-
case Constants.STATS_TYPE_AUDIO: wordStats[word.value].audio++; break;
308-
case Constants.STATS_TYPE_TEXT: wordStats[word.value].text++; break;
322+
if (this.filterText) {
323+
switch(statsType) {
324+
case Constants.STATS_TYPE_AUDIO: wordStats[word.value].audio++; break;
325+
case Constants.STATS_TYPE_TEXT: wordStats[word.value].text++; break;
326+
}
309327
}
310328
}
311329
}

src/static/optionPage.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ <h4 class="sectionHeader">Filter Method</h4>
6565
<input id="filterRemove" class="w3-radio" type="radio" name="filterMethod" value="REMOVE">
6666
Remove<span class="notes">(dog: doghouse = house)</span>
6767
</label>
68+
69+
<br/>
70+
<label>
71+
<input id="filterOff" class="w3-radio" type="radio" name="filterMethod" value="OFF">
72+
Off<span class="notes">(disabled/summary only)</span>
73+
</label>
6874
</div>
6975

7076
<div id="censorSettings" class="w3-hide">
@@ -579,6 +585,8 @@ <h4 id="substitution-mode">Substitution Mode</h4>
579585
</ul>
580586
<h4 id="remove-mode">Remove Mode</h4>
581587
<p>When selected, the offensive word will simply be removed completely from the page, with no indicator that it was ever there. Note that this can sometimes lead to strange sentences.</p>
588+
<h4 id="off-mode">Off Mode</h4>
589+
<p>This mode disables filtering and is useful for detecting words without modifying the page. You can see the list of matching words found in the extension popup. The summary will list each found word once per page. Stats will not be captured, because words aren&#39;t being filtered. This essentially results in the filter &quot;being disabled&quot; and applies to every domain. If you want filtering to resume, please choose one of the above mentioned filter modes.</p>
582590
<h3 id="global-matching-method">Global Matching Method</h3>
583591
<p>The matching method will dictate how the filter matches words from the word list. By default, the <code>Partial Match</code> mode is selected. <em>All matching methods are case-insensitive</em>.</p>
584592
<ul>
@@ -651,7 +659,8 @@ <h3 id="advanced-mode">Advanced Mode:</h3>
651659
<p><em><strong>WARNING</strong>: This mode will result in lower performance.</em></p>
652660
<p>Handle matching text that is split between multiple elements on the page. This is not usually necessary.</p>
653661
<p><em>Example:</em></p>
654-
<pre><code class="language-html">&lt;span&gt;some text to fil&lt;/span&gt;&lt;span&gt;ter&lt;/span&gt;</code></pre>
662+
<pre><code class="language-html">&lt;span&gt;some text to fil&lt;/span&gt;&lt;span&gt;ter&lt;/span&gt;
663+
</code></pre>
655664
<p>On the page, this would look like: &quot;some text to filter&quot;, but the extension (without advanced mode) would see <code>some text to fil</code>, and <code>ter</code>. If you were trying to filter the word <code>text</code>, it would work as expected (because &quot;text&quot; isn&#39;t split across elements). If you were trying to filter the word <code>filter</code>, it would fail without Advanced mode being turned on, because it doesn&#39;t match on <code>some text to fil</code>, or <code>ter</code>. This is not a common practice, and is only needed in a very small number of websites.</p>
656665
<h3 id="deep-mode">Deep Mode:</h3>
657666
<p><em><strong>WARNING</strong>: This mode will result in lower performance.</em></p>
@@ -676,7 +685,7 @@ <h1 id="audio">Audio</h1>
676685
<li>Hide all subtitles - This may make it difficult to know what is happening when the audio is muted</li>
677686
</ul>
678687
</li>
679-
<li><strong>Filler Audio</strong> - Replace the silence while muting with a filler sound. (Must use "Video volume" mute method)</li>
688+
<li><strong>Filler Audio</strong> - Replace the silence while muting with a filler sound. (Must use &quot;Video volume&quot; mute method)</li>
680689
<li><strong>Only mute audio</strong> - This will disable the text filter, and only perform filtering/muting on supported video sites.
681690
This mode will disable the text filter everywhere, and only mute audio (and filter subtitles) on supported sites.</li>
682691
<li><strong>Require subtitles to be showing</strong> - This setting is only for sites using the &quot;cue&quot; mode, and will only filter when subtitles are set to &quot;showing&quot; for the current video. If enabled, this typically means there is a manual step involved in turning on the subtitles (varies by site) to enable audio muting through the filter.</li>

0 commit comments

Comments
 (0)