acpilot 2.2.0 → 2.3.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.
- package/dist/cli.js +188 -122
- package/package.json +11 -2
package/dist/cli.js
CHANGED
|
@@ -49955,16 +49955,135 @@ if (hasConsent) { addPreconnect("${href}"); }`,
|
|
|
49955
49955
|
});
|
|
49956
49956
|
|
|
49957
49957
|
// ../../src/lib/scanner/rules/impressum.ts
|
|
49958
|
+
async function fetchImpressumPage(impressumHref, baseUrl) {
|
|
49959
|
+
try {
|
|
49960
|
+
const absoluteUrl = new URL(impressumHref, baseUrl).href;
|
|
49961
|
+
console.log(`[Impressum] Fetching separate impressum page: ${absoluteUrl}`);
|
|
49962
|
+
const res = await fetch(absoluteUrl, {
|
|
49963
|
+
signal: AbortSignal.timeout(8e3),
|
|
49964
|
+
headers: { "User-Agent": "AccessPilot/1.0 (DSGVO Scanner)" }
|
|
49965
|
+
});
|
|
49966
|
+
if (!res.ok) return null;
|
|
49967
|
+
const contentType = res.headers.get("content-type") || "";
|
|
49968
|
+
if (!contentType.includes("text/html") && !contentType.includes("application/xhtml")) {
|
|
49969
|
+
return null;
|
|
49970
|
+
}
|
|
49971
|
+
const html3 = await res.text();
|
|
49972
|
+
const $2 = load(html3);
|
|
49973
|
+
const text3 = $2("body").text().replace(/\s+/g, " ");
|
|
49974
|
+
return { text: text3, html: html3, $: $2 };
|
|
49975
|
+
} catch (err) {
|
|
49976
|
+
console.warn(`[Impressum] Failed to fetch impressum page: ${err instanceof Error ? err.message : err}`);
|
|
49977
|
+
return null;
|
|
49978
|
+
}
|
|
49979
|
+
}
|
|
49980
|
+
function checkImpressumContent(normalizedText, rawHtml, $content, impressumHref, url, findings, indexRef) {
|
|
49981
|
+
const hasAddress = /\b\d{5}\s+\w/i.test(normalizedText) || // German PLZ (5 digits) + city
|
|
49982
|
+
/\b\d{4}\s+\w/i.test(normalizedText) || // Austrian PLZ (4 digits) + city
|
|
49983
|
+
/straße|str\.\s*\d|weg\s+\d|gasse|platz\s+\d|allee/i.test(normalizedText) || // Street patterns
|
|
49984
|
+
/\bstraße\b|\bstr\b/i.test(normalizedText);
|
|
49985
|
+
if (!hasAddress) {
|
|
49986
|
+
findings.push({
|
|
49987
|
+
id: `impressum-missing-address-${indexRef.value++}`,
|
|
49988
|
+
rule: "impressum",
|
|
49989
|
+
severity: "moderate",
|
|
49990
|
+
impact: "Ohne Anschrift im Impressum fehlt eine Pflichtangabe nach TMG \xA75.",
|
|
49991
|
+
description: "Keine postalische Anschrift im Impressum erkennbar",
|
|
49992
|
+
explanation: "Das Impressum muss die ladungsf\xE4hige Anschrift des Anbieters enthalten (Stra\xDFe, Hausnummer, PLZ und Ort).",
|
|
49993
|
+
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
49994
|
+
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
49995
|
+
fix: "Stellen Sie sicher, dass im Impressum eine vollst\xE4ndige Anschrift (Stra\xDFe, Hausnummer, PLZ, Ort) angegeben ist.",
|
|
49996
|
+
codeExample: "<p>Musterstra\xDFe 1<br>12345 Musterstadt</p>",
|
|
49997
|
+
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
49998
|
+
wcagLevel: "AA",
|
|
49999
|
+
wcagName: "Impressum Pflichtangaben \u2013 Anschrift",
|
|
50000
|
+
legalBasis: "DSGVO",
|
|
50001
|
+
pageUrl: url
|
|
50002
|
+
});
|
|
50003
|
+
}
|
|
50004
|
+
const hasEmail = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i.test(normalizedText) || /kontaktformular|contact\s*form/i.test(normalizedText) || /e-?mail\s*:/i.test(normalizedText) || // Check for mailto: links in HTML
|
|
50005
|
+
$content('a[href^="mailto:"]').length > 0 || /mailto:/i.test(rawHtml) || // Obfuscated emails: [at], (at), [dot]
|
|
50006
|
+
/\w+\s*[\[(]\s*at\s*[\])]\s*\w+/i.test(normalizedText);
|
|
50007
|
+
if (!hasEmail) {
|
|
50008
|
+
findings.push({
|
|
50009
|
+
id: `impressum-missing-email-${indexRef.value++}`,
|
|
50010
|
+
rule: "impressum",
|
|
50011
|
+
severity: "moderate",
|
|
50012
|
+
impact: "Ohne E-Mail-Adresse oder Kontaktm\xF6glichkeit fehlt eine Pflichtangabe im Impressum.",
|
|
50013
|
+
description: "Keine E-Mail-Adresse oder Kontaktm\xF6glichkeit im Impressum erkennbar",
|
|
50014
|
+
explanation: "Das Impressum muss eine M\xF6glichkeit zur schnellen elektronischen Kontaktaufnahme enthalten (in der Regel eine E-Mail-Adresse).",
|
|
50015
|
+
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50016
|
+
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50017
|
+
fix: "Geben Sie im Impressum eine E-Mail-Adresse an oder verweisen Sie auf ein Kontaktformular.",
|
|
50018
|
+
codeExample: "<p>E-Mail: info@example.de</p>",
|
|
50019
|
+
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50020
|
+
wcagLevel: "AA",
|
|
50021
|
+
wcagName: "Impressum Pflichtangaben \u2013 Kontakt",
|
|
50022
|
+
legalBasis: "DSGVO",
|
|
50023
|
+
pageUrl: url
|
|
50024
|
+
});
|
|
50025
|
+
}
|
|
50026
|
+
const hasPhone = (
|
|
50027
|
+
// Labeled patterns
|
|
50028
|
+
/tel\.?\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /telefon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /phone\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /mobil\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fax\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /handy\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /ruf\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || // International format: +49, +43, +41 etc. (without needing a label)
|
|
50029
|
+
/\+\d{1,3}\s*[(]?\d[\d\s()+\-/]{5,}/i.test(normalizedText) || // German landline/mobile without label: 0800, 030, 089, 0171 etc.
|
|
50030
|
+
/\b0\d{2,4}[\s/\-.]?\d{3,}[\d\s/\-.]*\d/i.test(normalizedText) || // tel: links in HTML
|
|
50031
|
+
$content('a[href^="tel:"]').length > 0 || /href\s*=\s*["']tel:/i.test(rawHtml)
|
|
50032
|
+
);
|
|
50033
|
+
if (!hasPhone) {
|
|
50034
|
+
findings.push({
|
|
50035
|
+
id: `impressum-missing-phone-${indexRef.value++}`,
|
|
50036
|
+
rule: "impressum",
|
|
50037
|
+
severity: "moderate",
|
|
50038
|
+
impact: "Eine Telefonnummer im Impressum wird seit 2024 empfohlen, um eine weitere Kontaktm\xF6glichkeit bereitzustellen.",
|
|
50039
|
+
description: "Keine Telefonnummer im Impressum erkennbar",
|
|
50040
|
+
explanation: "Seit der Novellierung des DDG (2024) wird eine Telefonnummer im Impressum als zus\xE4tzliche Kontaktm\xF6glichkeit empfohlen.",
|
|
50041
|
+
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50042
|
+
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50043
|
+
fix: "Erg\xE4nzen Sie das Impressum um eine Telefonnummer.",
|
|
50044
|
+
codeExample: "<p>Telefon: +49 (0) 30 1234567</p>",
|
|
50045
|
+
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50046
|
+
wcagLevel: "AA",
|
|
50047
|
+
wcagName: "Impressum Pflichtangaben \u2013 Telefon",
|
|
50048
|
+
legalBasis: "DSGVO",
|
|
50049
|
+
pageUrl: url
|
|
50050
|
+
});
|
|
50051
|
+
}
|
|
50052
|
+
const hasRegister = /handelsregister|amtsgericht|hrb\s*\d|hra\s*\d/i.test(normalizedText) || /ust\.?\s*-?\s*id\.?\s*-?\s*nr/i.test(normalizedText) || /umsatzsteuer/i.test(normalizedText) || /steuernummer|steuer-?nr/i.test(normalizedText) || /vat\s*-?\s*id/i.test(normalizedText) || /DE\s?\d{9}/.test(normalizedText) || // German VAT ID format
|
|
50053
|
+
/ATU\d{8}/.test(normalizedText) || // Austrian VAT ID
|
|
50054
|
+
/CHE[\-.]?\d{3}[\-.]?\d{3}[\-.]?\d{3}/i.test(normalizedText) || // Swiss UID
|
|
50055
|
+
/registernummer|register-?nr/i.test(normalizedText) || /eingetragen\s*(im|beim)/i.test(normalizedText);
|
|
50056
|
+
if (!hasRegister) {
|
|
50057
|
+
findings.push({
|
|
50058
|
+
id: `impressum-missing-register-${indexRef.value++}`,
|
|
50059
|
+
rule: "impressum",
|
|
50060
|
+
severity: "moderate",
|
|
50061
|
+
impact: "Fehlende Handelsregister- oder USt-IdNr.-Angaben k\xF6nnen einen Versto\xDF gegen TMG \xA75 darstellen.",
|
|
50062
|
+
description: "Keine Handelsregister- oder USt-IdNr.-Angaben im Impressum erkennbar",
|
|
50063
|
+
explanation: "Sofern eine Handelsregistereintragung oder USt-IdNr. vorliegt, sind diese im Impressum anzugeben (TMG \xA75 Abs. 1 Nr. 4 und 6). Hinweis: Nicht alle Anbieter sind registerpflichtig.",
|
|
50064
|
+
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50065
|
+
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50066
|
+
fix: "Erg\xE4nzen Sie das Impressum um die Handelsregisternummer und/oder USt-IdNr., sofern vorhanden.",
|
|
50067
|
+
codeExample: "<p>Registergericht: Amtsgericht M\xFCnchen, HRB 123456</p>\n<p>USt-IdNr.: DE123456789</p>",
|
|
50068
|
+
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50069
|
+
wcagLevel: "AA",
|
|
50070
|
+
wcagName: "Impressum Pflichtangaben \u2013 Register/USt-IdNr.",
|
|
50071
|
+
legalBasis: "DSGVO",
|
|
50072
|
+
pageUrl: url
|
|
50073
|
+
});
|
|
50074
|
+
}
|
|
50075
|
+
}
|
|
49958
50076
|
var impressumRule;
|
|
49959
50077
|
var init_impressum = __esm({
|
|
49960
50078
|
"../../src/lib/scanner/rules/impressum.ts"() {
|
|
49961
50079
|
"use strict";
|
|
50080
|
+
init_esm11();
|
|
49962
50081
|
impressumRule = {
|
|
49963
50082
|
id: "impressum",
|
|
49964
50083
|
name: "Impressum & Datenschutzlink",
|
|
49965
|
-
run: (html3, url, $2) => {
|
|
50084
|
+
run: async (html3, url, $2) => {
|
|
49966
50085
|
const findings = [];
|
|
49967
|
-
|
|
50086
|
+
const indexRef = { value: 0 };
|
|
49968
50087
|
const allLinks = $2("a");
|
|
49969
50088
|
let impressumLink = null;
|
|
49970
50089
|
let impressumHref = "";
|
|
@@ -50049,33 +50168,41 @@ var init_impressum = __esm({
|
|
|
50049
50168
|
}
|
|
50050
50169
|
}
|
|
50051
50170
|
if (!impressumLink && !impressumSectionFound) {
|
|
50052
|
-
$2('dialog, [role="dialog"], .modal, .accordion, details, [data-modal], [data-dialog]').each(
|
|
50053
|
-
|
|
50054
|
-
|
|
50055
|
-
|
|
50056
|
-
|
|
50057
|
-
|
|
50171
|
+
$2('dialog, [role="dialog"], .modal, .accordion, details, [data-modal], [data-dialog]').each(
|
|
50172
|
+
(_, el) => {
|
|
50173
|
+
if (impressumSectionFound) return;
|
|
50174
|
+
const text3 = $2(el).text().toLowerCase();
|
|
50175
|
+
if (impressumTextPatterns.test(text3)) {
|
|
50176
|
+
impressumSectionFound = true;
|
|
50177
|
+
impressumLink = el;
|
|
50178
|
+
}
|
|
50058
50179
|
}
|
|
50059
|
-
|
|
50180
|
+
);
|
|
50060
50181
|
}
|
|
50061
50182
|
if (!impressumLink && !impressumSectionFound) {
|
|
50062
|
-
$2(
|
|
50183
|
+
$2(
|
|
50184
|
+
'[data-target*="impressum"], [data-section*="impressum"], [aria-controls*="impressum"], [data-target*="imprint"], [data-section*="imprint"], [aria-controls*="imprint"]'
|
|
50185
|
+
).each((_, el) => {
|
|
50063
50186
|
if (impressumSectionFound) return;
|
|
50064
50187
|
impressumSectionFound = true;
|
|
50065
50188
|
impressumLink = el;
|
|
50066
50189
|
});
|
|
50067
50190
|
}
|
|
50068
50191
|
if (!impressumLink && !impressumSectionFound) {
|
|
50069
|
-
$2('[href*="impressum"], [href*="imprint"], [to*="impressum"], [to*="imprint"]').each(
|
|
50070
|
-
|
|
50071
|
-
|
|
50072
|
-
|
|
50073
|
-
|
|
50192
|
+
$2('[href*="impressum"], [href*="imprint"], [to*="impressum"], [to*="imprint"]').each(
|
|
50193
|
+
(_, el) => {
|
|
50194
|
+
if (impressumLink) return;
|
|
50195
|
+
impressumLink = el;
|
|
50196
|
+
impressumHref = $2(el).attr("href") || $2(el).attr("to") || "";
|
|
50197
|
+
}
|
|
50198
|
+
);
|
|
50074
50199
|
}
|
|
50075
|
-
console.log(
|
|
50200
|
+
console.log(
|
|
50201
|
+
`[Impressum] Detection for ${url}: link=${!!impressumLink}, section=${impressumSectionFound}, href="${impressumHref}"`
|
|
50202
|
+
);
|
|
50076
50203
|
if (!impressumLink && !impressumSectionFound) {
|
|
50077
50204
|
findings.push({
|
|
50078
|
-
id: `impressum-missing-link-${
|
|
50205
|
+
id: `impressum-missing-link-${indexRef.value++}`,
|
|
50079
50206
|
rule: "impressum",
|
|
50080
50207
|
severity: "critical",
|
|
50081
50208
|
impact: "Ohne Impressum-Link verst\xF6\xDFt die Website gegen die gesetzliche Anbieterkennzeichnungspflicht nach TMG \xA75 / DDG \xA75.",
|
|
@@ -50155,14 +50282,16 @@ var init_impressum = __esm({
|
|
|
50155
50282
|
});
|
|
50156
50283
|
}
|
|
50157
50284
|
if (!datenschutzLinkFound) {
|
|
50158
|
-
$2('[href*="datenschutz"], [href*="privacy"], [to*="datenschutz"], [to*="privacy"]').each(
|
|
50159
|
-
|
|
50160
|
-
|
|
50161
|
-
|
|
50285
|
+
$2('[href*="datenschutz"], [href*="privacy"], [to*="datenschutz"], [to*="privacy"]').each(
|
|
50286
|
+
(_, el) => {
|
|
50287
|
+
if (datenschutzLinkFound) return;
|
|
50288
|
+
datenschutzLinkFound = true;
|
|
50289
|
+
}
|
|
50290
|
+
);
|
|
50162
50291
|
}
|
|
50163
50292
|
if (!datenschutzLinkFound) {
|
|
50164
50293
|
findings.push({
|
|
50165
|
-
id: `impressum-missing-datenschutz-${
|
|
50294
|
+
id: `impressum-missing-datenschutz-${indexRef.value++}`,
|
|
50166
50295
|
rule: "impressum",
|
|
50167
50296
|
severity: "serious",
|
|
50168
50297
|
impact: "Ohne Datenschutzhinweise kann die Website gegen die DSGVO Art. 13/14 Informationspflichten versto\xDFen.",
|
|
@@ -50179,98 +50308,31 @@ var init_impressum = __esm({
|
|
|
50179
50308
|
pageUrl: url
|
|
50180
50309
|
});
|
|
50181
50310
|
}
|
|
50182
|
-
|
|
50183
|
-
|
|
50184
|
-
const
|
|
50185
|
-
|
|
50186
|
-
|
|
50187
|
-
|
|
50188
|
-
|
|
50189
|
-
if (
|
|
50190
|
-
|
|
50191
|
-
|
|
50192
|
-
|
|
50193
|
-
|
|
50194
|
-
|
|
50195
|
-
|
|
50196
|
-
|
|
50197
|
-
|
|
50198
|
-
|
|
50199
|
-
|
|
50200
|
-
|
|
50201
|
-
|
|
50202
|
-
|
|
50203
|
-
|
|
50204
|
-
|
|
50205
|
-
|
|
50206
|
-
|
|
50207
|
-
codeExample: "<p>Musterstra\xDFe 1<br>12345 Musterstadt</p>",
|
|
50208
|
-
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50209
|
-
wcagLevel: "AA",
|
|
50210
|
-
wcagName: "Impressum Pflichtangaben \u2013 Anschrift",
|
|
50211
|
-
legalBasis: "DSGVO",
|
|
50212
|
-
pageUrl: url
|
|
50213
|
-
});
|
|
50214
|
-
}
|
|
50215
|
-
const hasEmail = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i.test(normalizedText) || /kontaktformular|contact\s*form/i.test(normalizedText) || /e-?mail\s*:/i.test(normalizedText);
|
|
50216
|
-
if (!hasEmail) {
|
|
50217
|
-
findings.push({
|
|
50218
|
-
id: `impressum-missing-email-${index2++}`,
|
|
50219
|
-
rule: "impressum",
|
|
50220
|
-
severity: "moderate",
|
|
50221
|
-
impact: "Ohne E-Mail-Adresse oder Kontaktm\xF6glichkeit fehlt eine Pflichtangabe im Impressum.",
|
|
50222
|
-
description: "Keine E-Mail-Adresse oder Kontaktm\xF6glichkeit im Impressum erkennbar",
|
|
50223
|
-
explanation: "Das Impressum muss eine M\xF6glichkeit zur schnellen elektronischen Kontaktaufnahme enthalten (in der Regel eine E-Mail-Adresse). Diese konnte auf der aktuellen Seite nicht identifiziert werden.",
|
|
50224
|
-
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50225
|
-
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50226
|
-
fix: "Geben Sie im Impressum eine E-Mail-Adresse an oder verweisen Sie auf ein Kontaktformular.",
|
|
50227
|
-
codeExample: "<p>E-Mail: info@example.de</p>",
|
|
50228
|
-
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50229
|
-
wcagLevel: "AA",
|
|
50230
|
-
wcagName: "Impressum Pflichtangaben \u2013 Kontakt",
|
|
50231
|
-
legalBasis: "DSGVO",
|
|
50232
|
-
pageUrl: url
|
|
50233
|
-
});
|
|
50234
|
-
}
|
|
50235
|
-
const hasPhone = /tel\.?\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /telefon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /phone\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText);
|
|
50236
|
-
if (!hasPhone) {
|
|
50237
|
-
findings.push({
|
|
50238
|
-
id: `impressum-missing-phone-${index2++}`,
|
|
50239
|
-
rule: "impressum",
|
|
50240
|
-
severity: "moderate",
|
|
50241
|
-
impact: "Eine Telefonnummer im Impressum wird seit 2024 empfohlen, um eine weitere Kontaktm\xF6glichkeit bereitzustellen.",
|
|
50242
|
-
description: "Keine Telefonnummer im Impressum erkennbar",
|
|
50243
|
-
explanation: "Seit der Novellierung des DDG (2024) wird eine Telefonnummer im Impressum als zus\xE4tzliche Kontaktm\xF6glichkeit empfohlen. Diese konnte auf der aktuellen Seite nicht identifiziert werden.",
|
|
50244
|
-
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50245
|
-
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50246
|
-
fix: "Erg\xE4nzen Sie das Impressum um eine Telefonnummer.",
|
|
50247
|
-
codeExample: "<p>Telefon: +49 (0) 30 1234567</p>",
|
|
50248
|
-
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50249
|
-
wcagLevel: "AA",
|
|
50250
|
-
wcagName: "Impressum Pflichtangaben \u2013 Telefon",
|
|
50251
|
-
legalBasis: "DSGVO",
|
|
50252
|
-
pageUrl: url
|
|
50253
|
-
});
|
|
50254
|
-
}
|
|
50255
|
-
const hasRegister = /handelsregister|amtsgericht|hrb\s*\d|hra\s*\d/i.test(normalizedText) || /ust\.?\s*-?\s*id\.?\s*-?\s*nr/i.test(normalizedText) || /umsatzsteuer/i.test(normalizedText) || /steuernummer|steuer-?nr/i.test(normalizedText) || /vat\s*-?\s*id/i.test(normalizedText) || /DE\s?\d{9}/.test(normalizedText);
|
|
50256
|
-
if (!hasRegister) {
|
|
50257
|
-
findings.push({
|
|
50258
|
-
id: `impressum-missing-register-${index2++}`,
|
|
50259
|
-
rule: "impressum",
|
|
50260
|
-
severity: "moderate",
|
|
50261
|
-
impact: "Fehlende Handelsregister- oder USt-IdNr.-Angaben k\xF6nnen einen Versto\xDF gegen TMG \xA75 darstellen.",
|
|
50262
|
-
description: "Keine Handelsregister- oder USt-IdNr.-Angaben im Impressum erkennbar",
|
|
50263
|
-
explanation: "Sofern eine Handelsregistereintragung oder USt-IdNr. vorliegt, sind diese im Impressum anzugeben (TMG \xA75 Abs. 1 Nr. 4 und 6). Diese konnten auf der aktuellen Seite nicht identifiziert werden. Hinweis: Nicht alle Anbieter sind registerpflichtig.",
|
|
50264
|
-
element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
|
|
50265
|
-
selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
|
|
50266
|
-
fix: "Erg\xE4nzen Sie das Impressum um die Handelsregisternummer und/oder USt-IdNr., sofern vorhanden.",
|
|
50267
|
-
codeExample: "<p>Registergericht: Amtsgericht M\xFCnchen, HRB 123456</p>\n<p>USt-IdNr.: DE123456789</p>",
|
|
50268
|
-
wcagReference: "TMG \xA75 / DDG \xA75",
|
|
50269
|
-
wcagLevel: "AA",
|
|
50270
|
-
wcagName: "Impressum Pflichtangaben \u2013 Register/USt-IdNr.",
|
|
50271
|
-
legalBasis: "DSGVO",
|
|
50272
|
-
pageUrl: url
|
|
50273
|
-
});
|
|
50311
|
+
if (impressumLink || impressumSectionFound) {
|
|
50312
|
+
const isOnPage = impressumSectionFound || impressumLink && (impressumHref.startsWith("#") || impressumHref === "");
|
|
50313
|
+
const isSeparatePage = impressumLink && impressumHref && !impressumHref.startsWith("#") && impressumHref !== "";
|
|
50314
|
+
if (isOnPage) {
|
|
50315
|
+
let sectionText = $2("body").text();
|
|
50316
|
+
let sectionHtml = html3;
|
|
50317
|
+
let $section = $2;
|
|
50318
|
+
if (impressumHref.startsWith("#") && impressumHref.length > 1) {
|
|
50319
|
+
const anchorId = impressumHref.slice(1);
|
|
50320
|
+
const section = $2(`#${anchorId}`);
|
|
50321
|
+
if (section.length > 0) {
|
|
50322
|
+
sectionText = section.text();
|
|
50323
|
+
sectionHtml = section.html() || html3;
|
|
50324
|
+
$section = load(section.html() || "");
|
|
50325
|
+
}
|
|
50326
|
+
}
|
|
50327
|
+
const normalizedText = sectionText.replace(/\s+/g, " ");
|
|
50328
|
+
checkImpressumContent(normalizedText, sectionHtml, $section, impressumHref, url, findings, indexRef);
|
|
50329
|
+
} else if (isSeparatePage) {
|
|
50330
|
+
const fetched = await fetchImpressumPage(impressumHref, url);
|
|
50331
|
+
if (fetched) {
|
|
50332
|
+
checkImpressumContent(fetched.text, fetched.html, fetched.$, impressumHref, url, findings, indexRef);
|
|
50333
|
+
} else {
|
|
50334
|
+
console.log(`[Impressum] Could not fetch separate impressum page at "${impressumHref}" \u2014 skipping content checks`);
|
|
50335
|
+
}
|
|
50274
50336
|
}
|
|
50275
50337
|
}
|
|
50276
50338
|
return findings;
|
|
@@ -59579,7 +59641,8 @@ async function scanUrl(url) {
|
|
|
59579
59641
|
break;
|
|
59580
59642
|
}
|
|
59581
59643
|
try {
|
|
59582
|
-
|
|
59644
|
+
const result = rule.run(html3, url, $2);
|
|
59645
|
+
rawFindings.push(...result instanceof Promise ? await result : result);
|
|
59583
59646
|
} catch (ruleErr) {
|
|
59584
59647
|
console.error(`[Scanner] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
|
|
59585
59648
|
}
|
|
@@ -60240,7 +60303,8 @@ async function scanUrlFast(url, options) {
|
|
|
60240
60303
|
const rawFindings = [];
|
|
60241
60304
|
for (const rule of allRules) {
|
|
60242
60305
|
try {
|
|
60243
|
-
|
|
60306
|
+
const result = rule.run(html3, url, $final);
|
|
60307
|
+
rawFindings.push(...result instanceof Promise ? await result : result);
|
|
60244
60308
|
} catch (ruleErr) {
|
|
60245
60309
|
console.error(`[ScanFast] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
|
|
60246
60310
|
}
|
|
@@ -60373,7 +60437,8 @@ async function scanHtml(html3, url = "local") {
|
|
|
60373
60437
|
const rawFindings = [];
|
|
60374
60438
|
for (const rule of allRules) {
|
|
60375
60439
|
try {
|
|
60376
|
-
|
|
60440
|
+
const result = rule.run(html3, url, $2);
|
|
60441
|
+
rawFindings.push(...result instanceof Promise ? await result : result);
|
|
60377
60442
|
} catch (ruleErr) {
|
|
60378
60443
|
console.error(`[ScanHtml] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
|
|
60379
60444
|
}
|
|
@@ -60576,7 +60641,7 @@ var init_scanner = __esm({
|
|
|
60576
60641
|
var readline = __toESM(require("readline"));
|
|
60577
60642
|
|
|
60578
60643
|
// package.json
|
|
60579
|
-
var version = "2.
|
|
60644
|
+
var version = "2.3.0";
|
|
60580
60645
|
|
|
60581
60646
|
// src/cli.ts
|
|
60582
60647
|
var PKG_VERSION = version;
|
|
@@ -60711,11 +60776,12 @@ function parseArgs() {
|
|
|
60711
60776
|
function printHelp() {
|
|
60712
60777
|
console.log(` ${BOLD}Verwendung:${RESET}`);
|
|
60713
60778
|
console.log(` ${GREEN}npx acpilot${RESET} ${DIM}Interaktiver Modus${RESET}`);
|
|
60714
|
-
console.log(` ${GREEN}npx acpilot${RESET} --url http://localhost:3000 ${DIM}
|
|
60779
|
+
console.log(` ${GREEN}npx acpilot${RESET} --url http://localhost:3000 ${DIM}Lokaler Dev-Server${RESET}`);
|
|
60780
|
+
console.log(` ${GREEN}npx acpilot${RESET} --url https://example.com ${DIM}Live-Website${RESET}`);
|
|
60715
60781
|
console.log(` --api-key AP_xxx`);
|
|
60716
60782
|
console.log("");
|
|
60717
60783
|
console.log(` ${BOLD}Optionen:${RESET}`);
|
|
60718
|
-
console.log(` ${CYAN}--url${RESET} <url> URL
|
|
60784
|
+
console.log(` ${CYAN}--url${RESET} <url> URL (localhost oder live, z.B. https://example.com)`);
|
|
60719
60785
|
console.log(` ${CYAN}--api-key${RESET} <key> API-Key ${DIM}(oder ACCESSPILOT_API_KEY env)${RESET}`);
|
|
60720
60786
|
console.log(` ${CYAN}--max-pages${RESET} <n> Max. Seiten ${DIM}(Standard: 50)${RESET}`);
|
|
60721
60787
|
console.log(` ${CYAN}--max-depth${RESET} <n> Crawl-Tiefe ${DIM}(Standard: 3)${RESET}`);
|
|
@@ -60789,9 +60855,9 @@ async function main() {
|
|
|
60789
60855
|
if (args.interactive || !url) {
|
|
60790
60856
|
const rl = createRL();
|
|
60791
60857
|
console.log("");
|
|
60792
|
-
console.log(` ${DIM}Scannt deine
|
|
60858
|
+
console.log(` ${DIM}Scannt deine Website mit der vollen ACPilot Engine (lokal oder live).${RESET}`);
|
|
60793
60859
|
printDivider("Konfiguration");
|
|
60794
|
-
url = await ask(rl, `${BOLD}URL
|
|
60860
|
+
url = await ask(rl, `${BOLD}URL (localhost oder live)${RESET}`, "http://localhost:3000");
|
|
60795
60861
|
try {
|
|
60796
60862
|
new URL(url);
|
|
60797
60863
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acpilot",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Full ACPilot accessibility scanner for local dev servers",
|
|
5
5
|
"bin": {
|
|
6
6
|
"acpilot": "./dist/cli.js"
|
|
@@ -27,6 +27,15 @@
|
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=18.0.0"
|
|
29
29
|
},
|
|
30
|
-
"keywords": [
|
|
30
|
+
"keywords": [
|
|
31
|
+
"accessibility",
|
|
32
|
+
"a11y",
|
|
33
|
+
"wcag",
|
|
34
|
+
"scan",
|
|
35
|
+
"localhost",
|
|
36
|
+
"dev",
|
|
37
|
+
"acpilot",
|
|
38
|
+
"puppeteer"
|
|
39
|
+
],
|
|
31
40
|
"license": "UNLICENSED"
|
|
32
41
|
}
|