diff --git a/tests/test_site_privacy.py b/tests/test_site_privacy.py index a478c836b..906d39ba7 100644 --- a/tests/test_site_privacy.py +++ b/tests/test_site_privacy.py @@ -44,9 +44,9 @@ class TheCounterAsksNothingOfTheDevice(unittest.TestCase): self.assertIn("from.origin", self.code) self.assertNotIn("from.pathname", self.code) - def test_a_search_with_personal_details_is_not_sent(self) -> None: - self.assertIn("!query.includes('@')", self.code) - self.assertIn("\\d{7,}", self.code) + def test_a_search_is_sent_only_as_a_file_name_or_a_hash(self) -> None: + self.assertIn("isHash(query) || isFileName(query) || isFileStem(query) ? query : 'other'", self.code) + self.assertIn("countEvent(`search-none/${searchWords(query)}`)", self.code) self.assertIn("message.startsWith('No results for')", self.code) def test_one_endpoint(self) -> None: diff --git a/website/src/scripts/count.ts b/website/src/scripts/count.ts index a401fc8d6..31c5473e7 100644 --- a/website/src/scripts/count.ts +++ b/website/src/scripts/count.ts @@ -7,8 +7,8 @@ // Sent: the page path (never its query or #anchor), the domain of the site // the reader came from, and the name of an action. An action is a click on // a copy button, a pack or file download, a link that leaves the site, a -// site search that found nothing (the words, when they cannot hold a -// person's details), and the answer to "Did this page help?". +// site search that found nothing (its words only when they are a file +// name or a hash), and the answer to "Did this page help?". const ENDPOINT = 'https://retrobios.goatcounter.com/count'; const HOST = 'abdess.github.io'; @@ -104,11 +104,18 @@ const onClick = (event: MouseEvent): void => { // Search // A search that found nothing names a missing page. Its words are sent -// only when they cannot hold a person's details: short, no "@", no long -// run of digits. A hash is a file asked for, and is kept. -const isHash = (query: string): boolean => /^[0-9a-f]{8}$|^[0-9a-f]{32}$|^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(query); -const sendable = (query: string): boolean => - isHash(query) || (query.length >= 2 && query.length <= 40 && !query.includes('@') && !/\d{7,}/.test(query)); +// only in a shape no person's details take: a hash, a file name +// ("scph5501.bin"), or one word of letters and at most four digits +// ("scph5501", "ps3updat"). Any other search is counted with no words, +// as "other": a name, a phone number or an address is never sent. +const isHash = (query: string): boolean => /^(?:[0-9a-f]{8}|[0-9a-f]{32}|[0-9a-f]{40}|[0-9a-f]{64})$/.test(query); +// The extensions of the files the collection holds: "john.smith" is no file. +const isFileName = (query: string): boolean => + /^[\w.+-]{1,60}\.(?:bin|rom|zip|7z|pup|keys|img|dat|bios|ic\d+|u\d+|sys|nvm|mec|rom1|j64|pce|col|sms|gb|gbc|gba|nds|cfg|chd|iso|txt|fw|xml|yaml|pak|pk3|grp)$/.test(query); +const isFileStem = (query: string): boolean => + /^[a-z][a-z0-9_-]{1,30}$/.test(query) && /\d/.test(query) && (query.match(/\d/g) ?? []).length <= 4; +const searchWords = (query: string): string => + isHash(query) || isFileName(query) || isFileStem(query) ? query : 'other'; const watchSearch = (): void => { const root = document.getElementById('starlight__search'); @@ -121,9 +128,9 @@ const watchSearch = (): void => { // Pagefind's English message once a search has ended with nothing; // while it runs the message reads "Searching for ...". const message = root.querySelector('.pagefind-ui__message')?.textContent ?? ''; - if (!query || !message.startsWith('No results for') || sent.has(query) || !sendable(query)) return; + if (!query || !message.startsWith('No results for') || sent.has(query)) return; sent.add(query); - countEvent(`search-none/${query}`); + countEvent(`search-none/${searchWords(query)}`); }; new MutationObserver(() => { window.clearTimeout(timer); diff --git a/wiki/privacy.md b/wiki/privacy.md index 6f0854844..16ccd5c07 100644 --- a/wiki/privacy.md +++ b/wiki/privacy.md @@ -25,8 +25,9 @@ code made from the request for 8 hours, then drops it. These actions are counted the same way, with the page they happened on: - a click on a copy button, a pack download or a file link; -- a site search that found nothing, with its words, so a missing page shows up. - Words with an `@` or a long number are never sent; +- a site search that found nothing, so a missing page shows up. Its words are + sent only when they read as a file name (`scph5501.bin`, `scph5501`) or a + hash. Any other search is counted as "other", without its words; - the answer to "Did this page help?". Not sent: your screen size, your language, a search that found results.