@duckduckgo/autoconsent 16.26.0 → 16.28.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 (63) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +45 -0
  3. package/dist/addon-firefox/compact-rules.json +1 -1
  4. package/dist/addon-firefox/content.bundle.js +28 -3
  5. package/dist/addon-firefox/manifest.json +1 -1
  6. package/dist/addon-firefox/rule-index.json +1 -1
  7. package/dist/addon-firefox/rules.json +1 -1
  8. package/dist/addon-mv3/compact-rules.json +1 -1
  9. package/dist/addon-mv3/content.bundle.js +28 -3
  10. package/dist/addon-mv3/manifest.json +1 -1
  11. package/dist/addon-mv3/rule-index.json +1 -1
  12. package/dist/addon-mv3/rules.json +1 -1
  13. package/dist/autoconsent.cjs.js +28 -3
  14. package/dist/autoconsent.esm.js +28 -3
  15. package/dist/autoconsent.playwright.js +28 -3
  16. package/dist/autoconsent.standalone.js +29 -4
  17. package/dist/types/cmps/base.d.ts +1 -0
  18. package/dist/types/dom-actions.d.ts +1 -0
  19. package/dist/types/rules.d.ts +6 -2
  20. package/dist/types/utils.d.ts +1 -0
  21. package/docs/rule-syntax.md +14 -0
  22. package/lib/cmps/base.ts +7 -0
  23. package/lib/dom-actions.ts +7 -1
  24. package/lib/heuristic-patterns.ts +2 -2
  25. package/lib/rules.ts +9 -2
  26. package/lib/utils.ts +16 -0
  27. package/package.json +1 -1
  28. package/rules/autoconsent/amazon-pay.json +13 -0
  29. package/rules/autoconsent/bbva.json +12 -0
  30. package/rules/autoconsent/complianz-opt-out.json +6 -0
  31. package/rules/autoconsent/deliveroo.json +15 -0
  32. package/rules/autoconsent/ebay.json +34 -0
  33. package/rules/autoconsent/gemini-google-com.json +9 -0
  34. package/rules/autoconsent/justwatch-com.json +1 -1
  35. package/rules/autoconsent/kingrecords-co-jp.json +21 -0
  36. package/rules/autoconsent/paypal-us.json +4 -3
  37. package/rules/autoconsent/pornpics-de.json +1 -1
  38. package/rules/autoconsent/rule34-xxx.json +12 -0
  39. package/rules/autoconsent/sex.com.json +45 -0
  40. package/rules/autoconsent/theguardian.com.json +13 -3
  41. package/rules/autoconsent/twitch.json +27 -12
  42. package/rules/autoconsent/wpconsent.json +30 -0
  43. package/rules/autoconsent/xhamster-eu.json +3 -1
  44. package/rules/compact-rules.json +1 -1
  45. package/rules/rule-index.json +1 -1
  46. package/rules/rules.json +1 -1
  47. package/tests/amazon-pay.spec.ts +3 -0
  48. package/tests/bbva.spec.ts +4 -0
  49. package/tests/complianz-opt-out.spec.ts +5 -0
  50. package/tests/deliveroo.spec.ts +3 -0
  51. package/tests/gemini-google-com.spec.ts +5 -0
  52. package/tests/justwatch-com.spec.ts +1 -1
  53. package/tests/kingrecords-co-jp.spec.ts +3 -0
  54. package/tests/paypal-us.spec.ts +4 -0
  55. package/tests/pornpics-de.spec.ts +1 -1
  56. package/tests/rule34-xxx.spec.ts +6 -0
  57. package/tests/sex.com.spec.ts +3 -0
  58. package/tests/wpconsent.spec.ts +5 -0
  59. package/tests-wtr/dom-actions/dom-actions.stylesheet.html +13 -0
  60. package/tests-wtr/dom-actions/dom-actions.stylesheet.ts +26 -0
  61. package/tests-wtr/heuristics/heuristics-utils.test.ts +6 -0
  62. package/tests-wtr/utils/append-stylesheet-rule.html +11 -0
  63. package/tests-wtr/utils/append-stylesheet-rule.test.ts +29 -0
@@ -32,6 +32,7 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
32
32
  waitForThenClick(selector: ElementSelector, timeout?: number, all?: boolean): Promise<boolean>;
