doom and eyes fixes

This commit is contained in:
2026-05-11 15:12:26 +07:00
parent 197f0419d7
commit 262806eb55
18 changed files with 24861 additions and 253 deletions
+25 -9
View File
@@ -36,7 +36,6 @@ impl ToxicityClassifier {
let seq_len = ids.len();
// В tract .into_shape() возвращает Tensor напрямую, unwrap не нужен
let ids_tensor = tensor1(&ids)
.into_shape(&[1, seq_len])
.expect("Shape error");
@@ -56,20 +55,37 @@ impl ToxicityClassifier {
let outputs = match result {
Ok(out) => out,
Err(e) => {
println!("AI Link Error: {:?}", e);
println!("💥 [MODEL ERR]: {:?}", e);
return false;
}
};
// Извлекаем результат
let logits = match outputs[0].to_array_view::<f32>() {
Ok(view) => view,
// --- БРОНЕБОЙНОЕ РЕШЕНИЕ (Никаких паник ndarray) ---
let view = match outputs[0].to_array_view::<f32>() {
Ok(v) => v,
Err(_) => return false,
};
logits.iter().any(|&x| {
let prob = 1.0 / (1.0 + (-x).exp());
prob > 0.8
})
// Превращаем любую матрицу (хоть 2D, хоть 3D) в плоский список чисел
let logits: Vec<f32> = view.iter().copied().collect();
// Теперь logits это просто одномерный массив, например [-2.5, 4.8]
let toxic_logit = if logits.len() >= 2 {
logits[1] // Вероятность мата
} else if logits.len() == 1 {
logits[0]
} else {
return false;
};
// Применяем сигмоиду
let prob = 1.0 / (1.0 + (-toxic_logit).exp());
println!(
"🤖 [AI DEBUG] Text: '{}' | Logits: {:?} | Prob: {:.4}",
text, logits, prob
);
prob > 0.85 // Порог 85%
}
}
+67 -140
View File
@@ -6,9 +6,7 @@ use axum::{
routing::{get, post},
Json, Router,
};
use regex::Regex;
use reqwest::Client;
use rustrict::CensorStr;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, LazyLock};
@@ -21,59 +19,7 @@ struct CommentPayload {
message: String,
}
static BAD_WORDS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"(?i)(хуй|хуя|хуе|хул|пизд|ебат|ебан|ебл|ебуч|бляд|блят|шлюх|сука|сучк|жоп|пидор|пидар|педик|голубо|гондон|гандон|залуп|мудак|мудил|мраз|уеб|чмо|долбое|даун|чурк|нигер|нигга|хач|хохол|кацап|жид|порно|наркот|убить|насилие|соли|закладк|мефедрон|гашиш|спайс|heroin|meth|murder|gore|cpam|scam|crypto|binance|bitco)").unwrap()
});
const SAFE_WORDS: &[&str] = &[
"колебат",
"оскорблят",
"употреблят",
"расслаблят",
"потреблят",
"истреблят",
"хлебат",
"щебет",
"мебел",
"стебл",
"ослабл",
"гребл",
"хлебл",
"дебел",
"колебл",
"страхуй",
"подстрахуй",
"рубл",
"сабл",
"челяб",
"влюбл",
"ограбл",
"дубл",
"гибл",
"корабл",
"джоп",
"джобс",
"прожора",
"скипидар",
"купидор",
"коридор",
"чмок",
"ячмен",
"черемух",
"мразер",
"замороз",
"мороз",
"мрамор",
"рисука",
"присука",
"бамбука",
"ожид",
"жидкос",
"жидк",
"даунгрейд",
"адаун",
];
// Восстановленный генератор нагрузки для спидтеста (25 MB)
static RANDOM_PAYLOAD: LazyLock<Bytes> = LazyLock::new(|| {
let mut data = vec![0u8; 25 * 1024 * 1024];
let mut state: u32 = 123456789;
@@ -84,75 +30,13 @@ static RANDOM_PAYLOAD: LazyLock<Bytes> = LazyLock::new(|| {
Bytes::from(data)
});
fn apply_allowlist(text: &str) -> String {
let mut safe_text = text.to_string();
for &safe_word in SAFE_WORDS {
safe_text = safe_text.replace(safe_word, "[SAFE]");
}
safe_text
}
fn normalize_for_filter(input: &str) -> String {
let mut normalized = String::new();
let mut prev_char: Option<char> = None;
for ch in input.to_lowercase().chars() {
if ch.is_whitespace() {
normalized.push(' ');
prev_char = Some(' ');
continue;
}
if !ch.is_alphanumeric() {
continue;
}
let mapped = match ch {
'a' | '@' => 'а',
'b' => 'в',
'c' => 'с',
'e' | '3' => 'е',
'o' | '0' => 'о',
'p' => 'р',
'x' => 'х',
'y' => 'у',
'm' => 'м',
't' => 'т',
'k' => 'к',
'h' => 'н',
'4' => 'ч',
_ => ch,
};
if let Some(p) = prev_char {
if p == mapped && mapped != ' ' {
continue;
}
}
normalized.push(mapped);
prev_char = Some(mapped);
}
normalized
}
fn is_spam(input: &str) -> bool {
let alpha_chars: String = input.chars().filter(|c| c.is_alphabetic()).collect();
if alpha_chars.is_empty() {
return false;
}
let uppercase_ratio =
alpha_chars.chars().filter(|c| c.is_uppercase()).count() as f64 / alpha_chars.len() as f64;
if input.len() > 10 && uppercase_ratio > 0.7 {
return true;
}
input
.split_whitespace()
.any(|word| word.len() > 35 && !word.starts_with("http"))
}
struct AppState {
ai_classifier: ToxicityClassifier,
}
#[tokio::main]
async fn main() {
println!("🧠 Booting AI Neural Link (Pure Rust Engine)...");
println!("🧠 [SYSTEM] Booting AI Neural Link (Pure Rust Engine)...");
let classifier = ToxicityClassifier::new("models/model.onnx", "models/tokenizer.json")
.expect("CRITICAL: Failed to load Neural Link model");
@@ -168,7 +52,7 @@ async fn main() {
.layer(DefaultBodyLimit::max(100 * 1024 * 1024))
.with_state(state);
println!("🚀 Netrunner Gateway started on 0.0.0.0:3001");
println!("🚀 [READY] Netrunner Gateway started on 0.0.0.0:3001");
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
@@ -177,48 +61,90 @@ async fn create_comment(
State(state): State<Arc<AppState>>,
Json(payload): Json<CommentPayload>,
) -> impl IntoResponse {
if is_spam(&payload.message) || is_spam(&payload.nickname) {
return (StatusCode::NOT_ACCEPTABLE, "Spam detected.");
}
if payload.message.is_inappropriate() || payload.nickname.is_inappropriate() {
return (StatusCode::NOT_ACCEPTABLE, "Policy violation.");
}
let message = payload.message.trim().to_string();
let nickname = payload.nickname.trim().to_string();
let normalized_msg = normalize_for_filter(&payload.message);
let normalized_nick = normalize_for_filter(&payload.nickname);
if BAD_WORDS_REGEX.is_match(&apply_allowlist(&normalized_msg))
|| BAD_WORDS_REGEX.is_match(&apply_allowlist(&normalized_nick))
{
return (StatusCode::NOT_ACCEPTABLE, "Policy violation.");
}
println!("📩 [INCOMING] '{}': '{}'", nickname, message);
let message = payload.message.clone();
let nickname = payload.nickname.clone();
// --- ГВАРД-РЕЙЛЫ (Только для текста сообщения) ---
let whitelist = [
"vpn",
"service",
"experience",
"use",
"working",
"fast",
"thanks",
"привет",
"спасибо",
"работает",
"test",
"тест",
];
let msg_lower = message.to_lowercase();
let msg_whitelisted = whitelist.iter().any(|&word| msg_lower.contains(word));
let msg_has_alphabetic = message.chars().any(|c| c.is_alphabetic());
let msg_safe_by_guard = msg_whitelisted || message.len() < 4 || !msg_has_alphabetic;
// Запускаем проверку
let is_toxic = tokio::task::spawn_blocking(move || {
state.ai_classifier.is_toxic(&message) || state.ai_classifier.is_toxic(&nickname)
// 1. ВСЕГДА проверяем никнейм
let is_nick_toxic = state.ai_classifier.is_toxic(&nickname);
// 2. Проверяем сообщение, если оно не попало под гвард-рейлы
let is_msg_toxic = if msg_safe_by_guard {
println!(
"🛡️ [GUARD RAILS] Message text bypassed (whitelisted, short, or non-alphabetic)."
);
false
} else {
state.ai_classifier.is_toxic(&message)
};
is_nick_toxic || is_msg_toxic
})
.await
.unwrap_or(false);
if is_toxic {
println!(
"🚫 [REJECTED] Blocked by AI: nick='{}', msg='{}'",
payload.nickname, payload.message
);
return (StatusCode::NOT_ACCEPTABLE, "Behavioral policy violation.");
}
println!("✅ [PASSED] Forwarding to PocketBase...");
let pb_url = format!(
"{}/api/collections/comments/records",
std::env::var("PB_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8090".to_string())
std::env::var("PB_INTERNAL_URL")
.unwrap_or_else(|_| "http://netrunner-cms:8090".to_string())
);
let client = Client::new();
match client.post(pb_url).json(&payload).send().await {
Ok(res) if res.status().is_success() => (StatusCode::OK, "Delivered."),
_ => (StatusCode::BAD_REQUEST, "Uplink offline."),
match client.post(&pb_url).json(&payload).send().await {
Ok(res) => {
if res.status().is_success() {
println!("🎉 [SUCCESS] Saved to PocketBase");
(StatusCode::OK, "Delivered.")
} else {
println!("❌ [ERROR] PocketBase rejected: {}", res.status());
(StatusCode::BAD_REQUEST, "Uplink rejected.")
}
}
Err(e) => {
println!("💥 [CRITICAL] PocketBase unreachable: {}", e);
(StatusCode::SERVICE_UNAVAILABLE, "Uplink offline.")
}
}
}
async fn speedtest_download() -> impl IntoResponse {
println!("📊 [SPEEDTEST] Download requested");
let body = RANDOM_PAYLOAD.clone();
let len = body.len();
(
[
(
@@ -237,5 +163,6 @@ async fn speedtest_download() -> impl IntoResponse {
}
async fn speedtest_upload(body: Bytes) -> impl IntoResponse {
println!("📊 [SPEEDTEST] Upload received: {} bytes", body.len());
(StatusCode::OK, format!("Received {} bytes", body.len()))
}