Compare commits

..

7 Commits

Author SHA1 Message Date
Xe Iaso
d38767c50d test(palemoon): actually invoke timeout
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 12:30:10 +00:00
Xe Iaso
f948406d2a test(palemoon): add 180 second timeout
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 12:29:38 +00:00
Xe Iaso
8d1c80c244 docs(changelog): remove this bit to make it its own PR
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 12:10:57 +00:00
Xe Iaso
feba16b0bb chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 12:10:17 +00:00
Xe Iaso
71b2561279 docs: update CHANGELOG
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-31 12:10:11 +00:00
Xe Iaso
87b0ed9b97 feat(lib): automatically allow any urls in the ogtags allow cache
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-29 16:24:06 +00:00
Xe Iaso
d67f728a9b feat(ogtags): when encountering opengraph URLs, add them to an allow cache
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-07-29 16:23:44 +00:00
12 changed files with 31 additions and 107 deletions

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: 'bug:'
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'feature:'
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,9 +0,0 @@
---
name: Security report
about: Do not file security reports here. Email security@techaro.lol.
title: "security:"
labels: ""
assignees: Xe
---
Do not file security reports here. Email security@techaro.lol.

View File

@@ -254,7 +254,6 @@ runtimedir
runtimedirectory runtimedirectory
sas sas
sasl sasl
screenshots
searchbot searchbot
searx searx
sebest sebest

View File

@@ -13,7 +13,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- This changes the project to: --> <!-- This changes the project to: -->
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package. - The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)). - [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments. - The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.

View File

@@ -26,8 +26,8 @@ func InitSlog(level string) {
slog.SetDefault(slog.New(h)) slog.SetDefault(slog.New(h))
} }
func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger { func GetRequestLogger(r *http.Request) *slog.Logger {
return base.With( return slog.With(
"host", r.Host, "host", r.Host,
"method", r.Method, "method", r.Method,
"path", r.URL.Path, "path", r.URL.Path,

View File

@@ -75,7 +75,6 @@ type Server struct {
hs512Secret []byte hs512Secret []byte
opts Options opts Options
store store.Interface store store.Interface
logger *slog.Logger
} }
func (s *Server) getTokenKeyfunc() jwt.Keyfunc { func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
@@ -151,7 +150,7 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request)
} }
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) { func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
lg := internal.GetRequestLogger(s.logger, r) lg := internal.GetRequestLogger(r)
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil { if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
lg.Debug("serving opengraph tag asset") lg.Debug("serving opengraph tag asset")
@@ -165,7 +164,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/" cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
} }
cr, rule, err := s.check(r, lg) cr, rule, err := s.check(r)
if err != nil { if err != nil {
lg.Error("check failed", "err", err) lg.Error("check failed", "err", err)
localizer := localization.GetLocalizer(r) localizer := localization.GetLocalizer(r)
@@ -281,7 +280,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
return true return true
default: default:
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host}) s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
lg.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule) slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error"))) s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error")))
return true return true
} }
@@ -317,7 +316,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
} }
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) { func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
lg := internal.GetRequestLogger(s.logger, r) lg := internal.GetRequestLogger(r)
localizer := localization.GetLocalizer(r) localizer := localization.GetLocalizer(r)
redir := r.FormValue("redir") redir := r.FormValue("redir")
@@ -336,7 +335,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
r.URL.Path = redir r.URL.Path = redir
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
cr, rule, err := s.check(r, lg) cr, rule, err := s.check(r)
if err != nil { if err != nil {
lg.Error("check failed", "err", err) lg.Error("check failed", "err", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@@ -388,7 +387,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
} }
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) { func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
lg := internal.GetRequestLogger(s.logger, r) lg := internal.GetRequestLogger(r)
localizer := localization.GetLocalizer(r) localizer := localization.GetLocalizer(r)
redir := r.FormValue("redir") redir := r.FormValue("redir")
@@ -435,7 +434,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
return return
} }
cr, rule, err := s.check(r, lg) cr, rule, err := s.check(r)
if err != nil { if err != nil {
lg.Error("check failed", "err", err) lg.Error("check failed", "err", err)
s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error"))) s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error")))
@@ -516,7 +515,7 @@ func cr(name string, rule config.Rule, weight int) policy.CheckResult {
} }
// Check evaluates the list of rules, and returns the result // Check evaluates the list of rules, and returns the result
func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *policy.Bot, error) { func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) {
host := r.Header.Get("X-Real-Ip") host := r.Header.Get("X-Real-Ip")
if host == "" { if host == "" {
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set") return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set")
@@ -540,7 +539,7 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge: case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge:
return cr("bot/"+b.Name, b.Action, weight), &b, nil return cr("bot/"+b.Name, b.Action, weight), &b, nil
case config.RuleWeigh: case config.RuleWeigh:
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust) slog.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
weight += b.Weight.Adjust weight += b.Weight.Adjust
} }
} }
@@ -549,7 +548,7 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
for _, t := range s.policy.Thresholds { for _, t := range s.policy.Thresholds {
result, _, err := t.Program.ContextEval(r.Context(), &policy.ThresholdRequest{Weight: weight}) result, _, err := t.Program.ContextEval(r.Context(), &policy.ThresholdRequest{Weight: weight})
if err != nil { if err != nil {
lg.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err) slog.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err)
continue continue
} }

