@duckduckgo/autoconsent 16.49.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 (236) hide show
  1. package/.agents/skills/optimize-button-patterns/SKILL.md +4 -4
  2. package/.github/workflows/release-labels.yml +2 -6
  3. package/AGENTS.md +1 -1
  4. package/CHANGELOG.md +130 -0
  5. package/build.sh +2 -0
  6. package/data/labelled-button-texts.csv +36 -36
  7. package/dist/addon-firefox/background.bundle.js +4 -1
  8. package/dist/addon-firefox/compact-rules.json +1 -1
  9. package/dist/addon-firefox/content.bundle.js +67 -46
  10. package/dist/addon-firefox/manifest.json +1 -1
  11. package/dist/addon-firefox/rule-index.json +1 -1
  12. package/dist/addon-firefox/rules.json +1 -1
  13. package/dist/addon-mv3/background.bundle.js +4 -1
  14. package/dist/addon-mv3/compact-rules.json +1 -1
  15. package/dist/addon-mv3/content.bundle.js +67 -46
  16. package/dist/addon-mv3/manifest.json +1 -1
  17. package/dist/addon-mv3/rule-index.json +1 -1
  18. package/dist/addon-mv3/rules.json +1 -1
  19. package/dist/autoconsent.cjs.js +67 -46
  20. package/dist/autoconsent.esm.js +67 -46
  21. package/dist/autoconsent.playwright.js +68 -46
  22. package/dist/autoconsent.standalone.js +68 -47
  23. package/dist/heuristics.cjs.js +886 -0
  24. package/dist/heuristics.mjs +847 -0
  25. package/dist/types/eval-snippets.d.ts +2 -0
  26. package/dist/types/heuristic-classify.d.ts +16 -0
  27. package/dist/types/heuristics.d.ts +2 -9
  28. package/dist/types/types.d.ts +3 -5
  29. package/lib/eval-snippets.ts +4 -0
  30. package/lib/heuristic-classify.ts +136 -0
  31. package/lib/heuristic-patterns.ts +33 -16
  32. package/lib/heuristics.ts +3 -124
  33. package/lib/types.ts +3 -5
  34. package/lib/web.ts +1 -1
  35. package/package.json +7 -2
  36. package/playwright/content.ts +5 -0
  37. package/readme.md +12 -0
  38. package/rules/autoconsent/abcacupuncture1.json +34 -0
  39. package/rules/autoconsent/aceodds.json +13 -0
  40. package/rules/autoconsent/allabout-japan.json +42 -0
  41. package/rules/autoconsent/amazon-hiring.json +16 -0
  42. package/rules/autoconsent/avada-cookie-consent.json +43 -0
  43. package/rules/autoconsent/avvio.json +22 -0
  44. package/rules/autoconsent/axeptio.json +22 -6
  45. package/rules/autoconsent/bdirect-booking-engine.json +33 -0
  46. package/rules/autoconsent/berelax.com.json +33 -0
  47. package/rules/autoconsent/bigcrunch.json +39 -0
  48. package/rules/autoconsent/bigid.json +59 -0
  49. package/rules/autoconsent/bluepark.json +31 -0
  50. package/rules/autoconsent/bricklink.json +33 -0
  51. package/rules/autoconsent/burpee-com.json +5 -1
  52. package/rules/autoconsent/bynder.json +33 -0
  53. package/rules/autoconsent/ceneo.json +9 -0
  54. package/rules/autoconsent/cinemaplus.json +9 -0
  55. package/rules/autoconsent/columbia-privacy-notice.json +32 -0
  56. package/rules/autoconsent/complianz-notice.json +1 -1
  57. package/rules/autoconsent/conjointly.json +43 -0
  58. package/rules/autoconsent/cookieconsent2-legacy.json +44 -0
  59. package/rules/autoconsent/cookiesjsr.json +27 -0
  60. package/rules/autoconsent/copaamerica.json +29 -0
  61. package/rules/autoconsent/cornerstone-brands.json +51 -0
  62. package/rules/autoconsent/darkounity.com.json +35 -0
  63. package/rules/autoconsent/dhbbank.json +13 -0
  64. package/rules/autoconsent/duda-cookie-notification.json +41 -0
  65. package/rules/autoconsent/ee-consent-notice.json +40 -0
  66. package/rules/autoconsent/endocrine.org.json +35 -0
  67. package/rules/autoconsent/ezoic.json +2 -2
  68. package/rules/autoconsent/fera-cookies.json +67 -0
  69. package/rules/autoconsent/fis-data-exchange.json +13 -0
  70. package/rules/autoconsent/getproven.json +33 -0
  71. package/rules/autoconsent/givedirectly.json +36 -0
  72. package/rules/autoconsent/godaddy-website-builder-cosmetic.json +13 -0
  73. package/rules/autoconsent/godaddy-website-builder.json +9 -0
  74. package/rules/autoconsent/guesty.json +31 -0
  75. package/rules/autoconsent/gundam-base-entry.json +40 -0
  76. package/rules/autoconsent/hearst-us.json +1 -1
  77. package/rules/autoconsent/indy-systems.json +26 -0
  78. package/rules/autoconsent/ing-nl.json +54 -0
  79. package/rules/autoconsent/iubenda.json +25 -3
  80. package/rules/autoconsent/la7-cmp.json +30 -0
  81. package/rules/autoconsent/laravel-cookie-consent.json +45 -0
  82. package/rules/autoconsent/legislation-gov-uk.json +43 -0
  83. package/rules/autoconsent/lucrin.json +18 -0
  84. package/rules/autoconsent/mackeeper.json +34 -0
  85. package/rules/autoconsent/makitatools.json +41 -0
  86. package/rules/autoconsent/milestone-internet.json +32 -0
  87. package/rules/autoconsent/ndl-go-jp.json +35 -0
  88. package/rules/autoconsent/nhm-ac-uk.json +29 -0
  89. package/rules/autoconsent/npo-ccm.json +42 -0
  90. package/rules/autoconsent/opalcollection-com.json +12 -0
  91. package/rules/autoconsent/orw-cookie-banner.json +42 -0
  92. package/rules/autoconsent/osf-io.json +29 -0
  93. package/rules/autoconsent/palace-company.json +13 -0
  94. package/rules/autoconsent/paypal-us.json +3 -3
  95. package/rules/autoconsent/paypal.json +5 -2
  96. package/rules/autoconsent/paytronix.json +30 -0
  97. package/rules/autoconsent/pitt-edu.json +12 -0
  98. package/rules/autoconsent/pizzapilgrims.co.uk.json +49 -0
  99. package/rules/autoconsent/podimo.json +73 -0
  100. package/rules/autoconsent/publicmobile.json +16 -0
  101. package/rules/autoconsent/rakshaos.json +45 -0
  102. package/rules/autoconsent/rutjes.json +13 -0
  103. package/rules/autoconsent/saturdaysnyc.json +33 -0
  104. package/rules/autoconsent/serpstat-com.json +13 -0
  105. package/rules/autoconsent/shopquarters.json +41 -0
  106. package/rules/autoconsent/shopware.json +71 -0
  107. package/rules/autoconsent/simpleview-cookie-banner.json +27 -0
  108. package/rules/autoconsent/solvhealth-com.json +12 -0
  109. package/rules/autoconsent/spermidinelife-us.json +29 -0
  110. package/rules/autoconsent/squarespace-cookie-banner.json +1 -1
  111. package/rules/autoconsent/squarespace-cookie-notice.json +29 -0
  112. package/rules/autoconsent/store2000-fr.json +17 -0
  113. package/rules/autoconsent/tempest-cookie-notice.json +9 -0
  114. package/rules/autoconsent/termsfeed.json +4 -4
  115. package/rules/autoconsent/texas-instruments.json +17 -0
  116. package/rules/autoconsent/thegrandyork.json +46 -0
  117. package/rules/autoconsent/thenounproject.json +61 -0
  118. package/rules/autoconsent/therealest.json +42 -0
  119. package/rules/autoconsent/tineye.json +35 -0
  120. package/rules/autoconsent/tk-aerztefuehrer.de.json +18 -0
  121. package/rules/autoconsent/toyota.json +16 -5
  122. package/rules/autoconsent/tplink.json +57 -9
  123. package/rules/autoconsent/trust360.json +29 -0
  124. package/rules/autoconsent/uft-cookie-notice.json +15 -0
  125. package/rules/autoconsent/up-cookie-consent.json +53 -0
  126. package/rules/autoconsent/uw-madison-cookieconsent.json +10 -0
  127. package/rules/autoconsent/vowels.json +13 -0
  128. package/rules/autoconsent/wbd-legal-terms.json +32 -0
  129. package/rules/autoconsent/web-rewind.com.json +21 -0
  130. package/rules/autoconsent/wildling-com.json +29 -0
  131. package/rules/autoconsent/worldbank.json +30 -0
  132. package/rules/autoconsent/yuntrack.json +50 -0
  133. package/rules/compact-rules.json +1 -1
  134. package/rules/rule-index.json +1 -1
  135. package/rules/rules.json +1 -1
  136. package/scripts/benchmark-button-classification.ts +18 -3
  137. package/scripts/button-text-benchmark.ts +5 -5
  138. package/tests/abcacupuncture1.spec.ts +3 -0
  139. package/tests/aceodds.spec.ts +3 -0
  140. package/tests/allabout-japan.spec.ts +3 -0
  141. package/tests/amazon-hiring.spec.ts +3 -0
  142. package/tests/avada-cookie-consent.spec.ts +9 -0
  143. package/tests/avvio.spec.ts +8 -0
  144. package/tests/axeptio.spec.ts +11 -4
  145. package/tests/bdirect-booking-engine.spec.ts +9 -0
  146. package/tests/berelax.com.spec.ts +5 -0
  147. package/tests/bigcrunch.spec.ts +16 -0
  148. package/tests/bigid.spec.ts +8 -0
  149. package/tests/bluepark.spec.ts +8 -0
  150. package/tests/bricklink.spec.ts +3 -0
  151. package/tests/burpee-com.spec.ts +2 -0
  152. package/tests/bynder.spec.ts +3 -0
  153. package/tests/ceneo.spec.ts +3 -0
  154. package/tests/cinemaplus.spec.ts +8 -0
  155. package/tests/columbia-privacy-notice.spec.ts +8 -0
  156. package/tests/complianz-notice.spec.ts +7 -3
  157. package/tests/conjointly.spec.ts +3 -0
  158. package/tests/cookieconsent2-legacy.spec.ts +8 -0
  159. package/tests/cookiesjsr.spec.ts +9 -0
  160. package/tests/copaamerica.spec.ts +3 -0
  161. package/tests/cornerstone-brands.spec.ts +8 -0
  162. package/tests/darkounity.com.spec.ts +6 -0
  163. package/tests/dhbbank.spec.ts +6 -0
  164. package/tests/duda-cookie-notification.spec.ts +8 -0
  165. package/tests/ee-consent-notice.spec.ts +9 -0
  166. package/tests/endocrine.org.spec.ts +3 -0
  167. package/tests/ezoic.spec.ts +1 -1
  168. package/tests/fera-cookies.spec.ts +6 -0
  169. package/tests/fis-data-exchange.spec.ts +9 -0
  170. package/tests/getproven.spec.ts +5 -0
  171. package/tests/givedirectly.spec.ts +3 -0
  172. package/tests/godaddy-website-builder-cosmetic.spec.ts +7 -0
  173. package/tests/godaddy-website-builder.spec.ts +8 -0
  174. package/tests/guesty.spec.ts +7 -0
  175. package/tests/gundam-base-entry.spec.ts +3 -0
  176. package/tests/hearst-us.spec.ts +1 -0
  177. package/tests/indy-systems.spec.ts +8 -0
  178. package/tests/ing-nl.spec.ts +6 -0
  179. package/tests/iubenda.spec.ts +5 -0
  180. package/tests/la7-cmp.spec.ts +6 -0
  181. package/tests/laravel-cookie-consent.spec.ts +10 -0
  182. package/tests/legislation-gov-uk.spec.ts +3 -0
  183. package/tests/lucrin.spec.ts +3 -0
  184. package/tests/mackeeper.spec.ts +3 -0
  185. package/tests/makitatools.spec.ts +5 -0
  186. package/tests/milestone-internet.spec.ts +8 -0
  187. package/tests/ndl-go-jp.spec.ts +8 -0
  188. package/tests/nhm-ac-uk.spec.ts +3 -0
  189. package/tests/npo-ccm.spec.ts +17 -0
  190. package/tests/opalcollection-com.spec.ts +3 -0
  191. package/tests/orw-cookie-banner.spec.ts +5 -0
  192. package/tests/osf-io.spec.ts +3 -0
  193. package/tests/palace-company.spec.ts +8 -0
  194. package/tests/paypal-us.spec.ts +2 -3
  195. package/tests/paytronix.spec.ts +7 -0
  196. package/tests/pitt-edu.spec.ts +3 -0
  197. package/tests/pizzapilgrims.co.uk.spec.ts +3 -0
  198. package/tests/podimo.spec.ts +3 -0
  199. package/tests/publicmobile.spec.ts +3 -0
  200. package/tests/rakshaos.spec.ts +9 -0
  201. package/tests/rutjes.spec.ts +3 -0
  202. package/tests/saturdaysnyc.spec.ts +3 -0
  203. package/tests/serpstat-com.spec.ts +3 -0
  204. package/tests/shopquarters.spec.ts +5 -0
  205. package/tests/shopware.spec.ts +8 -0
  206. package/tests/simpleview-cookie-banner.spec.ts +10 -0
  207. package/tests/solvhealth-com.spec.ts +10 -0
  208. package/tests/spermidinelife-us.spec.ts +3 -0
  209. package/tests/squarespace-cookie-notice.spec.ts +9 -0
  210. package/tests/store2000-fr.spec.ts +5 -0
  211. package/tests/tempest-cookie-notice.spec.ts +7 -0
  212. package/tests/termsfeed.spec.ts +4 -0
  213. package/tests/texas-instruments.spec.ts +3 -0
  214. package/tests/thegrandyork.spec.ts +3 -0
  215. package/tests/thenounproject.spec.ts +3 -0
  216. package/tests/therealest.spec.ts +6 -0
  217. package/tests/tineye.spec.ts +3 -0
  218. package/tests/tk-aerztefuehrer.de.spec.ts +6 -0
  219. package/tests/tplink.spec.ts +11 -1
  220. package/tests/trust360.spec.ts +8 -0
  221. package/tests/uft-cookie-notice.spec.ts +3 -0
  222. package/tests/up-cookie-consent.spec.ts +9 -0
  223. package/tests/uw-madison-cookieconsent.spec.ts +7 -0
  224. package/tests/vowels.spec.ts +3 -0
  225. package/tests/wbd-legal-terms.spec.ts +18 -0
  226. package/tests/web-rewind.com.spec.ts +5 -0
  227. package/tests/wildling-com.spec.ts +3 -0
  228. package/tests/worldbank.spec.ts +7 -0
  229. package/tests/yuntrack.spec.ts +6 -0
  230. package/tests-wtr/heuristics/button-classification-accuracy.test.ts +9 -5
  231. package/tests-wtr/heuristics/get-potential-popups.test.ts +28 -0
  232. package/tests-wtr/heuristics/heuristics-utils.test.ts +57 -6
  233. package/tsconfig.build.json +1 -1
  234. package/tsconfig.heuristics.json +10 -0
  235. package/rules/autoconsent/paypal-cookieprefs.json +0 -29
  236. package/tests/paypal-cookieprefs.spec.ts +0 -7
