@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.
Files changed (187) hide show
  1. package/.agents/skills/optimize-button-patterns/SKILL.md +4 -4
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +89 -0
  4. package/build.sh +2 -0
  5. package/data/labelled-button-texts.csv +36 -36
  6. package/dist/addon-firefox/compact-rules.json +1 -1
  7. package/dist/addon-firefox/content.bundle.js +48 -41
  8. package/dist/addon-firefox/manifest.json +1 -1
  9. package/dist/addon-firefox/rule-index.json +1 -1
  10. package/dist/addon-firefox/rules.json +1 -1
  11. package/dist/addon-mv3/compact-rules.json +1 -1
  12. package/dist/addon-mv3/content.bundle.js +48 -41
  13. package/dist/addon-mv3/manifest.json +1 -1
  14. package/dist/addon-mv3/rule-index.json +1 -1
  15. package/dist/addon-mv3/rules.json +1 -1
  16. package/dist/autoconsent.cjs.js +48 -41
  17. package/dist/autoconsent.esm.js +48 -41
  18. package/dist/autoconsent.playwright.js +49 -41
  19. package/dist/autoconsent.standalone.js +49 -42
  20. package/dist/heuristics.cjs.js +886 -0
  21. package/dist/heuristics.mjs +847 -0
  22. package/dist/types/heuristic-classify.d.ts +16 -0
  23. package/dist/types/heuristics.d.ts +2 -9
  24. package/dist/types/types.d.ts +3 -5
  25. package/lib/heuristic-classify.ts +136 -0
  26. package/lib/heuristic-patterns.ts +18 -14
  27. package/lib/heuristics.ts +3 -124
  28. package/lib/types.ts +3 -5
  29. package/lib/web.ts +1 -1
  30. package/package.json +7 -2
  31. package/playwright/content.ts +5 -0
  32. package/readme.md +12 -0
  33. package/rules/autoconsent/abcacupuncture1.json +34 -0
  34. package/rules/autoconsent/aceodds.json +13 -0
  35. package/rules/autoconsent/allabout-japan.json +42 -0
  36. package/rules/autoconsent/avvio.json +22 -0
  37. package/rules/autoconsent/axeptio.json +5 -2
  38. package/rules/autoconsent/bdirect-booking-engine.json +33 -0
  39. package/rules/autoconsent/berelax.com.json +33 -0
  40. package/rules/autoconsent/bigid.json +59 -0
  41. package/rules/autoconsent/bluepark.json +31 -0
  42. package/rules/autoconsent/burpee-com.json +5 -1
  43. package/rules/autoconsent/bynder.json +33 -0
  44. package/rules/autoconsent/cinemaplus.json +9 -0
  45. package/rules/autoconsent/columbia-privacy-notice.json +32 -0
  46. package/rules/autoconsent/complianz-notice.json +1 -1
  47. package/rules/autoconsent/cookieconsent2-legacy.json +44 -0
  48. package/rules/autoconsent/cookiesjsr.json +27 -0
  49. package/rules/autoconsent/copaamerica.json +29 -0
  50. package/rules/autoconsent/cornerstone-brands.json +51 -0
  51. package/rules/autoconsent/darkounity.com.json +35 -0
  52. package/rules/autoconsent/duda-cookie-notification.json +41 -0
  53. package/rules/autoconsent/endocrine.org.json +35 -0
  54. package/rules/autoconsent/ezoic.json +2 -2
  55. package/rules/autoconsent/fis-data-exchange.json +13 -0
  56. package/rules/autoconsent/getproven.json +33 -0
  57. package/rules/autoconsent/givedirectly.json +36 -0
  58. package/rules/autoconsent/godaddy-website-builder-cosmetic.json +13 -0
  59. package/rules/autoconsent/godaddy-website-builder.json +9 -0
  60. package/rules/autoconsent/guesty.json +31 -0
  61. package/rules/autoconsent/gundam-base-entry.json +40 -0
  62. package/rules/autoconsent/hearst-us.json +1 -1
  63. package/rules/autoconsent/indy-systems.json +26 -0
  64. package/rules/autoconsent/ing-nl.json +54 -0
  65. package/rules/autoconsent/iubenda.json +25 -3
  66. package/rules/autoconsent/lucrin.json +18 -0
  67. package/rules/autoconsent/makitatools.json +41 -0
  68. package/rules/autoconsent/milestone-internet.json +32 -0
  69. package/rules/autoconsent/nhm-ac-uk.json +29 -0
  70. package/rules/autoconsent/npo-ccm.json +42 -0
  71. package/rules/autoconsent/opalcollection-com.json +12 -0
  72. package/rules/autoconsent/orw-cookie-banner.json +42 -0
  73. package/rules/autoconsent/osf-io.json +29 -0
  74. package/rules/autoconsent/palace-company.json +13 -0
  75. package/rules/autoconsent/paytronix.json +30 -0
  76. package/rules/autoconsent/pitt-edu.json +12 -0
  77. package/rules/autoconsent/pizzapilgrims.co.uk.json +49 -0
  78. package/rules/autoconsent/podimo.json +73 -0
  79. package/rules/autoconsent/publicmobile.json +16 -0
  80. package/rules/autoconsent/rakshaos.json +45 -0
  81. package/rules/autoconsent/rutjes.json +13 -0
  82. package/rules/autoconsent/saturdaysnyc.json +33 -0
  83. package/rules/autoconsent/serpstat-com.json +13 -0
  84. package/rules/autoconsent/shopquarters.json +41 -0
  85. package/rules/autoconsent/shopware.json +71 -0
  86. package/rules/autoconsent/simpleview-cookie-banner.json +27 -0
  87. package/rules/autoconsent/solvhealth-com.json +12 -0
  88. package/rules/autoconsent/spermidinelife-us.json +29 -0
  89. package/rules/autoconsent/squarespace-cookie-banner.json +1 -1
  90. package/rules/autoconsent/squarespace-cookie-notice.json +29 -0
  91. package/rules/autoconsent/store2000-fr.json +17 -0
  92. package/rules/autoconsent/tempest-cookie-notice.json +9 -0
  93. package/rules/autoconsent/termsfeed.json +4 -4
  94. package/rules/autoconsent/therealest.json +42 -0
  95. package/rules/autoconsent/tineye.json +35 -0
  96. package/rules/autoconsent/tk-aerztefuehrer.de.json +18 -0
  97. package/rules/autoconsent/tplink.json +57 -9
  98. package/rules/autoconsent/trust360.json +29 -0
  99. package/rules/autoconsent/up-cookie-consent.json +53 -0
  100. package/rules/autoconsent/uw-madison-cookieconsent.json +10 -0
  101. package/rules/autoconsent/vowels.json +13 -0
  102. package/rules/autoconsent/web-rewind.com.json +21 -0
  103. package/rules/autoconsent/wildling-com.json +29 -0
  104. package/rules/autoconsent/worldbank.json +30 -0
  105. package/rules/autoconsent/yuntrack.json +50 -0
  106. package/rules/compact-rules.json +1 -1
  107. package/rules/rule-index.json +1 -1
  108. package/rules/rules.json +1 -1
  109. package/scripts/benchmark-button-classification.ts +18 -3
  110. package/scripts/button-text-benchmark.ts +5 -5
  111. package/tests/abcacupuncture1.spec.ts +3 -0
  112. package/tests/aceodds.spec.ts +3 -0
  113. package/tests/allabout-japan.spec.ts +3 -0
  114. package/tests/avvio.spec.ts +8 -0
  115. package/tests/axeptio.spec.ts +3 -0
  116. package/tests/bdirect-booking-engine.spec.ts +9 -0
  117. package/tests/berelax.com.spec.ts +5 -0
  118. package/tests/bigid.spec.ts +8 -0
  119. package/tests/bluepark.spec.ts +8 -0
  120. package/tests/burpee-com.spec.ts +2 -0
  121. package/tests/bynder.spec.ts +3 -0
  122. package/tests/cinemaplus.spec.ts +8 -0
  123. package/tests/columbia-privacy-notice.spec.ts +8 -0
  124. package/tests/complianz-notice.spec.ts +7 -3
  125. package/tests/cookieconsent2-legacy.spec.ts +8 -0
  126. package/tests/cookiesjsr.spec.ts +9 -0
  127. package/tests/copaamerica.spec.ts +3 -0
  128. package/tests/cornerstone-brands.spec.ts +8 -0
  129. package/tests/darkounity.com.spec.ts +6 -0
  130. package/tests/duda-cookie-notification.spec.ts +8 -0
  131. package/tests/endocrine.org.spec.ts +3 -0
  132. package/tests/ezoic.spec.ts +1 -1
  133. package/tests/fis-data-exchange.spec.ts +9 -0
  134. package/tests/getproven.spec.ts +5 -0
  135. package/tests/givedirectly.spec.ts +3 -0
  136. package/tests/godaddy-website-builder-cosmetic.spec.ts +7 -0
  137. package/tests/godaddy-website-builder.spec.ts +8 -0
  138. package/tests/guesty.spec.ts +7 -0
  139. package/tests/gundam-base-entry.spec.ts +3 -0
  140. package/tests/hearst-us.spec.ts +1 -0
  141. package/tests/indy-systems.spec.ts +8 -0
  142. package/tests/ing-nl.spec.ts +6 -0
  143. package/tests/iubenda.spec.ts +5 -0
  144. package/tests/lucrin.spec.ts +3 -0
  145. package/tests/makitatools.spec.ts +5 -0
  146. package/tests/milestone-internet.spec.ts +8 -0
  147. package/tests/nhm-ac-uk.spec.ts +3 -0
  148. package/tests/npo-ccm.spec.ts +17 -0
  149. package/tests/opalcollection-com.spec.ts +3 -0
  150. package/tests/orw-cookie-banner.spec.ts +5 -0
  151. package/tests/osf-io.spec.ts +3 -0
  152. package/tests/palace-company.spec.ts +8 -0
  153. package/tests/paytronix.spec.ts +7 -0
  154. package/tests/pitt-edu.spec.ts +3 -0
  155. package/tests/pizzapilgrims.co.uk.spec.ts +3 -0
  156. package/tests/podimo.spec.ts +3 -0
  157. package/tests/publicmobile.spec.ts +3 -0
  158. package/tests/rakshaos.spec.ts +9 -0
  159. package/tests/rutjes.spec.ts +3 -0
  160. package/tests/saturdaysnyc.spec.ts +3 -0
  161. package/tests/serpstat-com.spec.ts +3 -0
  162. package/tests/shopquarters.spec.ts +5 -0
  163. package/tests/shopware.spec.ts +8 -0
  164. package/tests/simpleview-cookie-banner.spec.ts +10 -0
  165. package/tests/solvhealth-com.spec.ts +10 -0
  166. package/tests/spermidinelife-us.spec.ts +3 -0
  167. package/tests/squarespace-cookie-notice.spec.ts +9 -0
  168. package/tests/store2000-fr.spec.ts +5 -0
  169. package/tests/tempest-cookie-notice.spec.ts +7 -0
  170. package/tests/termsfeed.spec.ts +4 -0
  171. package/tests/therealest.spec.ts +6 -0
  172. package/tests/tineye.spec.ts +3 -0
  173. package/tests/tk-aerztefuehrer.de.spec.ts +6 -0
  174. package/tests/tplink.spec.ts +11 -1
  175. package/tests/trust360.spec.ts +8 -0
  176. package/tests/up-cookie-consent.spec.ts +9 -0
  177. package/tests/uw-madison-cookieconsent.spec.ts +7 -0
  178. package/tests/vowels.spec.ts +3 -0
  179. package/tests/web-rewind.com.spec.ts +5 -0
  180. package/tests/wildling-com.spec.ts +3 -0
  181. package/tests/worldbank.spec.ts +7 -0
  182. package/tests/yuntrack.spec.ts +6 -0
  183. package/tests-wtr/heuristics/button-classification-accuracy.test.ts +9 -5
  184. package/tests-wtr/heuristics/get-potential-popups.test.ts +28 -0
  185. package/tests-wtr/heuristics/heuristics-utils.test.ts +2 -6
  186. package/tsconfig.build.json +1 -1
  187. 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, ButtonRegexClassification, HeuristicLevel, PopupData } from './types';
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 classifyButtons(buttons: ButtonData[]): void;
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
@@ -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 = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
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
- /do\s+not\s+sell|do\s+not\s+share\s+my\s+(personal\s+)?(information|info|data)|opt\s+out\s+of\s+sale/is,
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
- /(^|\s)(refus|rejet|rejeter|interdire|interdis|déclin|declin)/is,
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
- /(uniquement|seulement|indispensable|strictement nécessaire|que les cookies\s+(nécessaires|techniques|essentiels|indispensables|fonctionnels))/is,
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|revocar consentimiento|continuar sin aceptar|prefiero rechazarlas|descartar todas)/is,
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|richtlinie|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|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,
478
- /^(?!.*\b(policy|policies|notice|statement|impressum|richtlinie|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,15}(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|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,
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)$/is,
494
- /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
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
- ACCEPT_PATTERNS,
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 classifyButtons(buttons: ButtonData[]) {
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 = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
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 './heuristics';
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.50.0",
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",
@@ -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
+ }