33
33
  wait(ms: number): Promise<true>;
34
34
  hide(selector: string, method?: HideMethod): boolean;
35
+ stylesheet(cssRule: string, stylesheetId?: string): boolean;
35
36
  removeClass(selector: ElementSelector, className: string): boolean;
36
37
  setStyle(selector: ElementSelector, css: string): boolean;
37
38
  addStyle(selector: ElementSelector, css: string): boolean;
@@ -14,6 +14,7 @@ export declare class DomActions implements DomActionsProvider {
14
14
  wait(ms: number): Promise<true>;
15
15
  cookieContains(substring: string): boolean;
16
16
  hide(selector: string, method?: HideMethod): boolean;
17
+ stylesheet(cssRule: string, stylesheetId?: string): boolean;
17
18
  removeClass(selector: ElementSelector, className: string): boolean;
18
19
  setStyle(selector: ElementSelector, css: string): boolean;
19
20
  addStyle(selector: ElementSelector, css: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import { snippets } from './eval-snippets';
2
- export declare const SUPPORTED_RULE_STEP_VERSION = 2;
2
+ export declare const SUPPORTED_RULE_STEP_VERSION = 3;
3
3
  export type AutoConsentCMPRule = {
4
4
  readonly name: string;
5
5
  /**
@@ -33,7 +33,7 @@ export type ElementSelector = string | string[];
33
33
  export type AutoConsentRuleStep = {
34
34
  optional?: boolean;
35
35
  comment?: string;
36
- } & Partial<ElementExistsRule> & Partial<ElementVisibleRule> & Partial<EvalRule> & Partial<WaitForRule> & Partial<WaitForVisibleRule> & Partial<ClickRule> & Partial<WaitForThenClickRule> & Partial<WaitRule> & Partial<HideRule> & Partial<IfRule> & Partial<AnyRule> & Partial<NegatedRule> & Partial<CookieContainsRule> & Partial<RemoveClassRule> & Partial<SetStyleRule> & Partial<AddStyleRule>;
36
+ } & Partial<ElementExistsRule> & Partial<ElementVisibleRule> & Partial<EvalRule> & Partial<WaitForRule> & Partial<WaitForVisibleRule> & Partial<ClickRule> & Partial<WaitForThenClickRule> & Partial<WaitRule> & Partial<HideRule> & Partial<IfRule> & Partial<AnyRule> & Partial<NegatedRule> & Partial<CookieContainsRule> & Partial<RemoveClassRule> & Partial<SetStyleRule> & Partial<AddStyleRule> & Partial<StylesheetRule>;
37
37
  export type NegatedRule = {
38
38
  negated: boolean;
39
39
  };
@@ -97,3 +97,7 @@ export type AddStyleRule = {
97
97
  addStyle: string;
98
98
  selector: ElementSelector;
99
99
  };
100
+ export type StylesheetRule = {
101
+ stylesheet: string;
102
+ stylesheetId?: string;
103
+ };
@@ -3,6 +3,7 @@ import { Config } from './types';
3
3
  export declare function getStyleElement(styleOverrideElementId?: string): HTMLStyleElement;
4
4
  export declare function getHidingStyle(method: HideMethod): string;
5
5
  export declare function hideElements(styleEl: HTMLStyleElement, selector: string, method?: HideMethod): boolean;
6
+ export declare function appendStylesheetRule(styleEl: HTMLStyleElement, cssRule: string, marker?: string): boolean;
6
7
  export declare function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean>;
7
8
  export declare function isElementVisible(elem: HTMLElement): boolean;
8
9
  export declare function copyObject(data: any): any;
@@ -154,6 +154,18 @@ Set the inline style of the elements matched by the `selector`. `style-string` i
154
154
  ```
155
155
  Append the inline style of the elements matched by the `selector`. `style-string` is a string of CSS properties and values. The style is appended to the existing inline style, separated by a semicolon.
156
156
 
157
+ ## Stylesheet
158
+
159
+ ```javascript
160
+ {
161
+ "stylesheet": "css-rule-string",
162
+ "stylesheetId": "unique-marker"
163
+ }
164
+ ```
165
+ Append a CSS rule (e.g. `".overlay { display: none !important; }"`) to the style element injected by autoconsent. Unlike `setStyle`/`addStyle`, which modify inline styles of currently matched elements, an appended stylesheet rule keeps applying to elements matched later — useful when the page re-adds classes or inline styles after the opt-out (e.g. scroll locks reapplied on back/forward navigation).
166
+
167
+ The optional `stylesheetId` is a marker used to prevent duplicate inserts when the rule runs multiple times; if omitted, the CSS rule text itself is used for deduplication.
168
+
157
169
  ## Cookie match
158
170
  ```javascript
159
171
  {
@@ -241,11 +253,13 @@ The `minimumRuleStepVersion` field solves this: clients compare the rule's decla
241
253
  |---------|-----------------|
242
254
  | 1 | All original step types (`exists`, `visible`, `waitFor`, `waitForVisible`, `click`, `waitForThenClick`, `wait`, `hide`, `if`/`then`/`else`, `any`, `eval`, `cookieContains`, `negated`) |
243
255
  | 2 | `removeClass`, `setStyle`, `addStyle` |
256
+ | 3 | `stylesheet` |
244
257
 
245
258
  ### When to set it
246
259
 
247
260
  - If a rule only uses version-1 step types, omit the field (defaults to `1`).
248
261
  - If a rule uses `removeClass`, `setStyle`, or `addStyle`, set `"minimumRuleStepVersion": 2`.
262
+ - If a rule uses `stylesheet`, set `"minimumRuleStepVersion": 3`.
249
263
  - When a future version introduces new step types, any rule using them must set `minimumRuleStepVersion` to the corresponding version number.
250
264
 
251
265
  ### Adding new step types
package/lib/cmps/base.ts CHANGED
@@ -196,6 +196,10 @@ export default class AutoConsentCMPBase implements AutoCMP, DomActionsProvider {
196
196
  return this.autoconsent.domActions.hide(selector, method);
197
197
  }
198
198
 
199
+ stylesheet(cssRule: string, stylesheetId?: string) {
200
+ return this.autoconsent.domActions.stylesheet(cssRule, stylesheetId);
201
+ }
202
+
199
203
  removeClass(selector: ElementSelector, className: string) {
200
204
  return this.autoconsent.domActions.removeClass(selector, className);
201
205
  }
@@ -340,6 +344,9 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
340
344
  if (rule.hide) {
341
345
  results.push(this.hide(rule.hide, rule.method));
342
346
  }
347
+ if (rule.stylesheet !== undefined) {
348
+ results.push(this.stylesheet(rule.stylesheet, rule.stylesheetId));
349
+ }
343
350
  if (rule.removeClass !== undefined) {
344
351
  results.push(rule.selector ? this.removeClass(rule.selector, rule.removeClass) : false);
345
352
  }
@@ -1,6 +1,6 @@
1
1
  import { ElementSelector, HideMethod, VisibilityCheck } from './rules';
2
2
  import { DomActionsProvider } from './types';
3
- import { getStyleElement, hideElements, isElementVisible, waitFor } from './utils';
3
+ import { getStyleElement, hideElements, appendStylesheetRule, isElementVisible, waitFor } from './utils';
4
4
  import AutoConsent from './web';
5
5
 
6
6
  export class DomActions implements DomActionsProvider {
@@ -91,6 +91,12 @@ export class DomActions implements DomActionsProvider {
91
91
  return hideElements(styleEl, selector, method);
92
92
  }
93
93
 
94
+ stylesheet(cssRule: string, stylesheetId?: string): boolean {
95
+ this.autoconsentInstance.config.logs.rulesteps && console.log('[stylesheet]', cssRule, stylesheetId);
96
+ const styleEl = getStyleElement();
97
+ return appendStylesheetRule(styleEl, cssRule, stylesheetId);
98
+ }
99
+
94
100
  removeClass(selector: ElementSelector, className: string): boolean {
95
101
  const elements = this.elementSelector(selector);
96
102
  this.autoconsentInstance.config.logs.rulesteps && console.log('[removeClass]', selector, className, elements);
@@ -163,8 +163,8 @@ const REJECT_PATTERNS_ENGLISH = [
163
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
- // e.g. "i do not accept", "do not accept cookies"
167
- /^\s*(i\s+)?do\s+not\s+accept\s*(cookies)?\s*$/is,
166
+ // e.g. "i do not accept", "do not accept cookies", "i do not accept the use of cookies"
167
+ /^\s*(no,?\s*)?(i\s+)?do\s+not\s+accept\s*(the\s+use\s+of\s+)?(any\s+)?(cookies)?\s*$/is,
168
168
 
169
169
  // e.g. "continue without accepting", "continue without agreeing", "continue without agreeing →"
170
170
  /^\s*(continue|proceed|continue\s+browsing)\s+without\s+(accepting|agreeing|consent|cookies|tracking)(\s*→)?\s*$/is,
package/lib/rules.ts CHANGED
@@ -2,7 +2,8 @@ import { snippets } from './eval-snippets';
2
2
 
3
3
  // 1 - the first version of the rule step format
4
4
  // 2 - added removeClass, setStyle, addStyle
5
- export const SUPPORTED_RULE_STEP_VERSION = 2;
5
+ // 3 - added stylesheet
6
+ export const SUPPORTED_RULE_STEP_VERSION = 3;
6
7
 
7
8
  export type AutoConsentCMPRule = {
8
9
  readonly name: string;
@@ -52,7 +53,8 @@ export type AutoConsentRuleStep = { optional?: boolean; comment?: string } & Par
52
53
  Partial<CookieContainsRule> &
53
54
  Partial<RemoveClassRule> &
54
55
  Partial<SetStyleRule> &
55
- Partial<AddStyleRule>;
56
+ Partial<AddStyleRule> &
57
+ Partial<StylesheetRule>;
56
58
 
57
59
  export type NegatedRule = {
58
60
  negated: boolean;
@@ -134,3 +136,8 @@ export type AddStyleRule = {
134
136
  addStyle: string;
135
137
  selector: ElementSelector;
136
138
  };
139
+
140
+ export type StylesheetRule = {
141
+ stylesheet: string;
142
+ stylesheetId?: string;
143
+ };
package/lib/utils.ts CHANGED
@@ -32,6 +32,22 @@ export function hideElements(styleEl: HTMLStyleElement, selector: string, method
32
32
  return false;
33
33
  }
34
34
 
35
+ // append a stylesheet rule once; marker is used to avoid duplicate inserts on re-runs
36
+ export function appendStylesheetRule(styleEl: HTMLStyleElement, cssRule: string, marker?: string): boolean {
37
+ if (!(styleEl instanceof HTMLStyleElement) || cssRule.length === 0) {
38
+ return false;
39
+ }
40
+ const markerComment = marker ? `/* autoconsent:${marker} */` : '';
41
+ if (markerComment && styleEl.innerText.includes(markerComment)) {
42
+ return true;
43
+ }
44
+ if (!markerComment && styleEl.innerText.includes(cssRule.trim())) {
45
+ return true;
46
+ }
47
+ styleEl.innerText += `${markerComment}${markerComment ? ' ' : ''}${cssRule} `;
48
+ return true;
49
+ }
50
+
35
51
  export async function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean> {
36
52
  const result = await predicate();
37
53
  if (!result && maxTimes > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "16.26.0",
3
+ "version": "16.28.0",
4
4
  "description": "",
5
5
  "types": "./dist/types/web.d.ts",
6
6
  "exports": {
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "amazon-pay",
3
+ "vendorUrl": "https://pay.amazon.com/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://pay\\.amazon\\."
6
+ },
7
+ "prehideSelectors": [".cookie-menu.js-cookie-banner"],
8
+ "detectCmp": [{ "exists": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
9
+ "detectPopup": [{ "visible": ".cookie-menu.js-cookie-banner" }],
10
+ "optIn": [{ "click": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
11
+ "optOut": [{ "click": ".cookie-menu.js-cookie-banner .js-cookie-banner-close" }],
12
+ "test": [{ "cookieContains": "apaycnst-mktg=true" }]
13
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "bbva",
3
+ "prehideSelectors": [".cookiesgdpr__base:has(.cookiesgdpr__rejectbtn)"],
4
+ "detectCmp": [{ "exists": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" }],
5
+ "detectPopup": [{ "visible": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" }],
6
+ "optIn": [{ "waitForThenClick": ".cookiesgdpr__base .cookiesgdpr__acceptbtn" }],
7
+ "optOut": [
8
+ { "waitForThenClick": ".cookiesgdpr__base .cookiesgdpr__rejectbtn" },
9
+ { "waitForVisible": ".cookiesgdpr__base .cookiesgdpr__wrapper", "check": "none" }
10
+ ],
11
+ "test": [{ "cookieContains": "\"analitica\":false" }]
12
+ }
@@ -26,6 +26,12 @@
26
26
  }
27
27
  ],
28
28
  "optOut": [
29
+ {
30
+ "comment": "the popup markup exists before the library opens it; clicking too early gets undone by the pending open",
31
+ "waitFor": "[aria-describedby=\"cookieconsent:desc\"].cc-type-opt-out:not(.cc-invisible)",
32
+ "timeout": 2000,
33
+ "optional": true
34
+ },
29
35
  {
30
36
  "if": {
31
37
  "exists": ".cc-deny"
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "deliveroo",
3
+ "runContext": {
4
+ "urlPattern": "^https?://(\\w+\\.)?deliveroo\\.(co\\.uk|\\w+)/"
5
+ },
6
+ "prehideSelectors": ["div[role=\"dialog\"][class*=\"CustomCookieBanner\"]"],
7
+ "detectCmp": [{ "exists": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]" }],
8
+ "detectPopup": [{ "visible": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]" }],
9
+ "optIn": [{ "waitForThenClick": "#onetrust-consent-sdk #accept-recommended-btn-handler" }],
10
+ "optOut": [
11
+ { "waitForThenClick": "#onetrust-consent-sdk .ot-pc-refuse-all-handler" },
12
+ { "waitForVisible": "div[role=\"dialog\"][class*=\"CustomCookieBanner\"]", "check": "none" }
13
+ ],
14
+ "test": [{ "cookieContains": "OptanonAlertBoxClosed" }]
15
+ }
@@ -21,11 +21,45 @@
21
21
  "optIn": [
22
22
  {
23
23
  "waitForThenClick": "#gdpr-banner-accept"
24
+ },
25
+ {
26
+ "waitForVisible": "#gdpr-banner",
27
+ "check": "none",
28
+ "timeout": 2000,
29
+ "optional": true
30
+ },
31
+ {
32
+ "if": {
33
+ "visible": "#gdpr-banner"
34
+ },
35
+ "then": [
36
+ {
37
+ "waitForThenClick": "#gdpr-banner-accept",
38
+ "timeout": 2000
39
+ }
40
+ ]
24
41
  }
25
42
  ],
26
43
  "optOut": [
27
44
  {
28
45
  "waitForThenClick": "#gdpr-banner-decline"
46
+ },
47
+ {
48
+ "waitForVisible": "#gdpr-banner",
49
+ "check": "none",
50
+ "timeout": 2000,
51
+ "optional": true
52
+ },
53
+ {
54
+ "if": {
55
+ "visible": "#gdpr-banner"
56
+ },
57
+ "then": [
58
+ {
59
+ "waitForThenClick": "#gdpr-banner-decline",
60
+ "timeout": 2000
61
+ }
62
+ ]
29
63
  }
30
64
  ]
31
65
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "gemini.google.com",
3
+ "prehideSelectors": ["[data-test-id=\"cookie-banner-body\"]"],
4
+ "detectCmp": [{ "exists": "cookie-banner [data-test-id=\"cookie-banner-body\"] [data-test-id=\"reject-button\"]" }],
5
+ "detectPopup": [{ "visible": "[data-test-id=\"cookie-banner-body\"]" }],
6
+ "optIn": [{ "waitForThenClick": "[data-test-id=\"cookie-banner-body\"] [data-test-id=\"accept-button\"]" }],
7
+ "optOut": [{ "waitForThenClick": "[data-test-id=\"cookie-banner-body\"] [data-test-id=\"reject-button\"]" }],
8
+ "test": []
9
+ }
@@ -7,7 +7,7 @@
7
7
  "optOut": [
8
8
  { "click": ".consent-banner__actions button.basic-button.secondary" },
9
9
  { "waitForThenClick": ".consent-modal__footer button.basic-button.secondary" },
10
- { "waitForThenClick": ".consent-modal ion-content > div > a:nth-child(9)" },
10
+ { "waitForThenClick": ".consent-modal .base-modal__body a", "all": true },
11
11
  { "click": "label.consent-switch input[type=checkbox]:checked", "all": true, "optional": true },
12
12
  { "waitForVisible": ".consent-modal__footer button.basic-button.primary" },
13
13
  { "click": ".consent-modal__footer button.basic-button.primary" }
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "kingrecords-co-jp",
3
+ "vendorUrl": "https://www.kingrecords.co.jp/",
4
+ "runContext": {
5
+ "urlPattern": "^https?://(?:[^/]+\\.)?kingrecords\\.co\\.jp/"
6
+ },
7
+ "prehideSelectors": [".cookie:has(.cookie__inner .cookie__button)"],
8
+ "detectCmp": [{ "exists": ".cookie > .cookie__inner > .cookie__button > a" }],
9
+ "detectPopup": [{ "visible": ".cookie > .cookie__inner > .cookie__button > a" }],
10
+ "optIn": [
11
+ { "waitFor": "#loader.is--show", "timeout": 10000, "optional": true },
12
+ { "waitForThenClick": ".cookie > .cookie__inner > .cookie__button > a" }
13
+ ],
14
+ "optOut": [
15
+ { "waitFor": "#loader.is--show", "timeout": 10000, "optional": true },
16
+ { "waitForThenClick": ".cookie > .cookie__inner > .cookie__button > a" },
17
+ { "if": { "visible": ".cookie" }, "then": [{ "wait": 1000 }, { "click": ".cookie > .cookie__inner > .cookie__button > a" }] },
18
+ { "waitForVisible": ".cookie", "check": "none", "timeout": 5000 }
19
+ ],
20
+ "test": [{ "cookieContains": "cookiesubmit=1" }]
21
+ }
@@ -27,16 +27,17 @@
27
27
  "else": [
28
28
  { "waitForVisible": ".privacy-sheet-content #formContent" },
29
29
  {
30
- "click": "#formContent .cookiepref-11m2iee-checkbox_base input:checked",
30
+ "click": "#formContent input[type='checkbox']:checked",
31
31
  "all": true,
32
32
  "optional": true
33
33
  },
34
- { "click": ".cookieAction.saveCookie,.confirmCookie #submitCookiesBtn" }
34
+ { "click": ".cookieAction #submitCookiesBtn" }
35
35
  ]
36
36
  }
37
37
  ]
38
38
  }
39
39
  ]
40
40
  }
41
- ]
41
+ ],
42
+ "test": [{ "wait": 1000 }, { "cookieContains": "type%3Dexplicit" }]
42
43
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pornpics.de",
3
3
  "runContext": {
4
- "urlPattern": "^https?://(www\\.)?pornpics\\.de/"
4
+ "urlPattern": "^https?://(www\\.)?pornpics\\.(com|de)/"
5
5
  },
6
6
  "detectCmp": [{ "exists": "#cookie-form" }],
7
7
  "detectPopup": [{ "visible": "#cookie-form" }],
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "rule34-xxx",
3
+ "runContext": {
4
+ "urlPattern": "^https?://(www\\.)?rule34\\.xxx/"
5
+ },
6
+ "prehideSelectors": ["#gdprconsent.gdprcontainer"],
7
+ "detectCmp": [{ "exists": "#gdprconsent.gdprcontainer" }],
8
+ "detectPopup": [{ "visible": "#gdprconsent.gdprcontainer" }],
9
+ "optIn": [{ "waitForThenClick": "#gdprconsent .gdprbutton button" }],
10
+ "optOut": [{ "waitForThenClick": "#gdprconsent .gdprbutton a" }],
11
+ "test": [{ "cookieContains": "gdpr-consent=0" }]
12
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "sex.com",
3
+ "runContext": {
4
+ "urlPattern": "^https?://(\\w+\\.)?sex\\.com/"
5
+ },
6
+ "prehideSelectors": ["[class*=\"cookie-consent-required\"]"],
7
+ "detectCmp": [
8
+ {
9
+ "comment": "the flag is cleared once consent is stored, so it prevents re-detecting the leftover markup",
10
+ "exists": "html[data-cookie-consent-required] [data-testid=\"accept-all-cookie-button\"]"
11
+ }
12
+ ],
13
+ "detectPopup": [
14
+ {
15
+ "visible": "[data-testid=\"accept-all-cookie-button\"]"
16
+ }
17
+ ],
18
+ "optIn": [
19
+ {
20
+ "comment": "the modal is server-rendered, buttons only respond once the dialog is hydrated and locks scrolling",
21
+ "waitFor": "body[data-scroll-locked]"
22
+ },
23
+ {
24
+ "waitForThenClick": "[data-testid=\"accept-all-cookie-button\"]"
25
+ }
26
+ ],
27
+ "optOut": [
28
+ {
29
+ "comment": "the modal is server-rendered, buttons only respond once the dialog is hydrated and locks scrolling",
30
+ "waitFor": "body[data-scroll-locked]"
31
+ },
32
+ {
33
+ "comment": "optional categories default to off, so confirming the defaults rejects them",
34
+ "waitForThenClick": "[data-testid=\"customize-cookie-button\"]"
35
+ },
36
+ {
37
+ "waitForThenClick": "[data-testid=\"confirm-settings-cookie-button\"]"
38
+ }
39
+ ],
40
+ "test": [
41
+ {
42
+ "cookieContains": "%22analytics%22%3Afalse"
43
+ }
44
+ ]
45
+ }
@@ -3,7 +3,7 @@
3
3
  "vendorUrl": "https://www.theguardian.com/",
4
4
  "comment": "Sourcepoint 'consent or pay' wall shown in the EU/EEA (message 1482251) and UK (message 1482252), where the only reject option requires a paid subscription. The container id embeds the Sourcepoint message id, so we target the pay-wall variants specifically to avoid clashing with the free-reject Sourcepoint popup used in other regions (handled by Sourcepoint-frame). Brittle: message ids may change if the campaign is reconfigured.",
5
5
  "cosmetic": true,
6
- "minimumRuleStepVersion": 2,
6
+ "minimumRuleStepVersion": 3,
7
7
  "runContext": {
8
8
  "urlPattern": "^https?://(\\w+\\.)?theguardian\\.com/",
9
9
  "main": true,
@@ -11,11 +11,21 @@
11
11
  },
12
12
  "prehideSelectors": ["div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']"],
13
13
  "detectCmp": [{ "exists": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" }],
14
- "detectPopup": [{ "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" }],
14
+ "detectPopup": [
15
+ {
16
+ "any": [
17
+ { "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" },
18
+ { "exists": "html.sp-message-open" }
19
+ ]
20
+ }
21
+ ],
15
22
  "optIn": [],
16
23
  "optOut": [
17
24
  { "hide": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']" },
18
- { "removeClass": "sp-message-open", "selector": "html", "optional": true }
25
+ {
26
+ "stylesheet": "html.sp-message-open body { position: static !important; top: auto !important; width: auto !important; overflow: auto !important; }",
27
+ "stylesheetId": "theguardian-scroll-unlock"
28
+ }
19
29
  ],
20
30
  "test": [{ "visible": "div[id='sp_message_container_1482251'],div[id='sp_message_container_1482252']", "check": "none" }]
21
31
  }
@@ -1,20 +1,35 @@
1
1
  {
2
2
  "name": "twitch.tv",
3
3
  "runContext": {
4
- "urlPattern": "^https?://([\\w-]+\\.)?twitch\\.tv"
4
+ "urlPattern": "^https?://([\\w-]+\\.)?twitch\\.tv/"
5
5
  },
6
- "prehideSelectors": [
7
- "div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"
6
+ "prehideSelectors": ["[data-a-target=\"consent-banner\"],.ReactModalPortal:has([data-a-target=consent-modal-save])"],
7
+ "detectCmp": [{ "exists": "[data-a-target=\"consent-banner\"]" }],
8
+ "detectPopup": [{ "visible": "[data-a-target=\"consent-banner\"]" }],
9
+ "optIn": [
10
+ {
11
+ "if": { "exists": "[data-a-target=\"consent-banner-accept\"]" },
12
+ "then": [{ "click": "button[data-a-target=\"consent-banner-accept\"]" }],
13
+ "else": [{ "click": "[data-a-target=\"consent-banner\"] button:not([data-a-target])" }]
14
+ }
8
15
  ],
9
- "detectCmp": [{ "exists": ".consent-banner .consent-banner__content--gdpr-v2" }],
10
- "detectPopup": [{ "visible": ".consent-banner .consent-banner__content--gdpr-v2" }],
11
- "optIn": [{ "click": "button[data-a-target=\"consent-banner-accept\"]" }],
12
16
  "optOut": [
13
- { "hide": "div:has(> .consent-banner .consent-banner__content--gdpr-v2)" },
14
- { "click": "button[data-a-target=\"consent-banner-manage-preferences\"]" },
15
- { "waitFor": "input[type=checkbox][data-a-target=tw-checkbox]" },
16
- { "click": "input[type=checkbox][data-a-target=tw-checkbox][checked]:not([disabled])", "all": true, "optional": true },
17
- { "waitForThenClick": "[data-a-target=consent-modal-save]" },
18
- { "waitForVisible": ".ReactModalPortal:has([data-a-target=consent-modal-save])", "check": "none" }
17
+ {
18
+ "if": { "exists": "[data-a-target=\"consent-banner-accept\"]" },
19
+ "then": [
20
+ { "click": "[data-a-target=\"consent-banner\"] button:not([data-a-target])" },
21
+ { "waitForVisible": "[data-a-target=\"consent-banner\"]", "check": "none" }
22
+ ],
23
+ "else": [
24
+ { "click": "button[data-a-target=\"consent-banner-manage-preferences\"]" },
25
+ { "waitFor": "input[type=checkbox][data-a-target=tw-checkbox]" },
26
+ {
27
+ "click": "input[type=checkbox][data-a-target=tw-checkbox]:not(:checked):not([disabled])",
28
+ "optional": true
29
+ },
30
+ { "waitForThenClick": "[data-a-target=consent-modal-save]" },
31
+ { "waitForVisible": ".ReactModalPortal:has([data-a-target=consent-modal-save])", "check": "none" }
32
+ ]
33
+ }
19
34
  ]
20
35
  }
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "WPConsent",
3
+ "vendorUrl": "https://wordpress.org/plugins/wpconsent-cookies-banner-privacy-suite/",
4
+ "prehideSelectors": ["#wpconsent-root"],
5
+ "detectCmp": [
6
+ {
7
+ "exists": "#wpconsent-container"
8
+ }
9
+ ],
10
+ "detectPopup": [
11
+ {
12
+ "visible": ["#wpconsent-container", "#wpconsent-banner-holder.wpconsent-banner-visible"]
13
+ }
14
+ ],
15
+ "optIn": [
16
+ {
17
+ "waitForThenClick": ["#wpconsent-container", "#wpconsent-accept-all"]
18
+ }
19
+ ],
20
+ "optOut": [
21
+ {
22
+ "waitForThenClick": ["#wpconsent-container", "#wpconsent-cancel-all"]
23
+ }
24
+ ],
25
+ "test": [
26
+ {
27
+ "cookieContains": "wpconsent_preferences="
28
+ }
29
+ ]
30
+ }
@@ -8,7 +8,9 @@
8
8
  "detectPopup": [{ "visible": "[data-role=\"cookies-modal\"] [data-opt-hydration=\"cookies-dialog-eu\"]" }],
9
9
  "optIn": [{ "waitForThenClick": "[data-role=\"cookies-modal\"] [class*=experimentalButtons] button:nth-of-type(1)" }],
10
10
  "optOut": [
11
- { "waitForThenClick": "[data-role=\"cookies-modal\"] [class*=experimentalButtons] button:nth-of-type(2)" },
11
+ { "waitForVisible": "[data-role=\"cookies-modal\"] [class*=experimentalButtons] button:nth-of-type(2)" },
12
+ { "wait": 500 },
13
+ { "click": "[data-role=\"cookies-modal\"] [class*=experimentalButtons] button:nth-of-type(2)" },
12
14
  { "hide": "[data-role=\"cookies-modal\"] [class*=container]", "optional": true }
13
15
  ]
14
16
  }