@duckduckgo/autoconsent 14.97.0 → 15.0.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 (39) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/addon-firefox/background.bundle.js +10 -1
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +142 -33
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-firefox/popup.bundle.js +10 -1
  7. package/dist/addon-firefox/rules.json +1 -1
  8. package/dist/addon-mv3/background.bundle.js +10 -1
  9. package/dist/addon-mv3/compact-rules.json +1 -1
  10. package/dist/addon-mv3/content.bundle.js +142 -33
  11. package/dist/addon-mv3/manifest.json +1 -1
  12. package/dist/addon-mv3/popup.bundle.js +10 -1
  13. package/dist/addon-mv3/rules.json +1 -1
  14. package/dist/autoconsent.cjs.js +142 -33
  15. package/dist/autoconsent.esm.js +142 -33
  16. package/dist/autoconsent.extra.cjs.js +142 -33
  17. package/dist/autoconsent.extra.esm.js +142 -33
  18. package/dist/autoconsent.playwright.js +142 -33
  19. package/dist/types/cmps/base.d.ts +4 -2
  20. package/dist/types/heuristic-patterns.d.ts +3 -0
  21. package/dist/types/heuristics.d.ts +4 -7
  22. package/dist/types/types.d.ts +18 -2
  23. package/lib/cmps/base.ts +20 -7
  24. package/lib/heuristic-patterns.ts +68 -1
  25. package/lib/heuristics.ts +76 -27
  26. package/lib/types.ts +20 -2
  27. package/lib/utils.ts +2 -1
  28. package/lib/web.ts +3 -1
  29. package/package.json +1 -1
  30. package/rules/autoconsent/twitch.json +1 -1
  31. package/rules/compact-rules.json +1 -1
  32. package/rules/rules.json +1 -1
  33. package/tests-wtr/heuristics/get-actionable-popups.html +16 -0
  34. package/tests-wtr/heuristics/get-actionable-popups.ts +85 -6
  35. package/tests-wtr/heuristics/heuristic-cmp.html +69 -0
  36. package/tests-wtr/heuristics/heuristic-cmp.ts +178 -0
  37. package/tests-wtr/heuristics/heuristics-utils.test.ts +96 -28
  38. package/tests-wtr/lifecycle/find-cmp.html +14 -0
  39. package/tests-wtr/lifecycle/find-cmp.ts +44 -2
@@ -407,6 +407,14 @@
407
407
  });
408
408
  }
409
409
 
410
+ // lib/types.ts
411
+ var PopupHandlingModes = {
412
+ None: -1,
413
+ Reject: 0,
414
+ Tier1: 1,
415
+ Tier2: 2
416
+ };
417
+
410
418
  // lib/random.ts
411
419
  function getRandomID() {
412
420
  if (crypto && typeof crypto.randomUUID !== "undefined") {
@@ -716,7 +724,8 @@
716
724
  waits: false
717
725
  },
718
726
  performanceLoggingEnabled: false,
719
- heuristicPopupSearchTimeout: 100
727
+ heuristicPopupSearchTimeout: 100,
728
+ heuristicMode: PopupHandlingModes.Reject
720
729
  };
721
730
  const updatedConfig = copyObject(defaultConfig);
722
731
  for (const key of Object.keys(defaultConfig)) {
@@ -883,12 +892,17 @@
883
892
  /^\s*(use|accept|allow|continue\s+with)?\s*only\s*(strictly)?\s*(necessary|essentials?|required)?\s*(cookies)?\s*$/is,
884
893
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
885
894
  // often used in CCPA
886
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
895
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
887
896
  "allow selection",
888
- "disagree and close"
897
+ "disagree and close",
889
898
  // These are impactful, but look error-prone
899
+ // // e.g. "disagree"
900
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
890
901
  // // e.g. "i do not agree"
891
902
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
903
+ "no",
904
+ /^no,? thanks$/is,
905
+ /^opt[ -]out$/is
892
906
  ];
