@duckduckgo/autoconsent 16.12.0 → 16.13.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.
@@ -3,7 +3,8 @@ export declare const DETECT_PATTERNS: RegExp[];
3
3
  * @type {Array<string|RegExp>}
4
4
  */
5
5
  export declare const REJECT_PATTERNS: (string | RegExp)[];
6
- export declare const NEVER_MATCH_PATTERNS: RegExp[];
6
+ export declare const BUTTON_NEVER_MATCH_PATTERNS: RegExp[];
7
+ export declare const DETECT_NEVER_MATCH_PATTERNS: RegExp[];
7
8
  export declare const SETTINGS_PATTERNS: (string | RegExp)[];
8
9
  export declare const ACCEPT_PATTERNS: (string | RegExp)[];
9
10
  export declare const ACKNOWLEDGE_PATTERNS: (string | RegExp)[];
@@ -5,6 +5,7 @@ export declare function checkHeuristicPatterns(allText: string, detectPatterns?:
5
5
  };
6
6
  export declare function getActionablePopups(mode?: HeuristicLevel, timeout?: number): PopupData[];
7
7
  export declare function classifyButtons(buttons: ButtonData[]): void;
8
+ export declare function isExcludedPopup(popupText: string, excludePatterns?: RegExp[]): boolean;
8
9
  export declare function cleanButtonText(buttonText: string): string;
9
10
  export declare function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification;
10
11
  export declare function isDialogLikeElement(node: HTMLElement): boolean;
