diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 2f573db3..9cb0182e 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -21,11 +21,13 @@ bdba berr bingbot bitcoin +bitrate blogging Bluesky blueskybot boi botnet +botstopper BPort Brightbot broked @@ -55,6 +57,7 @@ ckies cloudflare Codespaces confd +connnection containerbuild coreutils Cotoyogi @@ -78,6 +81,7 @@ domainhere dracula dronebl droneblresponse +dropin duckduckbot eerror ellenjoe @@ -95,6 +99,7 @@ facebookgo Factset fastcgi fediverse +ffprobe finfos Firecrawl flagenv @@ -201,6 +206,7 @@ omgilibot openai opengraph openrc +oswald pag palemoon Pangu @@ -241,6 +247,7 @@ risc ruleset runlevels RUnlock +runtimedir sas sasl Scumm @@ -275,6 +282,8 @@ SVCNAME tagline tarballs tarrif +tbn +tbr techaro techarohq templ diff --git a/anubis.go b/anubis.go index 6b219cad..ae674b4f 100644 --- a/anubis.go +++ b/anubis.go @@ -32,3 +32,7 @@ const APIPrefix = "/.within.website/x/cmd/anubis/api/" // DefaultDifficulty is the default "difficulty" (number of leading zeroes) // that must be met by the client in order to pass the challenge. const DefaultDifficulty = 4 + +// ForcedLanguage is the language being used instead of the one of the request's Accept-Language header +// if being set. +var ForcedLanguage = "" diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index beae5347..57ab4a1c 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -50,6 +50,7 @@ var ( cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for") cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis") cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support") + forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header") hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set") cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies") ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned") @@ -378,6 +379,7 @@ func main() { anubis.CookieName = *cookiePrefix + "-auth" anubis.TestCookieName = *cookiePrefix + "-cookie-verification" + anubis.ForcedLanguage = *forcedLanguage // If OpenGraph configuration values are not set in the config file, use the // values from flags / envvars. diff --git a/data/bots/ai-catchall.yaml b/data/bots/ai-catchall.yaml index 05dc6e19..40dc742f 100644 --- a/data/bots/ai-catchall.yaml +++ b/data/bots/ai-catchall.yaml @@ -7,5 +7,5 @@ # Warning: May contain user agents that _must_ be blocked in robots.txt, or the opt-out will have no effect. - name: "ai-catchall" user_agent_regex: >- - AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|anthropic-ai|Brightbot 1.0|Bytespider|CCBot|Claude-Web|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|GoogleOther|GoogleOther-Image|GoogleOther-Video|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|imgproxy|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|NovaAct|omgili|omgilibot|Operator|PanguBot|Perplexity-User|PerplexityBot|PetalBot|QualifiedBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YouBot + AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|anthropic-ai|Brightbot 1.0|Bytespider|Claude-Web|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|GoogleOther|GoogleOther-Image|GoogleOther-Video|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|imgproxy|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|NovaAct|omgili|omgilibot|Operator|PanguBot|Perplexity-User|PerplexityBot|PetalBot|QualifiedBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YouBot action: DENY diff --git a/data/bots/ai-robots-txt.yaml b/data/bots/ai-robots-txt.yaml index 3920cf99..c330eb7d 100644 --- a/data/bots/ai-robots-txt.yaml +++ b/data/bots/ai-robots-txt.yaml @@ -1,6 +1,8 @@ # Warning: Contains user agents that _must_ be blocked in robots.txt, or the opt-out will have no effect. # Note: Blocks human-directed/non-training user agents +# +# CCBot is allowed because if Common Crawl is allowed, then scrapers don't need to scrape to get the data. - name: "ai-robots-txt" user_agent_regex: >- - AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|bedrockbot|Brightbot 1.0|Bytespider|CCBot|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|MyCentralAIScraperBot|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot|SemrushBot-BA|SemrushBot-CT|SemrushBot-OCOB|SemrushBot-SI|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot + AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|bedrockbot|Brightbot 1.0|Bytespider|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|MyCentralAIScraperBot|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot|SemrushBot-BA|SemrushBot-CT|SemrushBot-OCOB|SemrushBot-SI|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot action: DENY diff --git a/data/crawlers/_allow-good.yaml b/data/crawlers/_allow-good.yaml index f3784cba..6ccf0ce2 100644 --- a/data/crawlers/_allow-good.yaml +++ b/data/crawlers/_allow-good.yaml @@ -6,4 +6,5 @@ - import: (data)/crawlers/internet-archive.yaml - import: (data)/crawlers/kagibot.yaml - import: (data)/crawlers/marginalia.yaml -- import: (data)/crawlers/mojeekbot.yaml \ No newline at end of file +- import: (data)/crawlers/mojeekbot.yaml +- import: (data)/crawlers/commoncrawl.yaml diff --git a/data/crawlers/commoncrawl.yaml b/data/crawlers/commoncrawl.yaml new file mode 100644 index 00000000..457a5aff --- /dev/null +++ b/data/crawlers/commoncrawl.yaml @@ -0,0 +1,12 @@ +- name: common-crawl + user_agent_regex: CCBot + action: ALLOW + # https://index.commoncrawl.org/ccbot.json + remote_addresses: + [ + "2600:1f28:365:80b0::/60", + "18.97.9.168/29", + "18.97.14.80/29", + "18.97.14.88/30", + "98.85.178.216/32", + ] diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index f96864e8..7138b80b 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -18,11 +18,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)). - Implement localization system. Find locale files in lib/localization/locales/. - Implement a [development container](https://containers.dev/) manifest to make contributions easier. -- Fix dynamic cookie domains functionality ([#731](https://github.com/TecharoHQ/anubis/pull/731)). -- Add option for custom cookie prefix ([#732](https://github.com/TecharoHQ/anubis/pull/732)). +- Fix dynamic cookie domains functionality ([#731](https://github.com/TecharoHQ/anubis/pull/731)) +- Add option for custom cookie prefix ([#732](https://github.com/TecharoHQ/anubis/pull/732)) +- Add translation for German language ([#741](https://github.com/TecharoHQ/anubis/pull/741)) - Remove the "Success" interstitial after a proof of work challenge is concluded. - Anubis now has the concept of [storage backends](./admin/policies.mdx#storage-backends). These allow you to change how Anubis stores temporary data (in memory, on the disk, or in Valkey). If you run Anubis in an environment where you have a low amount of memory available for Anubis (eg: less than 64 megabytes), be sure to configure the [`bbolt`](./admin/policies.mdx#bbolt) storage backend. - The challenge issuance and validation process has been rewritten from scratch. Instead of generating challenge strings from request metadata (under the assumption that the values being compared against are stable), Anubis now generates random data for each challenge. This data is stored in the active [storage backend](./admin/policies.mdx#storage-backends) for up to 30 minutes. Fixes [#564](https://github.com/TecharoHQ/anubis/issues/564), [#746](https://github.com/TecharoHQ/anubis/issues/746), and other similar instances of this issue. +- Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742)) +- Add translation for Turkish language ([#751](https://github.com/TecharoHQ/anubis/pull/751)) +- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape + +### Potentially breaking changes + +The following potentially breaking change applies to native installs with systemd only: + +Each instance of systemd service template now has a unique `RuntimeDirectory`, as opposed to each instance of the service sharing a `RuntimeDirectory`. This change was made to avoid [the `RuntimeDirectory` getting nuked any time one of the Anubis instances restarts](https://github.com/TecharoHQ/anubis/issues/748). + +If you configured Anubis' unix sockets to listen on `/run/anubis/foo.sock` for instance `anubis@foo`, you will need to configure Anubis to listen on `/run/anubis/foo/sock` and additionally configure your HTTP load balancer as appropriate. + +If you need the legacy behaviour, install this [systemd unit dropin](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/): + +```systemd +# /etc/systemd/system/anubis@.service.d/50-runtimedir.conf +[Service] +RuntimeDirectory=anubis +``` ## v1.20.0: Thancred Waters diff --git a/docs/docs/admin/botstopper.mdx b/docs/docs/admin/botstopper.mdx new file mode 100644 index 00000000..56ad14cb --- /dev/null +++ b/docs/docs/admin/botstopper.mdx @@ -0,0 +1,215 @@ +--- +title: "Commercial support and an unbranded version" +--- + +If you want to use Anubis but organizational policies prevent you from using the branding that the open source project ships, we offer a commercial version of Anubis named BotStopper. BotStopper builds off of the open source core of Anubis and offers organizations more control over the branding, including but not limited to: + +- Custom images for different states of the challenge process (in process, success, failure) +- Custom CSS and fonts +- Custom titles for the challenge and error pages +- "Anubis" replaced with "BotStopper" across the UI +- A private bug tracker for issues + +In the near future this will expand to: + +- A private challenge implementation that does advanced fingerprinting to check if the client is a genuine browser or not +- Advanced fingerprinting via [Thoth-based advanced checks](./thoth.mdx) + +In order to sign up for BotStopper, please do one of the following: + +- Sign up [on GitHub Sponsors](https://github.com/sponsors/Xe) at the $50 per month tier or higher +- Email [sales@techaro.lol](mailto:sales@techaro.lol) with your requirements for invoicing, please note that custom invoicing will cost more than using GitHub Sponsors for understandable overhead reasons + +## Installation + +Install BotStopper like you would Anubis, but replace the image reference. EG: + +```diff +-ghcr.io/techarohq/anubis:latest ++ghcr.io/techarohq/botstopper/anubis:latest +``` + +### Binary packages + +Binary packages are available [in the GitHub Releases page](https://github.com/TecharoHQ/botstopper/releases), the main difference is that the package name is `techaro-botstopper`, the systemd service is `techaro-botstopper@your-instance.service`, the binary is `/usr/bin/botstopper`, and the configuration is in `/etc/techaro-botstopper`. All other instructions in the [native package install guide](./native-install.mdx) apply. + +### Docker / Podman + +In order to pull the BotStopper image, you need to [authenticate with GitHub's Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry). + +```text +docker login ghcr.io -u your-username --password-stdin +``` + +Then you can use the image as normal. + +### Kubernetes + +If you are using Kubernetes, you will need to create an image pull secret: + +```text +kubectl create secret docker-registry \ + techarohq-botstopper \ + --docker-server ghcr.io \ + --docker-username your-username \ + --docker-password your-access-token \ + --docker-email your@email.address +``` + +Then attach it to your Deployment: + +```diff + spec: + securityContext: + fsGroup: 1000 ++ imagePullSecrets: ++ - name: techarohq-botstopper +``` + +## Configuration + +### Docker compose + +Follow [the upstream Docker compose directions](https://anubis.techaro.lol/docs/admin/environments/docker-compose) with the following additional options: + +```diff + anubis: + image: ghcr.io/techarohq/botstopper/anubis:latest + environment: + BIND: ":8080" + DIFFICULTY: "4" + METRICS_BIND: ":9090" + SERVE_ROBOTS_TXT: "true" + TARGET: "http://nginx" + OG_PASSTHROUGH: "true" + OG_EXPIRY_TIME: "24h" + ++ # botstopper config here ++ CHALLENGE_TITLE: "Doing math for your connnection!" ++ ERROR_TITLE: "Something went wrong!" ++ OVERLAY_FOLDER: /assets ++ volumes: ++ - "./your_folder:/assets" +``` + +#### Example + +There is an example in [docker-compose.yaml](https://github.com/TecharoHQ/botstopper/blob/main/docker-compose.yaml). Start the example with `docker compose up`: + +```text +docker compose up -d +``` + +And then open [https://botstopper.local.cetacean.club:8443](https://botstopper.local.cetacean.club:8443) in your browser. + +> [!NOTE] +> This uses locally signed sacrificial TLS certificates stored in `./demo/pki`. Your browser will rightly reject these. Here is what the example looks like: +> +> ![](/img/botstopper/example-screenshot.webp) + +## Custom images and CSS + +Anubis uses an internal filesystem that contains CSS, JavaScript, and images. The BotStopper variant of Anubis lets you specify an overlay folder with the environment variable `OVERLAY_FOLDER`. The contents of this folder will be overlaid on top of Anubis' internal filesystem, allowing you to easily customize the images and CSS. + +Your directory tree should look like this, assuming your data is in `./your_folder`: + +```text +./your_folder +└── static + ├── css + │ └── custom.css + └── img + ├── happy.webp + ├── pensive.webp + └── reject.webp +``` + +For an example directory tree using some off-the-shelf images the Tango icon set, see the [testdata](https://github.com/TecharoHQ/botstopper/tree/main/testdata/static/img) folder. + +### Custom CSS + +CSS customization is done mainly with CSS variables. View [the example custom CSS file](https://github.com/TecharoHQ/botstopper/blob/main/testdata/static/css/custom.css) for more information about what can be customized. + +### Custom fonts + +If you want to add custom fonts, copy the `woff2` files alongside your `custom.css` file and then include them with the [`@font-face` CSS at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face): + +```css +@font-face { + font-family: "Oswald"; + font-style: normal; + font-weight: 200 900; + font-display: swap; + src: url("./fonts/oswald.woff2") format("woff2"); +} +``` + +Then adjust your CSS variables accordingly: + +```css +:root { + --body-sans-font: Oswald, sans-serif; + --body-preformatted-font: monospace; + --body-title-font: serif; +} +``` + +To convert `.ttf` fonts to [Web-optimized woff2 fonts](https://www.w3.org/TR/WOFF2/), use the `woff2_compress` command from the `woff2` or `woff2-tools` package: + +```console +$ woff2_compress oswald.ttf +Processing oswald.ttf => oswald.woff2 +Compressed 159517 to 70469. +``` + +Then you can import and use it as normal. + +### Customizing images + +Anubis uses three images to visually communicate the state of the program. These are: + +| Image name | Intended message | Example | +| :------------- | :----------------------------------------------- | :-------------------------------- | +| `happy.webp` | You have passed validation, all is good | ![](/img/botstopper/happy.webp) | +| `pensive.webp` | Checking is running, hold steady until it's done | ![](/img/botstopper/pensive.webp) | +| `reject.webp` | Something went wrong, this is a terminal state | ![](/img/botstopper/reject.webp) | + +To make your own images at the optimal quality, use the following ffmpeg command: + +```text +ffmpeg -i /path/to/image -vf scale=-1:384 happy.webp +``` + +`ffprobe` should report something like this on the generated images: + +```text +Input #0, webp_pipe, from 'happy.webp': + Duration: N/A, bitrate: N/A + Stream #0:0: Video: webp, none, 25 fps, 25 tbr, 25 tbn +``` + +In testing 384 by 384 pixels gives the best balance between filesize, quality, and clarity. + +```text +$ du -hs * +4.0K happy.webp + 12K pensive.webp +8.0K reject.webp +``` + +## Customizing messages + +You can customize messages using the following environment variables: + +| Message | Environment variable | Default | +| :------------------- | :------------------- | :----------------------------------------- | +| Challenge page title | `CHALLENGE_TITLE` | `Ensuring the security of your connection` | +| Error page title | `ERROR_TITLE` | `Error` | + +For example: + +```sh +# /etc/techaro-botstopper/gitea.env +CHALLENGE_TITLE="Wait a moment please!" +ERROR_TITLE="Client error" +``` diff --git a/docs/manifest/cfg/anubis/botPolicies.yaml b/docs/manifest/cfg/anubis/botPolicies.yaml index 0abf0dcf..38089926 100644 --- a/docs/manifest/cfg/anubis/botPolicies.yaml +++ b/docs/manifest/cfg/anubis/botPolicies.yaml @@ -11,6 +11,7 @@ ## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from. bots: + - import: (data)/crawlers/commoncrawl.yaml # Pathological bots to deny - # This correlates to data/bots/deny-pathological.yaml in the source tree # https://github.com/TecharoHQ/anubis/blob/main/data/bots/deny-pathological.yaml diff --git a/docs/static/img/botstopper/example-screenshot.webp b/docs/static/img/botstopper/example-screenshot.webp new file mode 100644 index 00000000..6d7c712c Binary files /dev/null and b/docs/static/img/botstopper/example-screenshot.webp differ diff --git a/docs/static/img/botstopper/happy.webp b/docs/static/img/botstopper/happy.webp new file mode 100644 index 00000000..fe867d08 Binary files /dev/null and b/docs/static/img/botstopper/happy.webp differ diff --git a/docs/static/img/botstopper/pensive.webp b/docs/static/img/botstopper/pensive.webp new file mode 100644 index 00000000..bc04a18d Binary files /dev/null and b/docs/static/img/botstopper/pensive.webp differ diff --git a/docs/static/img/botstopper/reject.webp b/docs/static/img/botstopper/reject.webp new file mode 100644 index 00000000..f6b93342 Binary files /dev/null and b/docs/static/img/botstopper/reject.webp differ diff --git a/lib/localization/locales/de.json b/lib/localization/locales/de.json new file mode 100644 index 00000000..ea1e27d6 --- /dev/null +++ b/lib/localization/locales/de.json @@ -0,0 +1,63 @@ +{ + "loading": "Ladevorgang...", + "why_am_i_seeing": "Warum sehe ich diese Seite?", + "protected_by": "Geschützt durch", + "made_with": "Mit ❤️ gemacht in 🇨🇦", + "mascot_design": "Maskottchen erstellt von", + "ai_companies_explanation": "Diese Seite wird angezeigt, da der Betreiber der Webseite Anubis eingerichtet hat, um sie vor aggressiven KI-Website-Scrapern zu schützen. Diese können Ausfälle der Webseite verursachen, wodurch die Webseite für jeden nicht erreichbar ist.", + "anubis_compromise": "Anubis ist eine Art Kompromiss. Es verwendet die sogenannte Proof-of-Work Methode nach Hashcash, ein Mechanismus, der ursprünglich zur E-Mail-Spam-Bekämpfung entwickelt wurde. Die Idee dahinter ist, dass ein einziger User nur eine kleine Verzögerung hat, auf die Webseite zu gelangen; bei Scrapern kann das allerdings große Auswirkungen haben.", + "hack_purpose": "Man könnte dies als eine Lösung bezeichnen, die gut genug ist, einem etwas Zeit zu verschaffen für Fingerprinting und dem Identifizieren von Headless Browsern, sodass im besten Fall normale User diese Seite garnicht erst zu sehen bekommen.", + "jshelter_note": "Anubis benötigt moderne JavaScript-Features, welche von Plugins wie zB JShelter deaktiviert werden. Bitte deaktiviere also JShelter oder ähnliche Plugins für diese Domain.", + "version_info": "Diese Webseite läuft mit Anubis version", + "try_again": "Nochmal probieren", + "go_home": "Zur Hauptseite", + "contact_webmaster": "oder wenn es sich hier um einen Fehler handelt, kontaktiere bitte den Administrator der Webseite unter", + "connection_security": "Bitte warte einen Moment während wir sicherstellen, dass eine sichere Verbindung verwendet wird.", + "javascript_required": "Es muss leider JavaScript aktiviert werden, um den Check durchführen zu können. Dies ist leider notwendig weil Firmen im KI-Sektor die sozialen Verhältnisse geändert haben, wie Website-Hosting funktioniert. Eine Lösung ohne JavaScript ist in Entwicklung.", + "benchmark_requires_js": "Das Benchmark-Tool benötigt das Aktivieren von JavaScript.", + "difficulty": "Schwierigkeit:", + "algorithm": "Algorithmus:", + "compare": "Vergleich:", + "time": "Zeit", + "iters": "Iterationen", + "time_a": "Zeit A", + "iters_a": "Iterationen A", + "time_b": "Zeit B", + "iters_b": "Iterationen B", + "static_check_endpoint": "Dies ist nur ein Check-Endpunkt, der von beispielsweise einem Reverse-Proxy geprüft werden kann.", + "authorization_required": "Zugriffserlaubnis benötigt", + "cookies_disabled": "Cookies sind in Ihrem Browser deaktiviert. Anubis benötigt Cookies um sicherzustellen, dass es sich hierbei um einen validen Zugriff handelt. Bitte aktiviere Cookies für diese Domain.", + "access_denied": "Zugriff verweigert: Fehlercode", + "dronebl_entry": "Eintrag in DroneBL", + "see_dronebl_lookup": "anzeigen", + "internal_server_error": "Interner Server Error: Misskonfiguration von Anubis. Bitte kontatkiere den Administrator damit dieser die Logs prüfen kann.", + "invalid_redirect": "Ungültige Weiterleitung", + "redirect_not_parseable": "URL der Weiterleitung kann nicht verarbeitet werden", + "redirect_domain_not_allowed": "Domain der Weiterleitung nicht erlaubt", + "failed_to_sign_jwt": "Signierung des JWT fehlgeschlagen", + "invalid_invocation": "Aufrufen von MakeChallenge ungültig", + "client_error_browser": "Client Error: Bitte stelle sicher, dass der Browser aktuell ist und probiere es später erneut.", + "oh_noes": "Vermaledeit!", + "benchmarking_anubis": "Benchmark wird durchgeführt!", + "you_are_not_a_bot": "Sie sind kein Bot!", + "making_sure_not_bot": "Ihr Browser wird geprüft!", + "celphase": "CELPHASE", + "js_web_crypto_error": "Ihr Browser hat leider kein funktionierendes web.crypto Element. Wird eine sichere Verbindung verwendet?", + "js_web_workers_error": "Ihr Browser unterstützt keine Web-Worker (Anubis verwendet diese, damit der Browser nicht unresponsive wird). Ist eventuell ein Plugin wie zB JShelter installiert?", + "js_cookies_error": "Ihr Browser speichert keine Cookies. Anubis verwendet Cookies um ein gültiges Token zu speichern damit es wissen kann, welche Browser bereits geprüft wurden. Bitte aktiviere Cookies für diese Domain. Die Cookie-Namen von Anubis könnten sich jederzeit ändern. Cookie-Namen sind kein Teil der öffentlichen API.", + "js_context_not_secure": "Diese Verbindung ist nicht sicher!", + "js_context_not_secure_msg": "Bitte probiere, dich via HTTPS zu verbinden und lass den Webseiten-Administrator wissen, sauber HTTPS einzurichten. Mehr Informationen unter: MDN.", + "js_calculating": "Berechnung wird durchgeführt...", + "js_missing_feature": "Fehlendes Feature", + "js_challenge_error": "Fehler während des Checks!", + "js_challenge_error_msg": "Der Check-Algorithmus konnte nicht geladen werden. Bitte lade diese Seite erneut.", + "js_calculating_difficulty": "Berechnung wird durchgeführt...
Schwierigkeit:", + "js_speed": "Geschwindigkeit:", + "js_verification_longer": "Der Check benötigt länger als erwartet. Bitte bleibe auf der Seite.", + "js_success": "Erfolgreich!", + "js_done_took": "Fertig! Dauer:", + "js_iterations": "Iterationen", + "js_finished_reading": "Fertig gelesen, weiter zur Seite →", + "js_calculation_error": "Fehler bei der Berechnung!", + "js_calculation_error_msg": "Fehler bei der Berechnung des Checks:" +} \ No newline at end of file diff --git a/lib/localization/locales/manifest.json b/lib/localization/locales/manifest.json index aa1341d7..7e646731 100644 --- a/lib/localization/locales/manifest.json +++ b/lib/localization/locales/manifest.json @@ -1,3 +1,3 @@ { - "supportedLanguages": ["en", "fr", "es", "pt-BR"] + "supportedLanguages": ["en", "fr", "es", "pt-BR", "de", "tr"] } diff --git a/lib/localization/locales/tr.json b/lib/localization/locales/tr.json new file mode 100644 index 00000000..b352f0f1 --- /dev/null +++ b/lib/localization/locales/tr.json @@ -0,0 +1,63 @@ +{ + "loading": "Yükleniyor...", + "why_am_i_seeing": "Bunu neden görüyorum?", + "protected_by": "Koruma sağlayan:", + "made_with": "🇨🇦'da ❤️ ile yapıldı", + "mascot_design": "Maskot tasarımı:", + "ai_companies_explanation": "Bunu görüyorsunuz çünkü bu web sitesinin yöneticisi, yapay zeka şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubis'i kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.", + "anubis_compromise": "Anubis bir uzlaşmadır. Anubis, spam e-postaları azaltmak için önerilen bir iş kanıtı sistemi olan Hashcash benzeri bir sistemi kullanır. Bireysel kullanımda bu ek yük göz ardı edilebilir olsa da, büyük ölçekli kazıyıcılarda birikerek kazımayı oldukça maliyetli hale getirir.", + "hack_purpose": "Bu geçici bir çözümdür. Esas amacı, başsız tarayıcıları parmak iziyle tanımlamak için daha fazla zaman kazandırmak, ve bu sayede meşru kullanıcıların bu zorluk sayfasını görmesini önlemektir.", + "jshelter_note": "Lütfen dikkat: Anubis, JShelter gibi eklentilerin devre dışı bıraktığı modern JavaScript özelliklerini gerektirir. Lütfen bu alan adı için JShelter veya benzeri eklentileri devre dışı bırakın.", + "version_info": "Bu web sitesi şu Anubis sürümünü çalıştırıyor:", + "try_again": "Tekrar dene", + "go_home": "Ana sayfaya dön", + "contact_webmaster": "ya da engellenmemeniz gerektiğini düşünüyorsanız, lütfen şu adrese e-posta gönderin:", + "connection_security": "Bağlantınızın güvenliği sağlanırken lütfen bekleyin.", + "javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScript’i etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zeka şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScript’siz bir çözüm geliştirilmektedir.", + "benchmark_requires_js": "Kıyaslama aracının çalıştırılması için JavaScript’in etkin olması gereklidir.", + "difficulty": "Zorluk:", + "algorithm": "Algoritma:", + "compare": "Karşılaştır:", + "time": "Süre", + "iters": "Tekrar", + "time_a": "Süre A", + "iters_a": "Tekrar A", + "time_b": "Süre B", + "iters_b": "Tekrar B", + "static_check_endpoint": "Bu sadece ters proxy'nizin kullanması için bir kontrol adresidir.", + "authorization_required": "Yetkilendirme gerekli", + "cookies_disabled": "Tarayıcınız çerezleri devre dışı bırakacak şekilde yapılandırılmış. Anubis, gerçek bir kullanıcı olduğunuzu doğrulamak için çerezlere ihtiyaç duyar. Lütfen bu alan adı için çerezleri etkinleştirin.", + "access_denied": "Erişim Reddedildi: hata kodu", + "dronebl_entry": "DroneBL bir giriş bildirdi", + "see_dronebl_lookup": "bakınız", + "internal_server_error": "Sunucu Hatası: Yönetici Anubis'i yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şunun civarındaki kayıtlara bakmasını isteyin:", + "invalid_redirect": "Geçersiz yönlendirme", + "redirect_not_parseable": "Yönlendirme URL'si çözümlenemiyor", + "redirect_domain_not_allowed": "Yönlendirme alan adına izin verilmiyor", + "failed_to_sign_jwt": "JWT imzalanamadı", + "invalid_invocation": "Geçersiz MakeChallenge çağrısı", + "client_error_browser": "İstemci Hatası: Lütfen tarayıcınızın güncel olduğundan emin olun ve daha sonra tekrar deneyin.", + "oh_noes": "Ah hayır!", + "benchmarking_anubis": "Anubis kıyaslanıyor!", + "you_are_not_a_bot": "Bot değilsiniz!", + "making_sure_not_bot": "Bot olmadığınızdan emin oluyoruz!", + "celphase": "CELPHASE", + "js_web_crypto_error": "Tarayıcınızda çalışan bir web.crypto öğesi yok. Bu sayfayı güvenli bir bağlantı üzerinden mi görüntülüyorsunuz?", + "js_web_workers_error": "Tarayıcınız web işçilerini desteklemiyor (Anubis, tarayıcınızın donmaması için bunları kullanır). JShelter gibi bir eklenti mi kurulu?", + "js_cookies_error": "Tarayıcınız çerezleri kaydetmiyor. Anubis, kullanıcıların zorlukları geçtiğini belirlemek için imzalı bir belirteci çerezde saklar. Lütfen bu alan adı için çerezleri etkinleştirin. Anubis’in kullandığı çerez adları önceden bildirilmeksizin değişebilir. Çerez adları ve değerleri resmi API’nin bir parçası değildir.", + "js_context_not_secure": "Bağlantınız güvenli değil!", + "js_context_not_secure_msg": "HTTPS üzerinden bağlanmayı deneyin veya yöneticiden HTTPS kurulumu yapmasını isteyin. Daha fazla bilgi için bkz. MDN.", + "js_calculating": "Hesaplanıyor...", + "js_missing_feature": "Eksik özellik", + "js_challenge_error": "Hesaplama hatası!", + "js_challenge_error_msg": "Algoritma çözümlemesi başarısız oldu. Sayfayı yeniden yüklemeyi deneyebilirsiniz.", + "js_calculating_difficulty": "Hesaplanıyor...
Zorluk:", + "js_speed": "Hız:", + "js_verification_longer": "Doğrulama beklenenden uzun sürüyor. Lütfen sayfayı yenilemeyin.", + "js_success": "Başarılı!", + "js_done_took": "Tamamlandı! Süre:", + "js_iterations": "tekrar", + "js_finished_reading": "Okumayı bitirdim, devam et →", + "js_calculation_error": "Hesaplama hatası!", + "js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:" +} \ No newline at end of file diff --git a/lib/localization/localization.go b/lib/localization/localization.go index d8af92c3..e268f0e4 100644 --- a/lib/localization/localization.go +++ b/lib/localization/localization.go @@ -3,6 +3,7 @@ package localization import ( "embed" "encoding/json" + "github.com/TecharoHQ/anubis" "net/http" "strings" "sync" @@ -57,14 +58,14 @@ func NewLocalizationService() *LocalizationService { globalService = &LocalizationService{bundle: bundle} }) - + // Safety check - if globalService is still nil, create a minimal one if globalService == nil { bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("json", json.Unmarshal) globalService = &LocalizationService{bundle: bundle} } - + return globalService } @@ -93,8 +94,13 @@ func (sl *SimpleLocalizer) T(messageID string) string { return sl.Localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: messageID}) } -// GetLocalizer creates a localizer based on the request's Accept-Language header +// GetLocalizer creates a localizer based on the request's Accept-Language header or forcedLanguage option func GetLocalizer(r *http.Request) *SimpleLocalizer { - localizer := NewLocalizationService().GetLocalizerFromRequest(r) + var localizer *i18n.Localizer + if anubis.ForcedLanguage == "" { + localizer = NewLocalizationService().GetLocalizerFromRequest(r) + } else { + localizer = NewLocalizationService().GetLocalizer(anubis.ForcedLanguage) + } return &SimpleLocalizer{Localizer: localizer} } diff --git a/lib/localization/localization_test.go b/lib/localization/localization_test.go index d416fc62..840d837b 100644 --- a/lib/localization/localization_test.go +++ b/lib/localization/localization_test.go @@ -27,6 +27,22 @@ func TestLocalizationService(t *testing.T) { } }) + t.Run("German localization", func(t *testing.T) { + localizer := service.GetLocalizer("de") + result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"}) + if result != "Ladevorgang..." { + t.Errorf("Expected 'Ladevorgang...', got '%s'", result) + } + }) + + t.Run("Turkish localization", func(t *testing.T) { + localizer := service.GetLocalizer("tr") + result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: "loading"}) + if result != "Yükleniyor..." { + t.Errorf("Expected 'Yükleniyor...', got '%s'", result) + } + }) + t.Run("All required keys exist in English", func(t *testing.T) { localizer := service.GetLocalizer("en") requiredKeys := []string{ @@ -56,6 +72,21 @@ func TestLocalizationService(t *testing.T) { } } }) + + t.Run("All required keys exist in Turkish", func(t *testing.T) { + localizer := service.GetLocalizer("tr") + requiredKeys := []string{ + "loading", "why_am_i_seeing", "protected_by", "made_with", + "mascot_design", "try_again", "go_home", "javascript_required", + } + + for _, key := range requiredKeys { + result := localizer.MustLocalize(&i18n.LocalizeConfig{MessageID: key}) + if result == "" { + t.Errorf("Key '%s' returned empty string", key) + } + } + }) } type manifest struct { diff --git a/run/anubis@.service b/run/anubis@.service index 7b0fd94b..2fe8fdda 100644 --- a/run/anubis@.service +++ b/run/anubis@.service @@ -12,7 +12,7 @@ CacheDirectory=anubis/%i CacheDirectoryMode=0755 StateDirectory=anubis/%i StateDirectoryMode=0755 -RuntimeDirectory=anubis +RuntimeDirectory=anubis/%i RuntimeDirectoryMode=0755 ReadWritePaths=/run diff --git a/web/static/robots.txt b/web/static/robots.txt index f09f0f90..6e65c425 100644 --- a/web/static/robots.txt +++ b/web/static/robots.txt @@ -9,7 +9,6 @@ User-agent: Applebot-Extended User-agent: bedrockbot User-agent: Brightbot 1.0 User-agent: Bytespider -User-agent: CCBot User-agent: ChatGPT-User User-agent: Claude-SearchBot User-agent: Claude-User