893
907
  var REJECT_PATTERNS_DUTCH = [
894
908
  "weigeren",
@@ -1464,6 +1478,64 @@
1464
1478
  /sostienici/is,
1465
1479
  /suscribir/is
1466
1480
  ];
1481
+ var SETTINGS_PATTERNS = [
1482
+ "settings",
1483
+ "preferences",
1484
+ /customi(s|z)e/is,
1485
+ "show details",
1486
+ "more options",
1487
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
1488
+ "manage choices",
1489
+ /(cookie )?preference center/is,
1490
+ "change settings",
1491
+ "configure",
1492
+ "change my preferences",
1493
+ "cookie manager",
1494
+ "cookie preference",
1495
+ "let me choose",
1496
+ "cookieconsent preferences",
1497
+ /privacy choices/is,
1498
+ /(privacy|cookie|custom) settings/is,
1499
+ /cookies? (settings|preferences|setting)/is,
1500
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
1501
+ "cookie consent options",
1502
+ "privacy controls",
1503
+ "show purposes",
1504
+ // German
1505
+ "einstellungen"
1506
+ ];
1507
+ var ACCEPT_PATTERNS = [
1508
+ /^(accept|allow)( all)?( cookies)?$/is,
1509
+ /i (accept|allow)( all)?/is,
1510
+ "yes",
1511
+ /^(i )?agree$/is,
1512
+ "continue with all",
1513
+ "accept and continue",
1514
+ /accept all above/is,
1515
+ /^(accept|agree) and close/is,
1516
+ "accept continue",
1517
+ "agree proceed",
1518
+ "allow and continue",
1519
+ "close and accept",
1520
+ /accept all$/is,
1521
+ "im ok with that",
1522
+ "accept optional cookies",
1523
+ /^alle (cookies )?akzeptieren$/is
1524
+ ];
1525
+ var ACKNOWLEDGE_PATTERNS = [
1526
+ "ok",
1527
+ "close",
1528
+ "continue",
1529
+ "x",
1530
+ /^got it!?$/,
1531
+ "i understand",
1532
+ "dismiss",
1533
+ "okay",
1534
+ "acknowledge",
1535
+ /^close (banner|cookie notification)$/is,
1536
+ /understood$/is,
1537
+ "confirm my choices"
1538
+ ];
1467
1539
 
1468
1540
  // lib/heuristics.ts
1469
1541
  var BUTTON_LIKE_ELEMENT_SELECTOR = 'button, input[type="button"], input[type="submit"], a, [role="button"], [class*="button"]';
@@ -1482,48 +1554,61 @@
1482
1554
  }
1483
1555
  return { patterns, snippets: snippets2 };
1484
1556
  }