@@ -40,6 +40,7 @@ export declare const snippets: {
40
40
  EVAL_COOKIEFIRST_1: () => boolean;
41
41
  EVAL_COOKIEINFORMATION_0: () => any;
42
42
  EVAL_COOKIEINFORMATION_1: () => any;
43
+ EVAL_EE_CONSENT_DECLINE_0: () => boolean;
43
44
  EVAL_ETSY_0: () => boolean;
44
45
  EVAL_ETSY_1: () => any;
45
46
  EVAL_EZOIC_0: () => any;
@@ -87,5 +88,6 @@ export declare const snippets: {
87
88
  EVAL_USERCENTRICS_API_6: () => boolean;
88
89
  EVAL_USERCENTRICS_BUTTON_0: () => any;
89
90
  EVAL_WAITROSE_0: () => boolean;
91
+ EVAL_WBD_LTP_CLOSE: () => any;
90
92
  };
91
93
  export declare function getFunctionBody(snippetFunc: () => any): string;
@@ -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';
@@ -148,6 +148,9 @@ export const snippets = {
148
148
  .forEach((i) => i.getAttribute('aria-checked') === 'true' && i.click()) || true,
149
149
  EVAL_COOKIEINFORMATION_0: () => CookieInformation.declineAllCategories() || true,
150
150
  EVAL_COOKIEINFORMATION_1: () => CookieInformation.submitAllCategories() || true,
151
+ // turn the single-button form into a decline submission, same fields as the CMP's own decline form
152
+ EVAL_EE_CONSENT_DECLINE_0: () =>
153
+ document.querySelectorAll('#cookieConsentFormAccept input[name^="ee:cookies_"]').forEach((i) => (i.value = 'n')) || true,
151
154
  EVAL_ETSY_0: () =>
152
155
  document.querySelectorAll('.gdpr-overlay-body input').forEach((toggle) => {
153
156
  toggle.checked = false;
@@ -264,6 +267,7 @@ export const snippets = {
264
267
  EVAL_USERCENTRICS_BUTTON_0: () =>
265
268
  JSON.parse(localStorage.getItem('usercentrics')).consents.every((c) => c.isEssential || !c.consentStatus),
266
269
  EVAL_WAITROSE_0: () => Array.from(document.querySelectorAll('label[id$=cookies-deny-label]')).forEach((e) => e.click()) || true,
270
+ EVAL_WBD_LTP_CLOSE: () => document.querySelector('dialog#ltp-dialog').close() || true,
267
271
  };
268
272
 
269
273
  export function getFunctionBody(snippetFunc: () => any) {
@@ -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
+ }
@@ -7,7 +7,8 @@ export const DETECT_PATTERNS = [
7
7
  /only necessary cookies/gi, // "only necessary" is probably too broad
8
8
  /(?:by continuing.{0,100}cookie)|(?:cookie.{0,100}by continuing)/gi,
9
9
  /(?:by continuing.{0,100}privacy)|(?:privacy.{0,100}by continuing)/gi,
10
- /we (?:use|serve)(?: optional| essential)? cookies/gi,
10
+ // a single qualifier is common: "optional", "essential", "necessary", "analytics", ...
11
+ /we (?:use|serve)(?: \w+)? cookies/gi,
11
12
  /we are using cookies/gi,
12
13
  /use of cookies/gi,
13
14
  /website uses cookies to enhance your browsing experience/gi,
@@ -165,6 +166,10 @@ export const DETECT_PATTERNS = [
165
166
 
166
167
  // Italian (IT)
167
168
  /usiamo.{0,20}cookie/gi,
169
+
170
+ // Japanese (JA)
171
+ // e.g. "当サイトはCookieを使用しています", "クッキー(Cookie)を利用しています"
172
+ /(?:cookie|クッキー).{0,10}を(?:使用|利用)/gi,
168
173
  ];
169
174
 
170
175
  const REJECT_PATTERNS_ENGLISH = [
@@ -185,8 +190,9 @@ const REJECT_PATTERNS_ENGLISH = [
185
190
  /^\s*(i\s+)?(want\s+to\s+)?only\s+(strictly\s+)?(necessary|essential|essentials|functional|required|minimal)\s*(cookies)?\s*(continue|only)?\s*$/is,
186
191
  /^\s*(strictly\s+)?(necessary|essential|essentials|functional|required|minimal)\s+((cookies\s+)?only|only\s+cookies)\s*$/is,
187
192
 
188
- // e.g. "do not sell or share my personal information", "opt out of sale ..." (CCPA)
189
- /do\s+not\s+sell|opt\s+out\s+of\s+sale/is,
193
+ // e.g. "do not sell or share my personal information", "do not share my personal information", "opt out of sale ..." (CCPA)
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,
190
196
 
191
197
  // e.g. "opt-out of sale/share or targeted advertising", "opt-out of advertising/social media cookies"
192
198
  /^opt[ -]?out of /is,
@@ -201,7 +207,6 @@ const REJECT_PATTERNS_ENGLISH = [
201
207
  /^no,? thank(s| you)$/is,
202
208
  /^opt[ -]out$/is,
203
209
  'dont enable',
204
- 'withdraw consent',
205
210
  'i do not agree',
206
211
  ];
207
212
 
@@ -236,10 +241,11 @@ const REJECT_PATTERNS_DUTCH = [
236
241
  const REJECT_PATTERNS_FRENCH = [
237
242
  // refuser / rejeter / interdire / décliner (reject verbs, any position)
238
243
  // "refuser et s'abonner" / "refuser et payer" are excluded via BUTTON_NEVER_MATCH_PATTERNS
239
- /(^|\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,
240
246
 
241
247
  // only necessary / essential / technical / functional
242
- /(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,
243
249
 
244
250
  // continue/proceed without accepting; refuse everything; disable purposes
245
251
  /(sans accepter|ne pas accepter|je naccepte rien|je désactive)/is,
@@ -318,7 +324,7 @@ const REJECT_PATTERNS_SPANISH = [
318
324
  /^(no,?\s+)?(solo|sólo|només)\s+(usar\s+|las?\s+|los\s+|lo\s+)?.{0,20}(necesari|esencial|estrictamente)/is,
319
325
 
320
326
  // refusals / opt-outs
321
- /^(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,
322
328
 
323
329
  'acceptar només les necessàries',
324
330
  'només sutilitzen cookies quan és necessari',
@@ -390,6 +396,13 @@ const REJECT_PATTERNS_TURKISH = ['reddet', 'çerezleri reddet'];
390
396
 
391
397
  const REJECT_PATTERNS_INDONESIAN = ['tolak cookie'];
392
398
 
399
+ const REJECT_PATTERNS_JAPANESE = [
400
+ // e.g. "拒否", "すべて拒否", "全て拒否する", "cookieを拒否"
401
+ /^(すべて|全て)?\s?(の)?(cookie|クッキー)?\s?(を)?拒否(する)?$/i,
402
+ '同意しない',
403
+ '同意しません',
404
+ ];
405
+
393
406
  /**
394
407
  * @type {Array<string|RegExp>}
395
408
  */
@@ -411,9 +424,13 @@ export const REJECT_PATTERNS = [
411
424
  ...REJECT_PATTERNS_RUSSIAN,
412
425
  ...REJECT_PATTERNS_TURKISH,
413
426
  ...REJECT_PATTERNS_INDONESIAN,
427
+ ...REJECT_PATTERNS_JAPANESE,
414
428
  ];
415
429
 
416
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,
417
434
  /pay|subscribe/is,
418
435
  /abonneer/is,
419
436
  /abonnier/is,
@@ -461,9 +478,11 @@ export const SETTINGS_PATTERNS = [
461
478
  // Multilingual "open customization" patterns: a customization verb next to a
462
479
  // cookie/preference/settings/options/details/purposes noun (both word orders).
463
480
  // The negative lookahead avoids policy links and save/confirm/accept phrases (those are accept/acknowledge/other).
464
- /^(?!.*\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,
465
- /^(?!.*\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,
466
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,
467
486
  'settings',
468
487
  'preferences',
469
488
  /customi(s|z)e/is,
@@ -477,8 +496,8 @@ export const SETTINGS_PATTERNS = [
477
496
  'cookieconsent preferences',
478
497
  /privacy choices/is,
479
498
  /^(privacy|cookie|custom) settings$/is,
480
- /^cookies? (settings|preferences|setting)$/is,
481
- /(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,
482
501
  'cookie consent options',
483
502
  'privacy controls',
484
503
  // German
@@ -669,7 +688,7 @@ export const ACCEPT_PATTERNS = [
669
688
  'yes',
670
689
  /accept all above/is,
671
690
  'close and accept',
672
- /accept all$/is,
691
+ /(^|\s)accept all$/is,
673
692
  'im ok with that',
674
693
 
675
694
  // Spanish (ES)
@@ -697,6 +716,8 @@ export const ACCEPT_PATTERNS = [
697
716
  'accepta',
698
717
  'accepta totes les cookies',
699
718
  'accepta-ho tot',
719
+ // FR/CA "d'accord" / "d'acord"
720
+ /^dac?cord( fermer)?$/is,
700
721
  'accepta-les totes',
701
722
  'acceptar galetes',
702
723
  'acceptar i tancar',
@@ -745,7 +766,6 @@ export const ACCEPT_PATTERNS = [
745
766
  'akceptuję!',
746
767
  'ok, zgadzam się',
747
768
  'potwierdzam wszystkie',
748
- 'przejdź do serwisu',
749
769
  'tak',
750
770
  'tak, zgadzam się na wszystkie pliki cookie',
751
771
  'tak, zgadzam się',
@@ -828,9 +848,6 @@ export const ACKNOWLEDGE_PATTERNS = [
828
848
  'vale',
829
849
  '¡vamos!',
830
850
 
831
- // Catalan (CA)
832
- 'dacord',
833
-
834
851
  // Polish (PL)
835
852
  'kontynuuj',
836
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.49.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.