View File

@@ -343,7 +343,7 @@ func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1") req.Header.Add("X-Real-Ip", "127.0.0.1")
cr, bot, err := s.check(req, s.logger) cr, bot, err := s.check(req)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -583,7 +583,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1") req.Header.Add("X-Real-Ip", "127.0.0.1")
req.Header.Add("Cf-Worker", "true") req.Header.Add("Cf-Worker", "true")
cr, _, err := s.check(req, s.logger) cr, _, err := s.check(req)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -601,7 +601,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
req.Header.Add("X-Real-Ip", "127.0.0.1") req.Header.Add("X-Real-Ip", "127.0.0.1")
cr, _, err := s.check(req, s.logger) cr, _, err := s.check(req)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -43,7 +43,6 @@ type Options struct {
OpenGraph config.OpenGraph OpenGraph config.OpenGraph
ServeRobotsTXT bool ServeRobotsTXT bool
CookieSecure bool CookieSecure bool
Logger *slog.Logger
} }
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) { func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
@@ -90,12 +89,8 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty
} }
func New(opts Options) (*Server, error) { func New(opts Options) (*Server, error) {
if opts.Logger == nil {
opts.Logger = slog.With("subsystem", "anubis")
}
if opts.ED25519PrivateKey == nil && opts.HS512Secret == nil { if opts.ED25519PrivateKey == nil && opts.HS512Secret == nil {
opts.Logger.Debug("opts.PrivateKey not set, generating a new one") slog.Debug("opts.PrivateKey not set, generating a new one")
_, priv, err := ed25519.GenerateKey(rand.Reader) _, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil { if err != nil {
return nil, fmt.Errorf("lib: can't generate private key: %v", err) return nil, fmt.Errorf("lib: can't generate private key: %v", err)
@@ -113,7 +108,6 @@ func New(opts Options) (*Server, error) {
opts: opts, opts: opts,
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store), OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store),
store: opts.Policy.Store, store: opts.Policy.Store,
logger: opts.Logger,
} }
mux := http.NewServeMux() mux := http.NewServeMux()

View File

@@ -120,7 +120,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
return return
} }
lg := internal.GetRequestLogger(s.logger, r) lg := internal.GetRequestLogger(r)
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) { if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) {
lg.Error("client was given a challenge but does not in fact support gzip compression") lg.Error("client was given a challenge but does not in fact support gzip compression")

View File

@@ -2,7 +2,7 @@
"loading": "Hleður...", "loading": "Hleður...",
"why_am_i_seeing": "Af hverju er ég að sjá þetta?", "why_am_i_seeing": "Af hverju er ég að sjá þetta?",
"protected_by": "Verndað með", "protected_by": "Verndað með",
"protected_from": "Frá", "protected_from": "From",
"made_with": "Gert í 🇨🇦 með ❤️", "made_with": "Gert í 🇨🇦 með ❤️",
"mascot_design": "Lukkudýrið hannað af", "mascot_design": "Lukkudýrið hannað af",
"ai_companies_explanation": "Þú ert að sjá þetta vegna þess að kerfisstjóri þessa vefsvæðis hefur sett upp Anubis til að vernda vefþjóninn fyrir holskeflu beiðna frá svokölluðum gervigreindarfyrirtækjum sem samviskulaust eru að skrapa upplýsingar af vefsvæðum annarra. Þetta getur valdið og veldur töfum og truflunum á þessum vefsvæðum, sem aftur veldur því að efni þeirra verður öllum óaðgengilegt.", "ai_companies_explanation": "Þú ert að sjá þetta vegna þess að kerfisstjóri þessa vefsvæðis hefur sett upp Anubis til að vernda vefþjóninn fyrir holskeflu beiðna frá svokölluðum gervigreindarfyrirtækjum sem samviskulaust eru að skrapa upplýsingar af vefsvæðum annarra. Þetta getur valdið og veldur töfum og truflunum á þessum vefsvæðum, sem aftur veldur því að efni þeirra verður öllum óaðgengilegt.",

View File

@@ -2,19 +2,19 @@
"loading": "Yükleniyor...", "loading": "Yükleniyor...",
"why_am_i_seeing": "Bunu neden görüyorum?", "why_am_i_seeing": "Bunu neden görüyorum?",
"protected_by": "Koruma sağlayan:", "protected_by": "Koruma sağlayan:",
"protected_from": "Yapan:", "protected_from": "From",
"made_with": "🇨🇦da ❤️ ile yapıldı", "made_with": "🇨🇦'da ❤️ ile yapıldı",
"mascot_design": "Maskot tasarımı:", "mascot_design": "Maskot tasarımı:",
"ai_companies_explanation": "Bunu görüyorsunuz; çünkü bu web sitesinin yöneticisi, yapay zekâ şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubisi kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.", "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, istenmeyen 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.", "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.", "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.", "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:", "version_info": "Bu web sitesi şu Anubis sürümünü çalıştırıyor:",
"try_again": "Tekrar dene", "try_again": "Tekrar dene",
"go_home": "Ana sayfaya dön", "go_home": "Ana sayfaya dön",
"contact_webmaster": "veya engellenmemeniz gerektiğini düşünüyorsanız lütfen şu adrese e-posta gönderin:", "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.", "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 JavaScripti etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zekâ şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScriptsiz bir çözüm geliştirilmektedir.", "javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScripti etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zeka şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScriptsiz bir çözüm geliştirilmektedir.",
"benchmark_requires_js": "Kıyaslama aracının çalıştırılması için JavaScriptin etkin olması gereklidir.", "benchmark_requires_js": "Kıyaslama aracının çalıştırılması için JavaScriptin etkin olması gereklidir.",
"difficulty": "Zorluk:", "difficulty": "Zorluk:",
"algorithm": "Algoritma:", "algorithm": "Algoritma:",
@@ -25,19 +25,19 @@
"iters_a": "Tekrar A", "iters_a": "Tekrar A",
"time_b": "Süre B", "time_b": "Süre B",
"iters_b": "Tekrar B", "iters_b": "Tekrar B",
"static_check_endpoint": "Bu sadece, ters vekil sunucunuzun kullanması için bir kontrol adresidir.", "static_check_endpoint": "Bu sadece ters proxy'nizin kullanması için bir kontrol adresidir.",
"authorization_required": "Yetkilendirme gerekli", "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.", "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", "access_denied": "Erişim Reddedildi: hata kodu",
"dronebl_entry": "DroneBL bir giriş bildirdi", "dronebl_entry": "DroneBL bir giriş bildirdi",
"see_dronebl_lookup": "bakınız", "see_dronebl_lookup": "bakınız",
"internal_server_error": "Sunucu Hatası: Yönetici Anubisi yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şu civardaki kayıtlara bakmasını isteyin:", "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", "invalid_redirect": "Geçersiz yönlendirme",
"redirect_not_parseable": "Yönlendirme URLsi çözümlenemiyor", "redirect_not_parseable": "Yönlendirme URL'si çözümlenemiyor",
"redirect_domain_not_allowed": "Yönlendirme alan adına izin verilmiyor", "redirect_domain_not_allowed": "Yönlendirme alan adına izin verilmiyor",
"failed_to_sign_jwt": "JWT imzalanamadı", "failed_to_sign_jwt": "JWT imzalanamadı",
"invalid_invocation": "Geçersiz MakeChallenge çağrısı", "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.", "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!", "oh_noes": "Ah hayır!",
"benchmarking_anubis": "Anubis kıyaslanıyor!", "benchmarking_anubis": "Anubis kıyaslanıyor!",
"you_are_not_a_bot": "Bot değilsiniz!", "you_are_not_a_bot": "Bot değilsiniz!",
@@ -61,4 +61,4 @@
"js_finished_reading": "Okumayı bitirdim, devam et →", "js_finished_reading": "Okumayı bitirdim, devam et →",
"js_calculation_error": "Hesaplama hatası!", "js_calculation_error": "Hesaplama hatası!",
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:" "js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:"
} }