1485
- function getActionablePopups(timeout = POPUP_SEARCH_MAX_TIME) {
1557
+ function getActionablePopups(mode = PopupHandlingModes.Reject, timeout = POPUP_SEARCH_MAX_TIME) {
1486
1558
  const popups = getPotentialPopups(timeout);
1487
1559
  const result = popups.reduce((acc, popup) => {
1488
1560
  const popupText = popup.text?.trim();
1489
1561
  if (popupText) {
1490
1562
  const { patterns } = checkHeuristicPatterns(popupText);
1491
1563
  if (patterns.length > 0) {
1492
- const { rejectButtons, otherButtons } = classifyButtons(popup.buttons);
1493
- if (rejectButtons.length > 0) {
1494
- acc.push({
1495
- ...popup,
1496
- rejectButtons,
1497
- otherButtons
1498
- });
1499
- }
1564
+ classifyButtons(popup.buttons);
1565
+ popup.regexClassification = classifyPopup(popup.buttons);
1566
+ acc.push({
1567
+ ...popup
1568
+ });
1500
1569
  }
1501
1570
  }
1502
1571
  return acc;
1503
1572
  }, []);
1504
- return result;
1573
+ return result.filter(
1574
+ (popup) => popup.regexClassification !== void 0 && popup.regexClassification !== PopupHandlingModes.None && popup.regexClassification <= mode
1575
+ ).sort((a, b) => (a.regexClassification ?? 0) - (b.regexClassification ?? 0));
1505
1576
  }
1506
1577
  function classifyButtons(buttons) {
1507
- const rejectButtons = [];
1508
- const otherButtons = [];
1509
1578
  for (const button of buttons) {
1510
- if (isRejectButton(button.text)) {
1511
- rejectButtons.push(button);
1512
- } else {
1513
- otherButtons.push(button);
1514
- }
1579
+ button.regexClassification = classifyButtonTextRegex(button.text);
1515
1580
  }
1516
- return {
1517
- rejectButtons,
1518
- otherButtons
1519
- };
1520
1581
  }
1521
- function isRejectButton(buttonText, rejectPatterns = REJECT_PATTERNS, neverMatchPatterns = NEVER_MATCH_PATTERNS) {
1582
+ function classifyPopup(buttons) {
1583
+ const { reject, settings, accept, acknowledge } = buttons.reduce(
1584
+ (acc, button) => {
1585
+ if (button.regexClassification && button.regexClassification !== "other") {
1586
+ acc[button.regexClassification]++;
1587
+ }
1588
+ return acc;
1589
+ },
1590
+ { reject: 0, settings: 0, accept: 0, acknowledge: 0 }
1591
+ );
1592
+ if (reject > 0) {
1593
+ return PopupHandlingModes.Reject;
1594
+ }
1595
+ if (settings > 0) {
1596
+ return PopupHandlingModes.None;
1597
+ }
1598
+ if (acknowledge > 0) {
1599
+ return PopupHandlingModes.Tier1;
1600
+ }
1601
+ if (accept > 0) {
1602
+ return accept === 1 ? PopupHandlingModes.Tier2 : PopupHandlingModes.None;
1603
+ }
1604
+ return PopupHandlingModes.None;
1605
+ }
1606
+ function testButtonMatches(buttonText, matchPatterns, neverMatchPatterns) {
1522
1607
  if (!buttonText) {
1523
1608
  return false;
1524
1609
  }
1525
1610
  const cleanedButtonText = cleanButtonText(buttonText);
1526
- return !neverMatchPatterns.some((p) => p.test(cleanedButtonText)) && rejectPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1611
+ return !neverMatchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText) && matchPatterns.some((p) => p instanceof RegExp && p.test(cleanedButtonText) || p === cleanedButtonText);
1527
1612
  }
1528
1613
  function cleanButtonText(buttonText) {
1529
1614
  let result = buttonText.toLowerCase();
@@ -1537,6 +1622,21 @@
1537
1622
  result = result.trim();
1538
1623
  return result;
1539
1624
  }
1625
+ function classifyButtonTextRegex(buttonText) {
1626
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1627
+ return "reject";
1628
+ }
1629
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
1630
+ return "settings";
1631
+ }
1632
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
1633
+ return "accept";
1634
+ }
1635
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
1636
+ return "acknowledge";
1637
+ }
1638
+ return "other";
1639
+ }
1540
1640
  function getPotentialPopups(timeout = POPUP_SEARCH_MAX_TIME) {
1541
1641
  const isFramed = !isTopFrame();
1542
1642
  if (isFramed && window.parent && window.parent !== window.top) {
@@ -1982,7 +2082,7 @@
1982
2082
  }
1983
2083
  };