@@ -160,7 +160,7 @@ export const DETECT_PATTERNS = [
160
160
  const REJECT_PATTERNS_ENGLISH = [
161
161
  // e.g. "reject", "reject all", "reject all cookies", "deny all", "refuse cookies", "decline",
162
162
  // "reject non-essential cookies", "reject unnecessary cookies", "reject all but necessary", "reject all and close"
163
- // note that "reject and subscribe" and "reject and pay" are excluded via NEVER_MATCH_PATTERNS
163
+ // note that "reject and subscribe" and "reject and pay" are excluded via BUTTON_NEVER_MATCH_PATTERNS
164
164
  /^\s*(no,?\s*)?(i\s+)?(reject|deny|refuse|decline|disable)\s*(all)?\s*(but|except)?\s*(non[- ]?essential|un(necessary|required)|optional|additional|targeting|analytics|marketing|non[- ]?necessary|extra|tracking|advertising|necessary|essential)?\s*(cookies)?\s*(and\s+close)?\s*$/is,
165
165
 
166
166
  // e.g. "i do not accept", "do not accept cookies"
@@ -224,7 +224,7 @@ const REJECT_PATTERNS_DUTCH = [
224
224
 
225
225
  const REJECT_PATTERNS_FRENCH = [
226
226
  // refuser / rejeter / interdire / décliner (reject verbs, any position)
227
- // "refuser et s'abonner" / "refuser et payer" are excluded via NEVER_MATCH_PATTERNS
227
+ // "refuser et s'abonner" / "refuser et payer" are excluded via BUTTON_NEVER_MATCH_PATTERNS
228
228
  /(^|\s)(refus|rejet|rejeter|interdire|interdis|déclin|declin)/is,
229
229
 
230
230
  // only necessary / essential / technical / functional
@@ -298,7 +298,7 @@ const REJECT_PATTERNS_BRAZILIAN_PORTUGUESE = [
298
298
 
299
299
  const REJECT_PATTERNS_SPANISH = [
300
300
  // rechazar / denegar / declinar / negar (reject verbs, any position)
301
- // "rechazar y pagar" / "rechazar y suscribirse" are excluded via NEVER_MATCH_PATTERNS
301
+ // "rechazar y pagar" / "rechazar y suscribirse" are excluded via BUTTON_NEVER_MATCH_PATTERNS
302
302
  /(^|\s)(rechaz|recház|deneg|negar|declin)/is,
303
303
 
304
304
  // accept/allow/use (only) necessary / essential / technical / functional / own
@@ -394,7 +394,7 @@ export const REJECT_PATTERNS = [
394
394
  ...REJECT_PATTERNS_INDONESIAN,
395
395
  ];
396
396
 
397
- export const NEVER_MATCH_PATTERNS = [
397
+ export const BUTTON_NEVER_MATCH_PATTERNS = [
398
398
  /pay|subscribe/is,
399
399
  /abonneer/is,
400
400
  /abonnier/is,
@@ -418,6 +418,23 @@ export const NEVER_MATCH_PATTERNS = [
418
418
  /subskrybuj/,
419
419
  ];
420
420
 
421
+ // Popup body-text patterns that suppress heuristic detection. Currently targets age gates and adult-content disclaimers whose "reject" button leads to a dead end.
422
+ // Applied only to popup-scoped text (via getActionablePopups); NOT applied to full-document detection, where these terms produce too many false positives.
423
+ export const DETECT_NEVER_MATCH_PATTERNS = [
424
+ // e.g. "age verification", "age confirmation", "age check", "age gate", "age restriction"
425
+ /age\s+(?:verification|confirmation|check|gate|restriction)/i,
426
+ // e.g. "over 18 years", "at least 21 years", "older than 21", "18+"
427
+ /(?:over|above|at\s*least|minimum|older\s+than)\s*(?:18|21)\s*(?:years|yo|y\.?o\.?|\+)?/i,
428
+ // e.g. "18 years of age", "18+ years old", "21 years or older"
429
+ /(?:18|21)\s*(?:\+|years?)\s*(?:of\s*age|or\s*older|or\s*above)/i,
430
+ // e.g. "I am 18+", "I am over 18", "I'm 21 or older"
431
+ /(?:i'?m|i\s*am)\s*(?:over|above|at\s*least)?\s*(?:18|21)(?:\+|\s*(?:or\s*older|years))?/i,
432
+ // e.g. "you must be 18", "users must be at least 21", "visitors must be over 18"
433
+ /(?:you|users?|visitors?)\s+must\s+be\s+(?:over|above|at\s*least)?\s*(?:18|21)/i,
434
+ // e.g. "adult oriented material", "adult content", "adult-only website"
435
+ /adult[\s-]+(?:oriented|only|content|material|websites?)/i,
436
+ ];
437
+
421
438
  export const SETTINGS_PATTERNS = [
422
439
  // Multilingual "open customization" patterns: a customization verb next to a
423
440
  // cookie/preference/settings/options/details/purposes noun (both word orders).
package/lib/heuristics.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  ACCEPT_PATTERNS,
3
3
  ACKNOWLEDGE_PATTERNS,
4
+ BUTTON_NEVER_MATCH_PATTERNS,
5
+ DETECT_NEVER_MATCH_PATTERNS,
4
6
  DETECT_PATTERNS,
5
- NEVER_MATCH_PATTERNS,
6
7
  REJECT_PATTERNS,
7
8
  SETTINGS_PATTERNS,
8
9
  } from './heuristic-patterns';
@@ -44,6 +45,9 @@ export function getActionablePopups(mode: HeuristicLevel = 'reject', timeout = P
44
45
  const result = popups.reduce((acc, popup) => {
45
46
  const popupText = popup.text?.trim();
46
47
  if (popupText) {
48
+ if (isExcludedPopup(popupText)) {
49
+ return acc;
50
+ }
47
51
  const { patterns } = checkHeuristicPatterns(popupText);
48
52
  if (patterns.length > 0) {
49
53
  classifyButtons(popup.buttons);
@@ -67,6 +71,14 @@ export function classifyButtons(buttons: ButtonData[]) {
67
71
  }
68
72
  }
69
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
+
70
82
  function classifyPopup(buttons: ButtonData[]): PopupClassification {
71
83
  const { reject, settings, accept, acknowledge } = buttons.reduce(
72
84
  (acc, button) => {
@@ -129,16 +141,16 @@ export function cleanButtonText(buttonText: string): string {
129
141
  }
130
142
 
131
143
  export function classifyButtonTextRegex(buttonText: string): ButtonRegexClassification {
132
- if (testButtonMatches(buttonText, REJECT_PATTERNS, NEVER_MATCH_PATTERNS)) {
144
+ if (testButtonMatches(buttonText, REJECT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
133
145
  return 'reject';
134
146
  }
135
- if (testButtonMatches(buttonText, SETTINGS_PATTERNS, NEVER_MATCH_PATTERNS)) {
147
+ if (testButtonMatches(buttonText, SETTINGS_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
136
148
  return 'settings';
137
149
  }
138
- if (testButtonMatches(buttonText, ACCEPT_PATTERNS, NEVER_MATCH_PATTERNS)) {
150
+ if (testButtonMatches(buttonText, ACCEPT_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
139
151
  return 'accept';
140
152
  }
141
- if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, NEVER_MATCH_PATTERNS)) {
153
+ if (testButtonMatches(buttonText, ACKNOWLEDGE_PATTERNS, BUTTON_NEVER_MATCH_PATTERNS)) {
142
154
  return 'acknowledge';
143
155
  }
144
156
  return 'other';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.12.0",
3
+ "version": "16.13.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "unive-nl",
3
+ "_metadata": {
4
+ "vendorUrl": "https://www.unive.nl/"
5
+ },
6
+ "cosmetic": false,
7
+ "runContext": {
8
+ "main": true,
9
+ "frame": false,
10
+ "urlPattern": "^https?://(?:[^/]+\\.)?unive\\.nl/"
11
+ },
12
+ "prehideSelectors": ["#__tealiumGDPRcpPrefs #consent-container"],
13
+ "detectCmp": [
14
+ {
15
+ "exists": "#__tealiumGDPRcpPrefs #consent-container #consent-modal-settings"
16
+ }
17
+ ],
18
+ "detectPopup": [
19
+ {
20
+ "visible": "#__tealiumGDPRcpPrefs #consent-container"
21
+ }
22
+ ],
23
+ "optIn": [
24
+ {
25
+ "waitForThenClick": "#consent-popup-close-modal"
26
+ }
27
+ ],
28
+ "optOut": [
29
+ {
30
+ "waitForThenClick": "#no-consent-popup-close-modal"
31
+ }
32
+ ],
33
+ "test": [
34
+ {
35
+ "waitForVisible": "#__tealiumGDPRcpPrefs #consent-container",
36
+ "check": "none",
37
+ "timeout": 2000
38
+ }
39
+ ]
40
+ }