From c981c23f7e344f22629e7aee43e001cbf6d3b913 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Wed, 2 Jul 2025 05:25:10 +0000 Subject: [PATCH 1/9] chore: npm run generate Signed-off-by: Xe Iaso --- lib/challenge/metarefresh/metarefresh_templ.go | 2 +- web/index_templ.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/challenge/metarefresh/metarefresh_templ.go b/lib/challenge/metarefresh/metarefresh_templ.go index 172395a5..c677a591 100644 --- a/lib/challenge/metarefresh/metarefresh_templ.go +++ b/lib/challenge/metarefresh/metarefresh_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.906 package metarefresh //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/web/index_templ.go b/web/index_templ.go index ab9328b1..0601ba22 100644 --- a/web/index_templ.go +++ b/web/index_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.898 +// templ: version: v0.3.906 package web //lint:file-ignore SA4006 This context is only used if a nested component is present. From 0e431383241e8e7fb0a2b958bc04625b9f472a29 Mon Sep 17 00:00:00 2001 From: Martin <31348196+Earl0fPudding@users.noreply.github.com> Date: Wed, 2 Jul 2025 07:33:00 +0200 Subject: [PATCH 2/9] feat(localization): Add option for forcing a language (#742) * Add forcesLanguage option * Change comments for forced language option * Add changes to CHANGELOG.md --------- Signed-off-by: Xe Iaso Co-authored-by: Xe Iaso --- anubis.go | 4 ++++ cmd/anubis/main.go | 2 ++ docs/docs/CHANGELOG.md | 6 +++--- lib/localization/localization.go | 14 ++++++++++---- 4 files changed, 19 insertions(+), 7 deletions(-) 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/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index b77ae99b..e19e106d 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -13,13 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) - Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained) - - 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)) - Remove the "Success" interstitial after a proof of work challenge is concluded. +- Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742)) ## v1.20.0: Thancred Waters 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} } From 888b7d6e7758daf73b00e6c444046691e60e2544 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Thu, 3 Jul 2025 10:29:05 +0000 Subject: [PATCH 3/9] fix(run/anubis@.service): unique runtimedir per instance (#750) * fix(run/anubis@.service): unique runtimedir per instance Signed-off-by: Xe Iaso * chore: spelling Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso --- .github/actions/spelling/expect.txt | 3 ++- docs/docs/CHANGELOG.md | 18 ++++++++++++++++++ run/anubis@.service | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 6c7396a9..ec1b5c11 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -75,6 +75,7 @@ domainhere dracula dronebl droneblresponse +dropin duckduckbot eerror ellenjoe @@ -237,6 +238,7 @@ risc ruleset runlevels RUnlock +runtimedir sas sasl Scumm @@ -328,5 +330,4 @@ yoursite Zenos zizmor zombocom -Zonbocom zos diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index e19e106d..88c85912 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -10,7 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + + - Add `COOKIE_SECURE` option to set the cookie [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies) - Sets cookie defaults to use [SameSite: None](https://web.dev/articles/samesite-cookies-explained) - Determine the `BIND_NETWORK`/`--bind-network` value from the bind address ([#677](https://github.com/TecharoHQ/anubis/issues/677)). @@ -21,6 +23,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove the "Success" interstitial after a proof of work challenge is concluded. - Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742)) +### 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 The big ticket items are as follows: 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 From c121896f9c907529bf50343a1607282638d9378e Mon Sep 17 00:00:00 2001 From: Martin <31348196+Earl0fPudding@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:48:17 +0200 Subject: [PATCH 4/9] feat(localization): Add German language translation (#741) * Add german translation * Adjust german localization * Adjust js_finished_reading in german localization * Mention this change in CHANGELOG.md * Add test for German localization * Update lib/localization/locales/de.json Co-authored-by: Florian Lehner Signed-off-by: Martin <31348196+Earl0fPudding@users.noreply.github.com> * Remove duplicate "leider" in lib/localization/locales/de.json Co-authored-by: Florian Lehner Signed-off-by: Martin <31348196+Earl0fPudding@users.noreply.github.com> * Update lib/localization/locales/de.json Co-authored-by: Florian Lehner Signed-off-by: Martin <31348196+Earl0fPudding@users.noreply.github.com> * Update lib/localization/locales/de.json Co-authored-by: Florian Lehner Signed-off-by: Martin <31348196+Earl0fPudding@users.noreply.github.com> --------- Signed-off-by: Martin <31348196+Earl0fPudding@users.noreply.github.com> Signed-off-by: Xe Iaso Co-authored-by: Florian Lehner Co-authored-by: Xe Iaso --- docs/docs/CHANGELOG.md | 1 + lib/localization/locales/de.json | 63 ++++++++++++++++++++++++++ lib/localization/locales/manifest.json | 2 +- lib/localization/localization_test.go | 8 ++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 lib/localization/locales/de.json diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 88c85912..fdd553f9 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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)) +- 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. - Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742)) 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..a5a0b9f5 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"] } diff --git a/lib/localization/localization_test.go b/lib/localization/localization_test.go index d416fc62..2761fbbd 100644 --- a/lib/localization/localization_test.go +++ b/lib/localization/localization_test.go @@ -27,6 +27,14 @@ 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("All required keys exist in English", func(t *testing.T) { localizer := service.GetLocalizer("en") requiredKeys := []string{ From d7a758f8055a9c03c243184d952c246be6906d4e Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Thu, 3 Jul 2025 23:09:45 +0000 Subject: [PATCH 5/9] docs: add BotStopper docs from the git repo (#752) Signed-off-by: Xe Iaso --- .github/actions/spelling/expect.txt | 7 + docs/docs/admin/botstopper.mdx | 215 ++++++++++++++++++ .../img/botstopper/example-screenshot.webp | Bin 0 -> 29266 bytes docs/static/img/botstopper/happy.webp | Bin 0 -> 3630 bytes docs/static/img/botstopper/pensive.webp | Bin 0 -> 9182 bytes docs/static/img/botstopper/reject.webp | Bin 0 -> 7262 bytes 6 files changed, 222 insertions(+) create mode 100644 docs/docs/admin/botstopper.mdx create mode 100644 docs/static/img/botstopper/example-screenshot.webp create mode 100644 docs/static/img/botstopper/happy.webp create mode 100644 docs/static/img/botstopper/pensive.webp create mode 100644 docs/static/img/botstopper/reject.webp diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index ec1b5c11..33c66523 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -20,11 +20,13 @@ bdba berr bingbot bitcoin +bitrate blogging Bluesky blueskybot boi botnet +botstopper BPort Brightbot broked @@ -52,6 +54,7 @@ ckie cloudflare Codespaces confd +connnection containerbuild coreutils Cotoyogi @@ -93,6 +96,7 @@ facebookgo Factset fastcgi fediverse +ffprobe finfos Firecrawl flagenv @@ -199,6 +203,7 @@ omgilibot openai opengraph openrc +oswald pag palemoon Pangu @@ -272,6 +277,8 @@ SVCNAME tagline tarballs tarrif +tbn +tbr techaro techarohq templ 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/static/img/botstopper/example-screenshot.webp b/docs/static/img/botstopper/example-screenshot.webp new file mode 100644 index 0000000000000000000000000000000000000000..6d7c712cd60eb79502c18c00d702e2d30d56bfdb GIT binary patch literal 29266 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 M5l}z?000000D5uZ@c;k- literal 0 HcmV?d00001 diff --git a/docs/static/img/botstopper/happy.webp b/docs/static/img/botstopper/happy.webp new file mode 100644 index 0000000000000000000000000000000000000000..fe867d08609c912a5dc0dc6f2dff787fc455b14a GIT binary patch literal 3630 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 AumAu6 literal 0 HcmV?d00001 diff --git a/docs/static/img/botstopper/pensive.webp b/docs/static/img/botstopper/pensive.webp new file mode 100644 index 0000000000000000000000000000000000000000..bc04a18dc0730eadaa95b46f177a811b442905f1 GIT binary patch literal 9182 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 literal 0 HcmV?d00001 diff --git a/docs/static/img/botstopper/reject.webp b/docs/static/img/botstopper/reject.webp new file mode 100644 index 0000000000000000000000000000000000000000..f6b9334241876ac01492efa2921cf79d4de343d1 GIT binary patch literal 7262 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 literal 0 HcmV?d00001 From 7c0996448a3b3f544e7c5f277a4d5641b61cd561 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 4 Jul 2025 00:10:45 +0000 Subject: [PATCH 6/9] chore(default-config): allowlist common crawl (#753) This may seem strange, but allowlisting common crawl means that scrapers have less incentive to scrape because they can just grab the data from common crawl instead of scraping it again. --- data/bots/ai-catchall.yaml | 2 +- data/bots/ai-robots-txt.yaml | 4 +++- data/crawlers/_allow-good.yaml | 3 ++- data/crawlers/commoncrawl.yaml | 12 ++++++++++++ docs/docs/CHANGELOG.md | 1 + 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 data/crawlers/commoncrawl.yaml 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 fdd553f9..0ff2804c 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. - Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742)) +- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape ### Potentially breaking changes From 2f1e78cc6c6d645a9f1f5a50ad8b4b8ce24f1e4d Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 4 Jul 2025 00:22:43 +0000 Subject: [PATCH 7/9] chore(docs/manifest): allow common crawl to test with the team Signed-off-by: Xe Iaso --- docs/manifest/cfg/anubis/botPolicies.yaml | 1 + 1 file changed, 1 insertion(+) 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 From 845095c3f6f3b4b425a3388d17e30c347314fd83 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 4 Jul 2025 00:23:21 +0000 Subject: [PATCH 8/9] chore(robots.txt): don't block CCBot Signed-off-by: Xe Iaso --- web/static/robots.txt | 1 - 1 file changed, 1 deletion(-) 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 From d0fae02d05008263086dc98164fe09b2cf7930eb Mon Sep 17 00:00:00 2001 From: Duru Can Celasun Date: Fri, 4 Jul 2025 05:50:16 +0100 Subject: [PATCH 9/9] feat(localization): Add Turkish language translation (#751) Signed-off-by: Xe Iaso Co-authored-by: Xe Iaso --- docs/docs/CHANGELOG.md | 1 + lib/localization/locales/manifest.json | 2 +- lib/localization/locales/tr.json | 63 ++++++++++++++++++++++++++ lib/localization/localization_test.go | 23 ++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 lib/localization/locales/tr.json diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 0ff2804c..7c39f477 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. - 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 diff --git a/lib/localization/locales/manifest.json b/lib/localization/locales/manifest.json index a5a0b9f5..7e646731 100644 --- a/lib/localization/locales/manifest.json +++ b/lib/localization/locales/manifest.json @@ -1,3 +1,3 @@ { - "supportedLanguages": ["en", "fr", "es", "pt-BR", "de"] + "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_test.go b/lib/localization/localization_test.go index 2761fbbd..840d837b 100644 --- a/lib/localization/localization_test.go +++ b/lib/localization/localization_test.go @@ -35,6 +35,14 @@ func TestLocalizationService(t *testing.T) { } }) + 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{ @@ -64,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 {