1984
2084
  var AutoConsentHeuristicCMP = class extends AutoConsentCMPBase {
1985
- constructor(autoconsentInstance) {
2085
+ constructor(autoconsentInstance, mode = PopupHandlingModes.Reject) {
1986
2086
  super(autoconsentInstance);
1987
2087
  this.popups = [];
1988
2088
  this.name = "HEURISTIC";
@@ -1991,6 +2091,7 @@
1991
2091
  frame: false
1992
2092
  // do not run in iframes for security reasons
1993
2093
  };
2094
+ this.mode = mode;
1994
2095
  }
1995
2096
  get hasSelfTest() {
1996
2097
  return true;
@@ -2004,25 +2105,33 @@
2004
2105
  async detectCmp() {
2005
2106
  await new Promise((resolve) => setTimeout(resolve, 0));
2006
2107
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorStart");
2007
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
2108
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
2008
2109
  this.autoconsent.config.performanceLoggingEnabled && performance.mark("heuristicDetectorEnd");
2009
2110
  this.autoconsent.config.performanceLoggingEnabled && performance.measure("heuristicDetector", "heuristicDetectorStart", "heuristicDetectorEnd");
2010
2111
  if (this.popups.length > 0) {
2112
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
2011
2113
  return Promise.resolve(true);
2012
2114
  }
2013
2115
  return Promise.resolve(false);
2014
2116
  }
2015
2117
  async detectPopup() {
2016
2118
  if (this.popups.length > 0) {
2017
- if (this.popups.length > 1 || this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1) {
2018
- this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple reject buttons");
2119
+ if (this.popups.length > 1) {
2120
+ this.autoconsent.config.logs.errors && console.warn("Heuristic found multiple popups");
2019
2121
  }
2020
2122
  return true;
2021
2123
  }
2022
2124
  return false;
2023
2125
  }
2126
+ getTargetButton() {
2127
+ const popup = this.popups[0];
2128
+ const level = popup.regexClassification;
2129
+ const buttons = popup.buttons;
2130
+ const targetButtonType = level === PopupHandlingModes.Reject ? "reject" : level === PopupHandlingModes.Tier1 ? "acknowledge" : "accept";
2131
+ return buttons.find((button) => button.regexClassification === targetButtonType);
2132
+ }
2024
2133
  optOut() {
2025
- const button = this.popups[0]?.rejectButtons?.[0];
2134
+ const button = this.getTargetButton();
2026
2135
  if (button) {
2027
2136
  return this.clickElement(button.element);
2028
2137
  }
@@ -2035,7 +2144,7 @@
2035
2144
  throw new Error("Not Implemented");
2036
2145
  }
2037
2146
  async test() {
2038
- const button = this.popups[0].rejectButtons?.[0];
2147
+ const button = this.getTargetButton();
2039
2148
  if (button) {
2040
2149
  await this.wait(500);
2041
2150
  return !isElementVisible(button.element);
@@ -3505,7 +3614,7 @@
3505
3614
  }
3506
3615
  }
3507
3616
  });
3508
- const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this)] : [];
3617
+ const heuristicRules = isTop && this.config.enableHeuristicAction && this.state.findCmpAttempts % 2 === 0 ? [new AutoConsentHeuristicCMP(this, this.config.heuristicMode)] : [];
3509
3618
  const rulesPriorityStages = [
3510
3619
  ["site-specific", siteSpecificRules],
3511
3620
  ["generic", genericRules],
@@ -1,4 +1,4 @@
1
- import { AutoCMP, DomActionsProvider, PopupData } from '../types';
1
+ import { AutoCMP, DomActionsProvider, PopupData, PopupHandlingMode } from '../types';
2
2
  import { AutoConsentCMPRule, AutoConsentRuleStep, ElementSelector, HideMethod, RunContext, VisibilityCheck } from '../rules';
3
3
  import AutoConsent from '../web';
4
4
  import { snippets } from '../eval-snippets';
@@ -62,12 +62,14 @@ export declare class AutoConsentCMP extends AutoConsentCMPBase {
62
62
  }
63
63
  export declare class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
64
64
  popups: PopupData[];
65
- constructor(autoconsentInstance: AutoConsent);
65
+ mode: PopupHandlingMode;
66
+ constructor(autoconsentInstance: AutoConsent, mode?: PopupHandlingMode);
66
67
  get hasSelfTest(): boolean;
67
68
  get isIntermediate(): boolean;
68
69
  get isCosmetic(): boolean;
69
70
  detectCmp(): Promise<boolean>;
70
71
  detectPopup(): Promise<boolean>;
72
+ getTargetButton(): import("../types").ButtonData | undefined;
71
73
  optOut(): Promise<boolean>;
72
74
  optIn(): Promise<boolean>;
73
75
  openCmp(): Promise<boolean>;
@@ -4,3 +4,6 @@ export declare const DETECT_PATTERNS: RegExp[];
4
4
  */
5
5
  export declare const REJECT_PATTERNS: (string | RegExp)[];
6
6
  export declare const NEVER_MATCH_PATTERNS: RegExp[];
7
+ export declare const SETTINGS_PATTERNS: (string | RegExp)[];
8
+ export declare const ACCEPT_PATTERNS: (string | RegExp)[];
9
+ export declare const ACKNOWLEDGE_PATTERNS: (string | RegExp)[];
@@ -1,15 +1,12 @@
1
- import { ButtonData, PopupData } from './types';
1
+ import { ButtonData, ButtonRegexClassification, PopupData, PopupHandlingMode } from './types';
2
2
  export declare function checkHeuristicPatterns(allText: string, detectPatterns?: RegExp[]): {
3
3
  patterns: string[];
4
4
  snippets: string[];
5
5
  };
6
- export declare function getActionablePopups(timeout?: number): PopupData[];
7
- export declare function classifyButtons(buttons: ButtonData[]): {
8
- rejectButtons: ButtonData[];
9
- otherButtons: ButtonData[];
10
- };
11
- export declare function isRejectButton(buttonText: string, rejectPatterns?: (string | RegExp)[], neverMatchPatterns?: RegExp[]): boolean;
6
+ export declare function getActionablePopups(mode?: PopupHandlingMode, timeout?: number): PopupData[];
7
+ export declare function classifyButtons(buttons: ButtonData[]): void;
12
8
  export declare function cleanButtonText(buttonText: string): string;
9
+ export declare function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification;
13
10
  export declare function isDialogLikeElement(node: HTMLElement): boolean;
14
11
  /**
15
12
  * Serialize all actionable buttons on the page
@@ -74,6 +74,7 @@ export type Config = {
74
74
  };
75
75
  performanceLoggingEnabled: boolean;
76
76
  heuristicPopupSearchTimeout: number;
77
+ heuristicMode: PopupHandlingMode;
77
78
  };
78
79
  export type LifecycleState = 'loading' | 'initialized' | 'waitingForInitResponse' | 'started' | 'nothingDetected' | 'cosmeticFiltersDetected' | 'cmpDetected' | 'openPopupDetected' | 'runningOptOut' | 'runningOptIn' | 'optOutSucceeded' | 'optOutFailed' | 'optInSucceeded' | 'optInFailed' | 'done';
79
80
  export type ConsentState = {
@@ -92,14 +93,29 @@ export type ConsentState = {
92
93
  endTime: number;
93
94
  performance?: Record<string, number[]>;
94
95
  };
96
+ export type ButtonRegexClassification = 'reject' | 'settings' | 'accept' | 'acknowledge' | 'other';
95
97
  export interface ButtonData {
96
98
  text: string;
97
99
  element: HTMLElement;
100
+ regexClassification?: ButtonRegexClassification;
98
101
  }
99
102
  export interface PopupData {
100
103
  text: string;
101
104
  element: HTMLElement;
102
105
  buttons: ButtonData[];
103
- rejectButtons?: ButtonData[];
104
- otherButtons?: ButtonData[];
106
+ regexClassification?: PopupHandlingMode;
105
107
  }
108
+ /**
109
+ * Controls the behavior of the heuristic popup detection.
110
+ * - Reject: Will click the reject button on a popup if it exists.
111
+ * - Tier1: Will also click the acknowledge button on a popup if it exists, and no Reject button exists.
112
+ * - Tier2: Will also click the accept button on a popup if it exists, and no Reject or Acknowledge button exists.
113
+ * - None: Disabled
114
+ */
115
+ export declare const PopupHandlingModes: {
116
+ readonly None: -1;
117
+ readonly Reject: 0;
118
+ readonly Tier1: 1;
119
+ readonly Tier2: 2;
120
+ };
121
+ export type PopupHandlingMode = (typeof PopupHandlingModes)[keyof typeof PopupHandlingModes];
package/lib/cmps/base.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AutoCMP, DomActionsProvider, PopupData } from '../types';
1
+ import { AutoCMP, DomActionsProvider, PopupData, PopupHandlingMode, PopupHandlingModes } from '../types';
2
2
  import { AutoConsentCMPRule, AutoConsentRuleStep, ElementSelector, HideMethod, RunContext, VisibilityCheck } from '../rules';
