@duckduckgo/autoconsent 16.50.0 → 16.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/optimize-button-patterns/SKILL.md +4 -4
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +89 -0
- package/build.sh +2 -0
- package/data/labelled-button-texts.csv +36 -36
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/content.bundle.js +48 -41
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/rule-index.json +1 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/content.bundle.js +48 -41
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/rule-index.json +1 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.cjs.js +48 -41
- package/dist/autoconsent.esm.js +48 -41
- package/dist/autoconsent.playwright.js +49 -41
- package/dist/autoconsent.standalone.js +49 -42
- package/dist/heuristics.cjs.js +886 -0
- package/dist/heuristics.mjs +847 -0
- package/dist/types/heuristic-classify.d.ts +16 -0
- package/dist/types/heuristics.d.ts +2 -9
- package/dist/types/types.d.ts +3 -5
- package/lib/heuristic-classify.ts +136 -0
- package/lib/heuristic-patterns.ts +18 -14
- package/lib/heuristics.ts +3 -124
- package/lib/types.ts +3 -5
- package/lib/web.ts +1 -1
- package/package.json +7 -2
- package/playwright/content.ts +5 -0
- package/readme.md +12 -0
- package/rules/autoconsent/abcacupuncture1.json +34 -0
- package/rules/autoconsent/aceodds.json +13 -0
- package/rules/autoconsent/allabout-japan.json +42 -0
- package/rules/autoconsent/avvio.json +22 -0
- package/rules/autoconsent/axeptio.json +5 -2
- package/rules/autoconsent/bdirect-booking-engine.json +33 -0
- package/rules/autoconsent/berelax.com.json +33 -0
- package/rules/autoconsent/bigid.json +59 -0
- package/rules/autoconsent/bluepark.json +31 -0
- package/rules/autoconsent/burpee-com.json +5 -1
- package/rules/autoconsent/bynder.json +33 -0
- package/rules/autoconsent/cinemaplus.json +9 -0
- package/rules/autoconsent/columbia-privacy-notice.json +32 -0
- package/rules/autoconsent/complianz-notice.json +1 -1
- package/rules/autoconsent/cookieconsent2-legacy.json +44 -0
- package/rules/autoconsent/cookiesjsr.json +27 -0
- package/rules/autoconsent/copaamerica.json +29 -0
- package/rules/autoconsent/cornerstone-brands.json +51 -0
- package/rules/autoconsent/darkounity.com.json +35 -0
- package/rules/autoconsent/duda-cookie-notification.json +41 -0
- package/rules/autoconsent/endocrine.org.json +35 -0
- package/rules/autoconsent/ezoic.json +2 -2
- package/rules/autoconsent/fis-data-exchange.json +13 -0
- package/rules/autoconsent/getproven.json +33 -0
- package/rules/autoconsent/givedirectly.json +36 -0
- package/rules/autoconsent/godaddy-website-builder-cosmetic.json +13 -0
- package/rules/autoconsent/godaddy-website-builder.json +9 -0
- package/rules/autoconsent/guesty.json +31 -0
- package/rules/autoconsent/gundam-base-entry.json +40 -0
- package/rules/autoconsent/hearst-us.json +1 -1
- package/rules/autoconsent/indy-systems.json +26 -0
- package/rules/autoconsent/ing-nl.json +54 -0
- package/rules/autoconsent/iubenda.json +25 -3
- package/rules/autoconsent/lucrin.json +18 -0
- package/rules/autoconsent/makitatools.json +41 -0
- package/rules/autoconsent/milestone-internet.json +32 -0
- package/rules/autoconsent/nhm-ac-uk.json +29 -0
- package/rules/autoconsent/npo-ccm.json +42 -0
- package/rules/autoconsent/opalcollection-com.json +12 -0
- package/rules/autoconsent/orw-cookie-banner.json +42 -0
- package/rules/autoconsent/osf-io.json +29 -0
- package/rules/autoconsent/palace-company.json +13 -0
- package/rules/autoconsent/paytronix.json +30 -0
- package/rules/autoconsent/pitt-edu.json +12 -0
- package/rules/autoconsent/pizzapilgrims.co.uk.json +49 -0
- package/rules/autoconsent/podimo.json +73 -0
- package/rules/autoconsent/publicmobile.json +16 -0
- package/rules/autoconsent/rakshaos.json +45 -0
- package/rules/autoconsent/rutjes.json +13 -0
- package/rules/autoconsent/saturdaysnyc.json +33 -0
- package/rules/autoconsent/serpstat-com.json +13 -0
- package/rules/autoconsent/shopquarters.json +41 -0
- package/rules/autoconsent/shopware.json +71 -0
- package/rules/autoconsent/simpleview-cookie-banner.json +27 -0
- package/rules/autoconsent/solvhealth-com.json +12 -0
- package/rules/autoconsent/spermidinelife-us.json +29 -0
- package/rules/autoconsent/squarespace-cookie-banner.json +1 -1
- package/rules/autoconsent/squarespace-cookie-notice.json +29 -0
- package/rules/autoconsent/store2000-fr.json +17 -0
- package/rules/autoconsent/tempest-cookie-notice.json +9 -0
- package/rules/autoconsent/termsfeed.json +4 -4
- package/rules/autoconsent/therealest.json +42 -0
- package/rules/autoconsent/tineye.json +35 -0
- package/rules/autoconsent/tk-aerztefuehrer.de.json +18 -0
- package/rules/autoconsent/tplink.json +57 -9
- package/rules/autoconsent/trust360.json +29 -0
- package/rules/autoconsent/up-cookie-consent.json +53 -0
- package/rules/autoconsent/uw-madison-cookieconsent.json +10 -0
- package/rules/autoconsent/vowels.json +13 -0
- package/rules/autoconsent/web-rewind.com.json +21 -0
- package/rules/autoconsent/wildling-com.json +29 -0
- package/rules/autoconsent/worldbank.json +30 -0
- package/rules/autoconsent/yuntrack.json +50 -0
- package/rules/compact-rules.json +1 -1
- package/rules/rule-index.json +1 -1
- package/rules/rules.json +1 -1
- package/scripts/benchmark-button-classification.ts +18 -3
- package/scripts/button-text-benchmark.ts +5 -5
- package/tests/abcacupuncture1.spec.ts +3 -0
- package/tests/aceodds.spec.ts +3 -0
- package/tests/allabout-japan.spec.ts +3 -0
- package/tests/avvio.spec.ts +8 -0
- package/tests/axeptio.spec.ts +3 -0
- package/tests/bdirect-booking-engine.spec.ts +9 -0
- package/tests/berelax.com.spec.ts +5 -0
- package/tests/bigid.spec.ts +8 -0
- package/tests/bluepark.spec.ts +8 -0
- package/tests/burpee-com.spec.ts +2 -0
- package/tests/bynder.spec.ts +3 -0
- package/tests/cinemaplus.spec.ts +8 -0
- package/tests/columbia-privacy-notice.spec.ts +8 -0
- package/tests/complianz-notice.spec.ts +7 -3
- package/tests/cookieconsent2-legacy.spec.ts +8 -0
- package/tests/cookiesjsr.spec.ts +9 -0
- package/tests/copaamerica.spec.ts +3 -0
- package/tests/cornerstone-brands.spec.ts +8 -0
- package/tests/darkounity.com.spec.ts +6 -0
- package/tests/duda-cookie-notification.spec.ts +8 -0
- package/tests/endocrine.org.spec.ts +3 -0
- package/tests/ezoic.spec.ts +1 -1
- package/tests/fis-data-exchange.spec.ts +9 -0
- package/tests/getproven.spec.ts +5 -0
- package/tests/givedirectly.spec.ts +3 -0
- package/tests/godaddy-website-builder-cosmetic.spec.ts +7 -0
- package/tests/godaddy-website-builder.spec.ts +8 -0
- package/tests/guesty.spec.ts +7 -0
- package/tests/gundam-base-entry.spec.ts +3 -0
- package/tests/hearst-us.spec.ts +1 -0
- package/tests/indy-systems.spec.ts +8 -0
- package/tests/ing-nl.spec.ts +6 -0
- package/tests/iubenda.spec.ts +5 -0
- package/tests/lucrin.spec.ts +3 -0
- package/tests/makitatools.spec.ts +5 -0
- package/tests/milestone-internet.spec.ts +8 -0
- package/tests/nhm-ac-uk.spec.ts +3 -0
- package/tests/npo-ccm.spec.ts +17 -0
- package/tests/opalcollection-com.spec.ts +3 -0
- package/tests/orw-cookie-banner.spec.ts +5 -0
- package/tests/osf-io.spec.ts +3 -0
- package/tests/palace-company.spec.ts +8 -0
- package/tests/paytronix.spec.ts +7 -0
- package/tests/pitt-edu.spec.ts +3 -0
- package/tests/pizzapilgrims.co.uk.spec.ts +3 -0
- package/tests/podimo.spec.ts +3 -0
- package/tests/publicmobile.spec.ts +3 -0
- package/tests/rakshaos.spec.ts +9 -0
- package/tests/rutjes.spec.ts +3 -0
- package/tests/saturdaysnyc.spec.ts +3 -0
- package/tests/serpstat-com.spec.ts +3 -0
- package/tests/shopquarters.spec.ts +5 -0
- package/tests/shopware.spec.ts +8 -0
- package/tests/simpleview-cookie-banner.spec.ts +10 -0
- package/tests/solvhealth-com.spec.ts +10 -0
- package/tests/spermidinelife-us.spec.ts +3 -0
- package/tests/squarespace-cookie-notice.spec.ts +9 -0
- package/tests/store2000-fr.spec.ts +5 -0
- package/tests/tempest-cookie-notice.spec.ts +7 -0
- package/tests/termsfeed.spec.ts +4 -0
- package/tests/therealest.spec.ts +6 -0
- package/tests/tineye.spec.ts +3 -0
- package/tests/tk-aerztefuehrer.de.spec.ts +6 -0
- package/tests/tplink.spec.ts +11 -1
- package/tests/trust360.spec.ts +8 -0
- package/tests/up-cookie-consent.spec.ts +9 -0
- package/tests/uw-madison-cookieconsent.spec.ts +7 -0
- package/tests/vowels.spec.ts +3 -0
- package/tests/web-rewind.com.spec.ts +5 -0
- package/tests/wildling-com.spec.ts +3 -0
- package/tests/worldbank.spec.ts +7 -0
- package/tests/yuntrack.spec.ts +6 -0
- package/tests-wtr/heuristics/button-classification-accuracy.test.ts +9 -5
- package/tests-wtr/heuristics/get-potential-popups.test.ts +28 -0
- package/tests-wtr/heuristics/heuristics-utils.test.ts +2 -6
- package/tsconfig.build.json +1 -1
- package/tsconfig.heuristics.json +10 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from './heuristic-patterns';
|
|
2
|
+
export type ButtonRegexClassification = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
|
|
3
|
+
export type PopupClassification = 'none' | 'reject' | 'tier1' | 'tier2';
|
|
4
|
+
export type ClassifiableButton = {
|
|
5
|
+
text: string;
|
|
6
|
+
regexClassification?: ButtonRegexClassification;
|
|
7
|
+
};
|
|
8
|
+
export declare function checkHeuristicPatterns(allText: string, detectPatterns?: RegExp[]): {
|
|
9
|
+
patterns: string[];
|
|
10
|
+
snippets: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function classifyButtons(buttons: ClassifiableButton[]): void;
|
|
13
|
+
export declare function isExcludedPopup(popupText: string, excludePatterns?: RegExp[]): boolean;
|
|
14
|
+
export declare function classifyPopup(buttons: ClassifiableButton[]): PopupClassification;
|
|
15
|
+
export declare function cleanButtonText(buttonText: string): string;
|
|
16
|
+
export declare function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import { ButtonData,
|
|
2
|
-
export declare function checkHeuristicPatterns(allText: string, detectPatterns?: RegExp[]): {
|
|
3
|
-
patterns: string[];
|
|
4
|
-
snippets: string[];
|
|
5
|
-
};
|
|
1
|
+
import { ButtonData, HeuristicLevel, PopupData } from './types';
|
|
6
2
|
export declare function getActionablePopups(mode?: HeuristicLevel, timeout?: number): PopupData[];
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function isExcludedPopup(popupText: string, excludePatterns?: RegExp[]): boolean;
|
|
9
|
-
export declare function cleanButtonText(buttonText: string): string;
|
|
10
|
-
export declare function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification;
|
|
3
|
+
export declare function getPotentialPopups(timeout?: number): PopupData[];
|
|
11
4
|
export declare function isDialogLikeElement(node: HTMLElement): boolean;
|
|
12
5
|
/**
|
|
13
6
|
* Serialize all actionable buttons on the page
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ContentScriptMessage } from './messages';
|
|
2
2
|
import { AutoConsentCMPRule, ElementSelector, HideMethod, RunContext, VisibilityCheck } from './rules';
|
|
3
3
|
import { CompactCMPRuleset } from './encoding';
|
|
4
|
+
import type { ButtonRegexClassification, ClassifiableButton, PopupClassification } from './heuristic-classify';
|
|
4
5
|
export type MessageSender = (message: ContentScriptMessage) => Promise<void>;
|
|
5
6
|
export interface AutoCMP {
|
|
6
7
|
name: string;
|
|
@@ -85,11 +86,9 @@ export type ConsentState = {
|
|
|
85
86
|
endTime: number;
|
|
86
87
|
performance?: Record<string, number[]>;
|
|
87
88
|
};
|
|
88
|
-
export type ButtonRegexClassification
|
|
89
|
-
export interface ButtonData {
|
|
90
|
-
text: string;
|
|
89
|
+
export type { ButtonRegexClassification, PopupClassification };
|
|
90
|
+
export interface ButtonData extends ClassifiableButton {
|
|
91
91
|
element: HTMLElement;
|
|
92
|
-
regexClassification?: ButtonRegexClassification;
|
|
93
92
|
}
|
|
94
93
|
export interface PopupData {
|
|
95
94
|
text: string;
|
|
@@ -105,4 +104,3 @@ export interface PopupData {
|
|
|
105
104
|
* - tier2: Heuristic detection is enabled, and will click the accept button on a popup if it exists, and no Reject, Acknowledge, or Settings button exists.
|
|
106
105
|
*/
|
|
107
106
|
export type HeuristicLevel = 'off' | 'reject' | 'tier1' | 'tier2';
|
|
108
|
-
export type PopupClassification = 'none' | 'reject' | 'tier1' | 'tier2';
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-only heuristic classification: popup text and button labels in, classifications out.
|
|
3
|
+
* Must not depend on DOM APIs, so that it can be used from Node (e.g. by tracker-radar-collector).
|
|
4
|
+
*/
|
|
5
|
+
import {
|
|
6
|
+
ACCEPT_PATTERNS,
|
|
7
|
+
ACKNOWLEDGE_PATTERNS,
|
|
8
|
+
BUTTON_NEVER_MATCH_PATTERNS,
|
|
9
|
+
DETECT_NEVER_MATCH_PATTERNS,
|
|
10
|
+
DETECT_PATTERNS,
|
|
11
|
+
REJECT_PATTERNS,
|
|
12
|
+
SETTINGS_PATTERNS,
|
|
13
|
+
} from './heuristic-patterns';
|
|
14
|
+
export * from './heuristic-patterns';
|
|
15
|
+
|
|
16
|
+
export type ButtonRegexClassification = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
|
|
17
|
+
export type PopupClassification = 'none' | 'reject' | 'tier1' | 'tier2';
|
|
18
|
+
|
|
19
|
+
export type ClassifiableButton = {
|
|
20
|
+
text: string;
|
|
21
|
+
regexClassification?: ButtonRegexClassification;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const TEXT_LIMIT = 100000;
|
|
25
|
+
|
|
26
|
+
export function checkHeuristicPatterns(allText: string, detectPatterns = DETECT_PATTERNS) {
|
|
27
|
+
allText = allText.slice(0, TEXT_LIMIT);
|
|
28
|
+
const patterns = [];
|
|
29
|
+
const snippets = [];
|
|
30
|
+
|
|
31
|
+
for (const p of detectPatterns) {
|
|
32
|
+
const matches = allText?.match(p);
|
|
33
|
+
if (matches) {
|
|
34
|
+
patterns.push(p.toString());
|
|
35
|
+
// When a non-global regex has capture groups, `String.prototype.match`
|
|
36
|
+
// returns undefined entries for optional groups that did not match.
|
|
37
|
+
for (const m of matches) {
|
|
38
|
+
if (typeof m === 'string') {
|
|
39
|
+
snippets.push(m.substring(0, 200));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { patterns, snippets };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function classifyButtons(buttons: ClassifiableButton[]) {
|
|
48
|
+
for (const button of buttons) {
|
|
49
|
+
button.regexClassification = classifyButtonTextRegex(button.text);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isExcludedPopup(popupText: string, excludePatterns = DETECT_NEVER_MATCH_PATTERNS): boolean {
|
|
54
|
+
if (!popupText) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const truncated = popupText.slice(0, TEXT_LIMIT);
|
|
58
|
+
return excludePatterns.some((p) => p.test(truncated));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function classifyPopup(buttons: ClassifiableButton[]): PopupClassification {
|
|
62
|
+
const { reject, settings, accept, acknowledge } = buttons.reduce(
|
|
63
|
+
(acc, button) => {
|
|
64
|
+
if (button.regexClassification && button.regexClassification !== 'other') {
|
|
65
|
+
acc[button.regexClassification]++;
|
|
66
|
+
}
|
|
67
|
+
return acc;
|
|
68
|
+
},
|
|
69
|
+
{ reject: 0, settings: 0, accept: 0, acknowledge: 0 },
|
|
70
|
+
);
|
|
71
|
+
if (reject > 0) {
|
|
72
|
+
return 'reject';
|
|
73
|
+
}
|
|
74
|
+
if (settings > 0) {
|
|
75
|
+
return 'none';
|
|
76
|
+
}
|
|
77
|
+
if (acknowledge > 0) {
|
|
78
|
+
return 'tier1';
|
|
79
|
+
}
|
|
80
|
+
if (accept > 0) {
|
|
81
|
+
return accept === 1 ? 'tier2' : 'none';
|
|
82
|
+
}
|
|
83
|
+
return 'none';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} buttonText
|
|
88
|
+
* @param {Array<string|RegExp>} matchPatterns
|
|
89
|
+
* @param {Array<string|RegExp>} neverMatchPatterns
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
function testButtonMatches(buttonText: string, matchPatterns: (string | RegExp)[], neverMatchPatterns: (string | RegExp)[]): boolean {
|
|
93
|
+
if (!buttonText) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const cleanedButtonText = cleanButtonText(buttonText);
|
|
97
|
+
return (
|
|
98
|
+
!neverMatchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText) &&
|
|
99
|
+
matchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function cleanButtonText(buttonText: string): string {
|
|
104
|
+
// lowercase
|
|
105
|
+
let result = buttonText.toLowerCase();
|
|
106
|
+
// remove special characters
|
|
107
|
+
result = result.replace(/[“”"'/#&[\]→✕×⟩❯><✗ב’›«»]+/g, '');
|
|
108
|
+
// remove emojis
|
|
109
|
+
result = result.replace(
|
|
110
|
+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u2600-\u26FF\u2700-\u27BF\u{1F900}-\u{1F9FF}\u{1FA70}-\u{1FAFF}]/gu,
|
|
111
|
+
'',
|
|
112
|
+
);
|
|
113
|
+
// remove newlines
|
|
114
|
+
result = result.replace(/\n+/g, ' ');
|
|
115
|
+
// remove multiple spaces
|
|
116
|
+
result = result.replace(/\s+/g, ' ');
|
|
117
|
+
// strip whitespace around the text
|
|
118
|
+
result = result.trim();
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification {
|
|
123
|
+
if (testButtonMatches(buttonText, REJECT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
124
|
+
return 'reject';
|
|
125
|
+
}
|
|
126
|
+
if (testButtonMatches(buttonText, SETTINGS_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
127
|
+
return 'settings';
|
|
128
|
+
}
|
|
129
|
+
if (testButtonMatches(buttonText, ACCEPT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
130
|
+
return 'accept';
|
|
131
|
+
}
|
|
132
|
+
if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
133
|
+
return 'acknowledge';
|
|
134
|
+
}
|
|
135
|
+
return 'other';
|
|
136
|
+
}
|
|
@@ -191,7 +191,8 @@ const REJECT_PATTERNS_ENGLISH = [
|
|
|
191
191
|
/^\s*(strictly\s+)?(necessary|essential|essentials|functional|required|minimal)\s+((cookies\s+)?only|only\s+cookies)\s*$/is,
|
|
192
192
|
|
|
193
193
|
// e.g. "do not sell or share my personal information", "do not share my personal information", "opt out of sale ..." (CCPA)
|
|
194
|
-
|
|
194
|
+
// "do not sell page" is a link; "do not sellcookie settings" is two concatenated buttons
|
|
195
|
+
/do\s+not\s+sell(?!\s*(page|cookie settings))|do\s+not\s+share\s+my\s+(personal\s+)?(information|info|data)|opt\s+out\s+of\s+sale/is,
|
|
195
196
|
|
|
196
197
|
// e.g. "opt-out of sale/share or targeted advertising", "opt-out of advertising/social media cookies"
|
|
197
198
|
/^opt[ -]?out of /is,
|
|
@@ -206,7 +207,6 @@ const REJECT_PATTERNS_ENGLISH = [
|
|
|
206
207
|
/^no,? thank(s| you)$/is,
|
|
207
208
|
/^opt[ -]out$/is,
|
|
208
209
|
'dont enable',
|
|
209
|
-
'withdraw consent',
|
|
210
210
|
'i do not agree',
|
|
211
211
|
];
|
|
212
212
|
|
|
@@ -241,10 +241,11 @@ const REJECT_PATTERNS_DUTCH = [
|
|
|
241
241
|
const REJECT_PATTERNS_FRENCH = [
|
|
242
242
|
// refuser / rejeter / interdire / décliner (reject verbs, any position)
|
|
243
243
|
// "refuser et s'abonner" / "refuser et payer" are excluded via BUTTON_NEVER_MATCH_PATTERNS
|
|
244
|
-
|
|
244
|
+
// skip English prose such as "cookies that you can refuse"
|
|
245
|
+
/^(?!.*\bcan refuse\b)(.*\s)?(refus|rejet|rejeter|interdire|interdis|déclin|declin)/is,
|
|
245
246
|
|
|
246
247
|
// only necessary / essential / technical / functional
|
|
247
|
-
|
|
248
|
+
/^(?=.*(cookie|nécessaire|essentiel|techni|requis|obligatoire|fonctionnel|indispensable|sdk)).*(uniquement|seulement|indispensable|strictement nécessaire|que les cookies\s+(nécessaires|techniques|essentiels|indispensables|fonctionnels))/is,
|
|
248
249
|
|
|
249
250
|
// continue/proceed without accepting; refuse everything; disable purposes
|
|
250
251
|
/(sans accepter|ne pas accepter|je naccepte rien|je désactive)/is,
|
|
@@ -323,7 +324,7 @@ const REJECT_PATTERNS_SPANISH = [
|
|
|
323
324
|
/^(no,?\s+)?(solo|sólo|només)\s+(usar\s+|las?\s+|los\s+|lo\s+)?.{0,20}(necesari|esencial|estrictamente)/is,
|
|
324
325
|
|
|
325
326
|
// refusals / opt-outs
|
|
326
|
-
/^(no acept|no consentir|no permitir|no estoy de acuerdo|no,? gracias|sin consentimiento|
|
|
327
|
+
/^(no acept|no consentir|no permitir|no estoy de acuerdo|no,? gracias|sin consentimiento|continuar sin aceptar|prefiero rechazarlas|descartar todas)/is,
|
|
327
328
|
|
|
328
329
|
'acceptar només les necessàries',
|
|
329
330
|
'només sutilitzen cookies quan és necessari',
|
|
@@ -427,6 +428,9 @@ export const REJECT_PATTERNS = [
|
|
|
427
428
|
];
|
|
428
429
|
|
|
429
430
|
export const BUTTON_NEVER_MATCH_PATTERNS = [
|
|
431
|
+
// text of several concatenated buttons, or popup body text rather than a button
|
|
432
|
+
/accept all.*reject all|reject all.*accept all/is,
|
|
433
|
+
/^.{300,}$/s,
|
|
430
434
|
/pay|subscribe/is,
|
|
431
435
|
/abonneer/is,
|
|
432
436
|
/abonnier/is,
|
|
@@ -474,9 +478,11 @@ export const SETTINGS_PATTERNS = [
|
|
|
474
478
|
// Multilingual "open customization" patterns: a customization verb next to a
|
|
475
479
|
// cookie/preference/settings/options/details/purposes noun (both word orders).
|
|
476
480
|
// The negative lookahead avoids policy links and save/confirm/accept phrases (those are accept/acknowledge/other).
|
|
477
|
-
/^(?!.*\b(policy|policies|notice|statement|impressum
|
|
478
|
-
/^(?!.*\b(policy|policies|notice|statement|impressum
|
|
481
|
+
/^(?!.*\b(policy|policies|notice|statement|impressum|\w*richtlinie\w*|\w*erklärung|page|beschrijving|privacy$|beleid|politique|política|polityk|speichern|guardar|opslaan|zapisz|enregistrer|sauvegarder|bevestig|bestätig|confirm|save|submit|akzeptier|accept|zaakcept)\b)(customi[sz]e|manage|adjust|configure|personali[sz]e|let me choose|edit|change|set|select|view|see|review|update|open|show|choose|anpassen|verwalten|konfigurieren|bearbeiten|öffnen|anzeigen|einblenden|einstellen|festlegen|auswählen|wählen|aanpassen|beheren|instellen|wijzig\w*|personaliseren|personaliseer|kies|bekijk|toon|personnaliser|paramétrer|gérer|configurer|choisir|afficher|définir|modifier|configurar|personalizar|gestionar|administrar|ajustar|seleccionar|modificar|establecer|dostosuj|zarządzaj|personalizuj|ustaw\w*|zmień|pokaż|wybierz)\b.{0,20}(cookies?|preferences?|settings?|options?|choices?|controls?|details?|purposes?|services?|consent|einstellung\w*|optionen|präferenzen|einzelheiten|zwecke|dienste|datenschutz\w*|auswahl|voorkeur\w*|instelling\w*|opties|diensten|préférences|paramètres|réglages|choix|détails|finalités|témoins|preferencia\w*|opciones|ajustes|configuraci\w*|detalles|servicios|elección|preferencj\w*|ustawie\w*|opcje|szczegół\w*|cele|galetes)\b/is,
|
|
482
|
+
/^(?!.*\b(policy|policies|notice|statement|impressum|\w*richtlinie\w*|\w*erklärung|page|beschrijving|privacy$|beleid|politique|política|polityk|speichern|guardar|opslaan|zapisz|enregistrer|sauvegarder|bevestig|bestätig|confirm|save|submit|akzeptier|accept|zaakcept)\b)(cookies?|preferences?|settings?|options?|choices?|controls?|details?|purposes?|services?|consent|einstellung\w*|optionen|präferenzen|einzelheiten|zwecke|dienste|datenschutz\w*|auswahl|voorkeur\w*|instelling\w*|opties|diensten|préférences|paramètres|réglages|choix|détails|finalités|témoins|preferencia\w*|opciones|ajustes|configuraci\w*|detalles|servicios|elección|preferencj\w*|ustawie\w*|opcje|szczegół\w*|cele|galetes)\b(.{0,14}\W)?(customi[sz]e|manage|adjust|configure|personali[sz]e|let me choose|edit|change|set|select|view|see|review|update|open|show|choose|anpassen|verwalten|konfigurieren|bearbeiten|öffnen|anzeigen|einblenden|einstellen|festlegen|auswählen|wählen|aanpassen|beheren|instellen|wijzig\w*|personaliseren|personaliseer|kies|bekijk|toon|personnaliser|paramétrer|gérer|configurer|choisir|afficher|définir|modifier|configurar|personalizar|gestionar|administrar|ajustar|seleccionar|modificar|establecer|dostosuj|zarządzaj|personalizuj|ustaw\w*|zmień|pokaż|wybierz)\b/is,
|
|
479
483
|
|
|
484
|
+
// settings noun + cookies, e.g. "paramètres des cookies", "ajustes de cookies", "instellingen voor cookies en diensten"
|
|
485
|
+
/^(paramètres|préférences|réglages|ajustes|opciones|elección|preferencias|instellingen)( (de|des|les|voor|van))? cookies( (et des|en|y) (services|diensten|servicios))?$/is,
|
|
480
486
|
'settings',
|
|
481
487
|
'preferences',
|
|
482
488
|
/customi(s|z)e/is,
|
|
@@ -490,8 +496,8 @@ export const SETTINGS_PATTERNS = [
|
|
|
490
496
|
'cookieconsent preferences',
|
|
491
497
|
/privacy choices/is,
|
|
492
498
|
/^(privacy|cookie|custom) settings$/is,
|
|
493
|
-
/^cookies? (settings|preferences|setting)
|
|
494
|
-
|
|
499
|
+
/^cookies? (settings|preferences|setting)(?!.*\bdo not\b)/is,
|
|
500
|
+
/^(?!.*\bpolic(y|ies)\b).*(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
|
|
495
501
|
'cookie consent options',
|
|
496
502
|
'privacy controls',
|
|
497
503
|
// German
|
|
@@ -682,7 +688,7 @@ export const ACCEPT_PATTERNS = [
|
|
|
682
688
|
'yes',
|
|
683
689
|
/accept all above/is,
|
|
684
690
|
'close and accept',
|
|
685
|
-
/accept all$/is,
|
|
691
|
+
/(^|\s)accept all$/is,
|
|
686
692
|
'im ok with that',
|
|
687
693
|
|
|
688
694
|
// Spanish (ES)
|
|
@@ -710,6 +716,8 @@ export const ACCEPT_PATTERNS = [
|
|
|
710
716
|
'accepta',
|
|
711
717
|
'accepta totes les cookies',
|
|
712
718
|
'accepta-ho tot',
|
|
719
|
+
// FR/CA "d'accord" / "d'acord"
|
|
720
|
+
/^dac?cord( fermer)?$/is,
|
|
713
721
|
'accepta-les totes',
|
|
714
722
|
'acceptar galetes',
|
|
715
723
|
'acceptar i tancar',
|
|
@@ -758,7 +766,6 @@ export const ACCEPT_PATTERNS = [
|
|
|
758
766
|
'akceptuję!',
|
|
759
767
|
'ok, zgadzam się',
|
|
760
768
|
'potwierdzam wszystkie',
|
|
761
|
-
'przejdź do serwisu',
|
|
762
769
|
'tak',
|
|
763
770
|
'tak, zgadzam się na wszystkie pliki cookie',
|
|
764
771
|
'tak, zgadzam się',
|
|
@@ -841,9 +848,6 @@ export const ACKNOWLEDGE_PATTERNS = [
|
|
|
841
848
|
'vale',
|
|
842
849
|
'¡vamos!',
|
|
843
850
|
|
|
844
|
-
// Catalan (CA)
|
|
845
|
-
'dacord',
|
|
846
|
-
|
|
847
851
|
// Polish (PL)
|
|
848
852
|
'kontynuuj',
|
|
849
853
|
'ok, zrozumiałem',
|
package/lib/heuristics.ts
CHANGED
|
@@ -1,40 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
ACKNOWLEDGE_PATTERNS,
|
|
4
|
-
BUTTON_NEVER_MATCH_PATTERNS,
|
|
5
|
-
DETECT_NEVER_MATCH_PATTERNS,
|
|
6
|
-
DETECT_PATTERNS,
|
|
7
|
-
REJECT_PATTERNS,
|
|
8
|
-
SETTINGS_PATTERNS,
|
|
9
|
-
} from './heuristic-patterns';
|
|
10
|
-
import { ButtonData, ButtonRegexClassification, HeuristicLevel, PopupClassification, PopupData } from './types';
|
|
1
|
+
import { checkHeuristicPatterns, classifyButtons, classifyPopup, isExcludedPopup } from './heuristic-classify';
|
|
2
|
+
import { ButtonData, HeuristicLevel, PopupClassification, PopupData } from './types';
|
|
11
3
|
import { isElementVisible, isTopFrame } from './utils';
|
|
12
4
|
|
|
13
5
|
const BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
|
|
14
|
-
const TEXT_LIMIT = 100000;
|
|
15
6
|
const POPUP_SEARCH_MAX_TIME = 100;
|
|
16
7
|
|
|
17
|
-
export function checkHeuristicPatterns(allText: string, detectPatterns = DETECT_PATTERNS) {
|
|
18
|
-
allText = allText.slice(0, TEXT_LIMIT);
|
|
19
|
-
const patterns = [];
|
|
20
|
-
const snippets = [];
|
|
21
|
-
|
|
22
|
-
for (const p of detectPatterns) {
|
|
23
|
-
const matches = allText?.match(p);
|
|
24
|
-
if (matches) {
|
|
25
|
-
patterns.push(p.toString());
|
|
26
|
-
// When a non-global regex has capture groups, `String.prototype.match`
|
|
27
|
-
// returns undefined entries for optional groups that did not match.
|
|
28
|
-
for (const m of matches) {
|
|
29
|
-
if (typeof m === 'string') {
|
|
30
|
-
snippets.push(m.substring(0, 200));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return { patterns, snippets };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
8
|
export function getActionablePopups(mode: HeuristicLevel = 'reject', timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
|
|
39
9
|
const acceptedLevels: PopupClassification[] =
|
|
40
10
|
mode === 'tier2' ? ['reject', 'tier1', 'tier2'] : mode === 'tier1' ? ['reject', 'tier1'] : ['reject'];
|
|
@@ -65,98 +35,7 @@ export function getActionablePopups(mode: HeuristicLevel = 'reject', timeout = P
|
|
|
65
35
|
.sort((a, b) => ((a.regexClassification ?? '') > (b.regexClassification ?? '') ? 1 : -1));
|
|
66
36
|
}
|
|
67
37
|
|
|
68
|
-
export function
|
|
69
|
-
for (const button of buttons) {
|
|
70
|
-
button.regexClassification = classifyButtonTextRegex(button.text);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function isExcludedPopup(popupText: string, excludePatterns = DETECT_NEVER_MATCH_PATTERNS): boolean {
|
|
75
|
-
if (!popupText) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
const truncated = popupText.slice(0, TEXT_LIMIT);
|
|
79
|
-
return excludePatterns.some((p) => p.test(truncated));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function classifyPopup(buttons: ButtonData[]): PopupClassification {
|
|
83
|
-
const { reject, settings, accept, acknowledge } = buttons.reduce(
|
|
84
|
-
(acc, button) => {
|
|
85
|
-
if (button.regexClassification && button.regexClassification !== 'other') {
|
|
86
|
-
acc[button.regexClassification]++;
|
|
87
|
-
}
|
|
88
|
-
return acc;
|
|
89
|
-
},
|
|
90
|
-
{ reject: 0, settings: 0, accept: 0, acknowledge: 0 },
|
|
91
|
-
);
|
|
92
|
-
if (reject > 0) {
|
|
93
|
-
return 'reject';
|
|
94
|
-
}
|
|
95
|
-
if (settings > 0) {
|
|
96
|
-
return 'none';
|
|
97
|
-
}
|
|
98
|
-
if (acknowledge > 0) {
|
|
99
|
-
return 'tier1';
|
|
100
|
-
}
|
|
101
|
-
if (accept > 0) {
|
|
102
|
-
return accept === 1 ? 'tier2' : 'none';
|
|
103
|
-
}
|
|
104
|
-
return 'none';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @param {string} buttonText
|
|
109
|
-
* @param {Array<string|RegExp>} matchPatterns
|
|
110
|
-
* @param {Array<string|RegExp>} neverMatchPatterns
|
|
111
|
-
* @returns {boolean}
|
|
112
|
-
*/
|
|
113
|
-
function testButtonMatches(buttonText: string, matchPatterns: (string | RegExp)[], neverMatchPatterns: (string | RegExp)[]): boolean {
|
|
114
|
-
if (!buttonText) {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
const cleanedButtonText = cleanButtonText(buttonText);
|
|
118
|
-
return (
|
|
119
|
-
!neverMatchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText) &&
|
|
120
|
-
matchPatterns.some((p) => (p instanceof RegExp && p.test(cleanedButtonText)) || p === cleanedButtonText)
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function cleanButtonText(buttonText: string): string {
|
|
125
|
-
// lowercase
|
|
126
|
-
let result = buttonText.toLowerCase();
|
|
127
|
-
// remove special characters
|
|
128
|
-
result = result.replace(/[“”"'/#&[\]→✕×⟩❯><✗ב’›«»]+/g, '');
|
|
129
|
-
// remove emojis
|
|
130
|
-
result = result.replace(
|
|
131
|
-
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u2600-\u26FF\u2700-\u27BF\u{1F900}-\u{1F9FF}\u{1FA70}-\u{1FAFF}]/gu,
|
|
132
|
-
'',
|
|
133
|
-
);
|
|
134
|
-
// remove newlines
|
|
135
|
-
result = result.replace(/\n+/g, ' ');
|
|
136
|
-
// remove multiple spaces
|
|
137
|
-
result = result.replace(/\s+/g, ' ');
|
|
138
|
-
// strip whitespace around the text
|
|
139
|
-
result = result.trim();
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification {
|
|
144
|
-
if (testButtonMatches(buttonText, REJECT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
145
|
-
return 'reject';
|
|
146
|
-
}
|
|
147
|
-
if (testButtonMatches(buttonText, SETTINGS_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
148
|
-
return 'settings';
|
|
149
|
-
}
|
|
150
|
-
if (testButtonMatches(buttonText, ACCEPT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
151
|
-
return 'accept';
|
|
152
|
-
}
|
|
153
|
-
if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
|
|
154
|
-
return 'acknowledge';
|
|
155
|
-
}
|
|
156
|
-
return 'other';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
|
|
38
|
+
export function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME): PopupData[] {
|
|
160
39
|
const isFramed = !isTopFrame();
|
|
161
40
|
// do not inspect frames that are more than one level deep
|
|
162
41
|
if (isFramed && window.parent && window.parent !== window.top) {
|
package/lib/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ContentScriptMessage } from './messages';
|
|
2
2
|
import { AutoConsentCMPRule, ElementSelector, HideMethod, RunContext, VisibilityCheck } from './rules';
|
|
3
3
|
import { CompactCMPRuleset } from './encoding';
|
|
4
|
+
import type { ButtonRegexClassification, ClassifiableButton, PopupClassification } from './heuristic-classify';
|
|
4
5
|
|
|
5
6
|
export type MessageSender = (message: ContentScriptMessage) => Promise<void>;
|
|
6
7
|
|
|
@@ -114,12 +115,10 @@ export type ConsentState = {
|
|
|
114
115
|
performance?: Record<string, number[]>;
|
|
115
116
|
};
|
|
116
117
|
|
|
117
|
-
export type ButtonRegexClassification
|
|
118
|
+
export type { ButtonRegexClassification, PopupClassification };
|
|
118
119
|
|
|
119
|
-
export interface ButtonData {
|
|
120
|
-
text: string;
|
|
120
|
+
export interface ButtonData extends ClassifiableButton {
|
|
121
121
|
element: HTMLElement;
|
|
122
|
-
regexClassification?: ButtonRegexClassification;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
export interface PopupData {
|
|
@@ -137,4 +136,3 @@ export interface PopupData {
|
|
|
137
136
|
* - tier2: Heuristic detection is enabled, and will click the accept button on a popup if it exists, and no Reject, Acknowledge, or Settings button exists.
|
|
138
137
|
*/
|
|
139
138
|
export type HeuristicLevel = 'off' | 'reject' | 'tier1' | 'tier2';
|
|
140
|
-
export type PopupClassification = 'none' | 'reject' | 'tier1' | 'tier2';
|
package/lib/web.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { dynamicCMPs } from './cmps/all';
|
|
|
7
7
|
import { AutoConsentCMP, AutoConsentHeuristicCMP } from './cmps/base';
|
|
8
8
|
import { DomActions } from './dom-actions';
|
|
9
9
|
import { isTopFrame, normalizeConfig, scheduleWhenIdle } from './utils';
|
|
10
|
-
import { checkHeuristicPatterns } from './
|
|
10
|
+
import { checkHeuristicPatterns } from './heuristic-classify';
|
|
11
11
|
import { decodeRules } from './encoding';
|
|
12
12
|
|
|
13
13
|
// Re-export types and functions for external use
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duckduckgo/autoconsent",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.51.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"types": "./dist/types/web.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"import": "./dist/autoconsent.esm.js",
|
|
10
10
|
"require": "./dist/autoconsent.cjs.js"
|
|
11
11
|
},
|
|
12
|
+
"./heuristics": {
|
|
13
|
+
"types": "./dist/types/heuristic-classify.d.ts",
|
|
14
|
+
"import": "./dist/heuristics.mjs",
|
|
15
|
+
"require": "./dist/heuristics.cjs.js"
|
|
16
|
+
},
|
|
12
17
|
"./rules/rules.json": "./rules/rules.json",
|
|
13
18
|
"./rules/compact-rules.json": "./rules/compact-rules.json"
|
|
14
19
|
},
|
|
@@ -17,7 +22,7 @@
|
|
|
17
22
|
},
|
|
18
23
|
"scripts": {
|
|
19
24
|
"clean": "rm -r dist",
|
|
20
|
-
"lint": "eslint . && prettier . --check && npm run rule-syntax-check",
|
|
25
|
+
"lint": "eslint . && prettier . --check && npm run rule-syntax-check && tsc -p tsconfig.heuristics.json",
|
|
21
26
|
"lint-fix": "eslint . --fix && prettier . --write && npm run rule-syntax-check",
|
|
22
27
|
"bundle": "./build.sh",
|
|
23
28
|
"rule-syntax-check": "node scripts/validate-json-rules.js",
|
package/playwright/content.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import AutoConsent from '../lib/web';
|
|
2
2
|
import { BackgroundMessage } from '../lib/messages';
|
|
3
3
|
import { MessageSender, RuleBundle } from '../lib/types';
|
|
4
|
+
import { getButtonData, getPotentialPopups } from '../lib/heuristics';
|
|
4
5
|
|
|
5
6
|
declare global {
|
|
6
7
|
interface Window {
|
|
7
8
|
autoconsentSendMessage: MessageSender;
|
|
8
9
|
autoconsentReceiveMessage: (message: BackgroundMessage) => Promise<void>;
|
|
10
|
+
autoconsentHeuristics: { getPotentialPopups: typeof getPotentialPopups; getButtonData: typeof getButtonData };
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
// lets other scripts in the same world (e.g. crawler scrapers) reuse popup and button discovery
|
|
15
|
+
window.autoconsentHeuristics = { getPotentialPopups, getButtonData };
|
|
16
|
+
|
|
12
17
|
if (!window.autoconsentReceiveMessage) {
|
|
13
18
|
const consent = new AutoConsent(window.autoconsentSendMessage, null, <RuleBundle>{ autoconsent: [] });
|
|
14
19
|
|
package/readme.md
CHANGED
|
@@ -32,6 +32,18 @@ In some environments (e.g. browser extensions), it's useful to access [eval snip
|
|
|
32
32
|
import { evalSnippets } from '@duckduckgo/autoconsent';
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
### Heuristics
|
|
36
|
+
|
|
37
|
+
The heuristic popup and button classification is available on its own, without DOM dependencies, so it can run in Node (e.g. to post-process crawl data):
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import { checkHeuristicPatterns, classifyButtonTextRegex, classifyButtons, classifyPopup } from '@duckduckgo/autoconsent/heuristics';
|
|
41
|
+
|
|
42
|
+
classifyButtonTextRegex('Reject all'); // 'reject'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`dist/autoconsent.playwright.js` also exposes `window.autoconsentHeuristics.getPotentialPopups(timeout)` and `getButtonData(element)`, so other scripts injected into the same world can reuse autoconsent's popup and button discovery.
|
|
46
|
+
|
|
35
47
|
## Browser extension
|
|
36
48
|
|
|
37
49
|
Autoconsent comes with a reference extension implementation. It is not published in stores since the feature is available in all [DuckDuckGo apps](https://duckduckgo.com/app), but you can build it yourself and use for testing.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "abcacupuncture1",
|
|
3
|
+
"vendorUrl": "https://abcacupuncture1.com/",
|
|
4
|
+
"runContext": {
|
|
5
|
+
"urlPattern": "^https?://(www\\.)?abcacupuncture1\\.com/"
|
|
6
|
+
},
|
|
7
|
+
"prehideSelectors": [".cky-overlay,.cky-consent-container"],
|
|
8
|
+
"detectCmp": [{ "any": [{ "exists": ".cky-consent-container" }, { "exists": ".fc-consent-root" }] }],
|
|
9
|
+
"detectPopup": [{ "any": [{ "visible": ".cky-consent-container" }, { "visible": ".fc-consent-root .fc-dialog-container" }] }],
|
|
10
|
+
"optIn": [
|
|
11
|
+
{ "click": ".fc-consent-root .fc-cta-consent", "optional": true },
|
|
12
|
+
{ "waitForThenClick": ".cky-consent-container [data-cky-tag=accept-button]", "timeout": 2000, "optional": true }
|
|
13
|
+
],
|
|
14
|
+
"optOut": [
|
|
15
|
+
{
|
|
16
|
+
"if": { "visible": ".cky-consent-container [data-cky-tag=reject-button]" },
|
|
17
|
+
"then": [{ "click": ".cky-consent-container [data-cky-tag=reject-button]" }]
|
|
18
|
+
},
|
|
19
|
+
{ "waitForVisible": ".fc-consent-root .fc-dialog-container", "timeout": 4000, "optional": true },
|
|
20
|
+
{
|
|
21
|
+
"if": { "visible": ".fc-consent-root .fc-dialog-container" },
|
|
22
|
+
"then": [
|
|
23
|
+
{ "click": ".fc-cta-do-not-consent,.fc-cta-manage-options" },
|
|
24
|
+
{ "click": ".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked", "all": true, "optional": true },
|
|
25
|
+
{ "click": ".fc-confirm-choices", "optional": true }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"if": { "visible": ".cky-consent-container [data-cky-tag=reject-button]" },
|
|
30
|
+
"then": [{ "waitForThenClick": ".cky-consent-container [data-cky-tag=reject-button]" }]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"test": [{ "cookieContains": "advertisement:no" }]
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aceodds.com",
|
|
3
|
+
"runContext": { "urlPattern": "^https?://(www\\.)?aceodds\\.com/" },
|
|
4
|
+
"prehideSelectors": ["#cookie-consent-banner"],
|
|
5
|
+
"detectCmp": [{ "exists": "#cookie-consent-banner #cookie-consent-accept" }],
|
|
6
|
+
"detectPopup": [{ "visible": "#cookie-consent-banner" }],
|
|
7
|
+
"optIn": [{ "waitForThenClick": "#cookie-consent-accept", "retry": 10, "retryInterval": 500 }],
|
|
8
|
+
"optOut": [
|
|
9
|
+
{ "waitForThenClick": "#cookie-consent-accept", "retry": 10, "retryInterval": 500 },
|
|
10
|
+
{ "waitForVisible": "#cookie-consent-banner", "check": "none" }
|
|
11
|
+
],
|
|
12
|
+
"test": [{ "cookieContains": "cookie-consent=1" }]
|
|
13
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "allabout-japan",
|
|
3
|
+
"vendorUrl": "https://allabout-japan.com/",
|
|
4
|
+
"runContext": {
|
|
5
|
+
"urlPattern": "^https?://(\\w+\\.)?allabout-japan\\.com/"
|
|
6
|
+
},
|
|
7
|
+
"prehideSelectors": [".cookie-message"],
|
|
8
|
+
"detectCmp": [{ "exists": ".cookie-message .cookie-agree" }],
|
|
9
|
+
"detectPopup": [{ "visible": ".cookie-message .cookie-agree" }],
|
|
10
|
+
"optIn": [
|
|
11
|
+
{
|
|
12
|
+
"waitForVisible": ".fc-consent-root .fc-dialog-container",
|
|
13
|
+
"timeout": 8000,
|
|
14
|
+
"optional": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"if": { "visible": ".fc-consent-root .fc-dialog-container" },
|
|
18
|
+
"then": [{ "click": ".fc-cta-consent" }]
|
|
19
|
+
},
|
|
20
|
+
{ "waitForThenClick": ".cookie-message .cookie-agree" }
|
|
21
|
+
],
|
|
22
|
+
"optOut": [
|
|
23
|
+
{ "hide": ".cookie-message" },
|
|
24
|
+
{
|
|
25
|
+
"waitForVisible": ".fc-consent-root .fc-dialog-container",
|
|
26
|
+
"timeout": 8000,
|
|
27
|
+
"optional": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"if": { "visible": ".fc-consent-root .fc-dialog-container" },
|
|
31
|
+
"then": [
|
|
32
|
+
{ "click": ".fc-cta-do-not-consent,.fc-cta-manage-options" },
|
|
33
|
+
{
|
|
34
|
+
"click": ".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked",
|
|
35
|
+
"all": true,
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
{ "click": ".fc-confirm-choices", "optional": true }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|