3
3
  import { requestEval } from '../eval-handler';
4
4
  import AutoConsent from '../web';
@@ -419,14 +419,16 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
419
419
 
420
420
  export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
421
421
  popups: PopupData[] = [];
422
+ mode: PopupHandlingMode;
422
423
 
423
- constructor(autoconsentInstance: AutoConsent) {
424
+ constructor(autoconsentInstance: AutoConsent, mode: PopupHandlingMode = PopupHandlingModes.Reject) {
424
425
  super(autoconsentInstance);
425
426
  this.name = 'HEURISTIC';
426
427
  this.runContext = {
427
428
  main: true,
428
429
  frame: false, // do not run in iframes for security reasons
429
430
  } as RunContext;
431
+ this.mode = mode;
430
432
  }
431
433
 
432
434
  get hasSelfTest(): boolean {
@@ -445,11 +447,13 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
445
447
  // wait for one tick to deprioritize heavy DOM operations
446
448
  await new Promise((resolve) => setTimeout(resolve, 0));
447
449
  this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorStart');
448
- this.popups = getActionablePopups(this.autoconsent.config.heuristicPopupSearchTimeout);
450
+ this.popups = getActionablePopups(this.mode, this.autoconsent.config.heuristicPopupSearchTimeout);
449
451
  this.autoconsent.config.performanceLoggingEnabled && performance.mark('heuristicDetectorEnd');
450
452
  this.autoconsent.config.performanceLoggingEnabled &&
451
453
  performance.measure('heuristicDetector', 'heuristicDetectorStart', 'heuristicDetectorEnd');
454
+
452
455
  if (this.popups.length > 0) {
456
+ this.name = `HEURISTIC-TIER${this.popups[0].regexClassification}`;
453
457
  return Promise.resolve(true);
454
458
  }
455
459
  return Promise.resolve(false);
@@ -457,17 +461,26 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
457
461
 
458
462
  async detectPopup() {
459
463
  if (this.popups.length > 0) {
460
- if (this.popups.length > 1 || (this.popups[0].rejectButtons && this.popups[0].rejectButtons.length > 1)) {
461
- this.autoconsent.config.logs.errors && console.warn('Heuristic found multiple reject buttons');
464
+ if (this.popups.length > 1) {
465
+ this.autoconsent.config.logs.errors && console.warn('Heuristic found multiple popups');
462
466
  }
463
467
  return true;
464
468
  }
465
469
  return false;
466
470
  }
467
471
 
472
+ getTargetButton() {
473
+ const popup = this.popups[0];
474
+ const level = popup.regexClassification;
475
+ const buttons = popup.buttons;
476
+ const targetButtonType =
477
+ level === PopupHandlingModes.Reject ? 'reject' : level === PopupHandlingModes.Tier1 ? 'acknowledge' : 'accept';
478
+ return buttons.find((button) => button.regexClassification === targetButtonType);
479
+ }
480
+
468
481
  optOut(): Promise<boolean> {
469
482
  // use only the first found popup candidate
470
- const button = this.popups[0]?.rejectButtons?.[0];
483
+ const button = this.getTargetButton();
471
484
  if (button) {
472
485
  return this.clickElement(button.element);
473
486
  }
@@ -483,7 +496,7 @@ export class AutoConsentHeuristicCMP extends AutoConsentCMPBase {
483
496
  }
484
497
 
485
498
  async test(): Promise<boolean> {
486
- const button = this.popups[0].rejectButtons?.[0];
499
+ const button = this.getTargetButton();
487
500
  if (button) {
488
501
  await this.wait(500);
489
502
  return !isElementVisible(button.element);
@@ -115,14 +115,19 @@ const REJECT_PATTERNS_ENGLISH = [
115
115
 
116
116
  // e.g. "do not sell or share my personal information", "do not sell my personal information"
117
117
  // often used in CCPA
118
- /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*information\s*$/is,
118
+ /^\s*do\s+not\s+sell(\s+or\s+share)?\s*my\s*personal\s*info(rmation)?\s*$/is,
119
119
 
120
120
  'allow selection',
121
121
  'disagree and close',
122
122
 
123
123
  // These are impactful, but look error-prone
124
+ // // e.g. "disagree"
125
+ /^(i)?\s*disagree\s*(and\s+close)?$/i,
124
126
  // // e.g. "i do not agree"
125
127
  // /^\s*(i\s+)?do\s+not\s+agree\s*$/i,
128
+ 'no',
129
+ /^no,? thanks$/is,
130
+ /^opt[ -]out$/is,
126
131
  ];
127
132
 
128
133
  const REJECT_PATTERNS_DUTCH = [
@@ -715,3 +720,65 @@ export const NEVER_MATCH_PATTERNS = [
715
720
  /sostienici/is,
716
721
  /suscribir/is,
717
722
  ];
723
+
724
+ export const SETTINGS_PATTERNS = [
725
+ 'settings',
726
+ 'preferences',
727
+ /customi(s|z)e/is,
728
+ 'show details',
729
+ 'more options',
730
+ /(manage|configure) (my|your) (preferences|choices|cookies)/is,
731
+ 'manage choices',
732
+ /(cookie )?preference center/is,
733
+ 'change settings',
734
+ 'configure',
735
+ 'change my preferences',
736
+ 'cookie manager',
737
+ 'cookie preference',
738
+ 'let me choose',
739
+ 'cookieconsent preferences',
740
+ /privacy choices/is,
741
+ /(privacy|cookie|custom) settings/is,
742
+ /cookies? (settings|preferences|setting)/is,
743
+ /(manage|customize|customise|opt-out|edit).*(cookies|preferences|settings|options)/is,
744
+ 'cookie consent options',
745
+ 'privacy controls',
746
+ 'show purposes',
747
+ // German
748
+ 'einstellungen',
749
+ ];
750
+
751
+ export const ACCEPT_PATTERNS = [
752
+ /^(accept|allow)( all)?( cookies)?$/is,
753
+ /i (accept|allow)( all)?/is,
754
+ 'yes',
755
+ /^(i )?agree$/is,
756
+ 'continue with all',
757
+ 'accept and continue',
758
+ /accept all above/is,
759
+ /^(accept|agree) and close/is,
760
+ 'accept continue',
761
+ 'agree proceed',
762
+ 'allow and continue',
763
+ 'close and accept',
764
+ /accept all$/is,
765
+ 'im ok with that',
766
+ 'accept optional cookies',
767
+
768
+ /^alle (cookies )?akzeptieren$/is,
769
+ ];
770
+
771
+ export const ACKNOWLEDGE_PATTERNS = [
772
+ 'ok',
773
+ 'close',
774
+ 'continue',
775
+ 'x',
776
+ /^got it!?$/,
777
+ 'i understand',
778
+ 'dismiss',
779
+ 'okay',
780
+ 'acknowledge',
781
+ /^close (banner|cookie notification)$/is,
782
+ /understood$/is,
783
+ 'confirm my choices',
784
+ ];