Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions locales/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ actions.stats:
🔍 Analyzed lyrics <code>%{lyrics_analyzed}</code> times
🙈 You ignored <code>%{ignored}</code> track lyrics
🤬 <code>%{lyrics_profane}</code> lyrics were considered profane
💩 <code>%{ai_slop_total}</code> tracks were detected as AI Slop

<blockquote expandable><b>Languages stats:</b>
%{languages}</blockquote>
Expand All @@ -32,6 +33,7 @@ actions.stats:
🔍 Проанализировано текстов <code>%{lyrics_analyzed}</code> раз
🙈 Вы проигнорировали <code>%{ignored}</code> текстов треков
🤬 <code>%{lyrics_profane}</code> текстов были признаны нецензурными
💩 <code>%{ai_slop_total}</code> треков были определены как ИИ шлак

<blockquote expandable><b>Статистика по языкам (названия на английском):</b>
%{languages}</blockquote>
Expand Down
5 changes: 5 additions & 0 deletions migrations/20260308152848_add_ai_slop_detection_counters.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
alter table "user"
add column ai_slop_spotify_ai_blocker bigint not null default 0,
add column ai_slop_soul_over_ai bigint not null default 0,
add column ai_slop_shlabs bigint not null default 0,
add column ai_slop_human_made bigint not null default 0;
36 changes: 36 additions & 0 deletions src/entity/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,38 @@ pub struct Model {
pub name: String,
pub locale: Locale,
pub role: Role,

pub removed_playlists: i64,
pub removed_collection: i64,

pub lyrics_checked: i64,
pub lyrics_analyzed: i64,
pub lyrics_genius: i64,
pub lyrics_musixmatch: i64,
#[sea_orm(enum_name = "LyricsLrcLib")]
pub lyrics_lrclib: i64,
pub lyrics_profane: i64,

#[sea_orm(enum_name = "AISlopSpotifyAIBlocker")]
pub ai_slop_spotify_ai_blocker: i64,
#[sea_orm(enum_name = "AISlopSoulOverAI")]
pub ai_slop_soul_over_ai: i64,
#[sea_orm(enum_name = "AISlopSHLabs")]
pub ai_slop_shlabs: i64,
#[sea_orm(enum_name = "AISlopHumanMade")]
pub ai_slop_human_made: i64,

pub status: Status,
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,

pub cfg_check_profanity: bool,
pub cfg_skip_tracks: bool,
pub cfg_skippage_secs: i64,
pub cfg_skippage_enabled: bool,
#[sea_orm(enum_name = "CfgAISlopDetection")]
pub cfg_ai_slop_detection: AISlopDetection,

pub magic_playlist: Option<String>,
pub spotify_state: Uuid,
pub ref_code: Option<String>,
Expand Down Expand Up @@ -70,24 +84,38 @@ pub enum Column {
Name,
Locale,
Role,

RemovedPlaylists,
RemovedCollection,

LyricsChecked,
LyricsAnalyzed,
LyricsGenius,
LyricsMusixmatch,
#[sea_orm(column_name = "lyrics_lrclib")]
LyricsLrcLib,
LyricsProfane,

#[sea_orm(column_name = "ai_slop_spotify_ai_blocker")]
AISlopSpotifyAIBlocker,
#[sea_orm(column_name = "ai_slop_soul_over_ai")]
AISlopSoulOverAI,
#[sea_orm(column_name = "ai_slop_shlabs")]
AISlopSHLabs,
#[sea_orm(column_name = "ai_slop_human_made")]
AISlopHumanMade,

Status,
CreatedAt,
UpdatedAt,

CfgCheckProfanity,
CfgSkipTracks,
CfgSkippageSecs,
CfgSkippageEnabled,
#[sea_orm(column_name = "cfg_ai_slop_detection")]
CfgAISlopDetection,

MagicPlaylist,
SpotifyState,
RefCode,
Expand Down Expand Up @@ -115,14 +143,22 @@ impl ColumnTrait for Column {
Self::Name => ColumnType::Text.def(),
Self::Locale => Locale::db_type(),
Self::Role => Role::db_type(),

Self::RemovedPlaylists => ColumnType::BigInteger.def(),
Self::RemovedCollection => ColumnType::BigInteger.def(),

Self::LyricsChecked => ColumnType::BigInteger.def(),
Self::LyricsAnalyzed => ColumnType::BigInteger.def(),
Self::LyricsGenius => ColumnType::BigInteger.def(),
Self::LyricsMusixmatch => ColumnType::BigInteger.def(),
Self::LyricsLrcLib => ColumnType::BigInteger.def(),
Self::LyricsProfane => ColumnType::BigInteger.def(),

Self::AISlopSpotifyAIBlocker => ColumnType::BigInteger.def(),
Self::AISlopSoulOverAI => ColumnType::BigInteger.def(),
Self::AISlopSHLabs => ColumnType::BigInteger.def(),
Self::AISlopHumanMade => ColumnType::BigInteger.def(),

Self::Status => Status::db_type(),
Self::CreatedAt => ColumnType::DateTime.def(),
Self::UpdatedAt => ColumnType::DateTime.def(),
Expand Down
21 changes: 21 additions & 0 deletions src/metrics/influx_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ struct TrackLanguageStats {
language: String,
}

#[derive(InfluxDbWriteable, Debug)]
struct AISlopDetectionStats {
time: Timestamp,
spotify_ai_blocker: u64,
soul_over_ai: u64,
shlabs: u64,
human_made: u64,
}

#[derive(InfluxDbWriteable, Debug)]
struct Uptime {
time: Timestamp,
Expand Down Expand Up @@ -113,6 +122,10 @@ pub async fn collect(client: &InfluxClient, app: &App) -> anyhow::Result<()> {
lyrics_musixmatch,
lyrics_lrclib,
lyrics_analyzed,
ai_slop_spotify_ai_blocker,
ai_slop_soul_over_ai,
ai_slop_shlabs,
ai_slop_human_made,
} = UserService::get_stats(app.db(), None).await?;

let tick_health_status = utils::tick_health().await;
Expand Down Expand Up @@ -154,6 +167,14 @@ pub async fn collect(client: &InfluxClient, app: &App) -> anyhow::Result<()> {
spotify_429: MetricsService::spotify_429_get(&mut redis_conn).await?,
}
.into_query("errors"),
AISlopDetectionStats {
time,
spotify_ai_blocker: ai_slop_spotify_ai_blocker as u64,
soul_over_ai: ai_slop_soul_over_ai as u64,
shlabs: ai_slop_shlabs as u64,
human_made: ai_slop_human_made as u64,
}
.into_query("ai_slop_detection"),
Uptime::new(time).into_query("uptime"),
];

Expand Down
9 changes: 9 additions & 0 deletions src/metrics/prometheus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct PrometheusMetrics {
pub lyrics_found: IntGauge,
pub lyrics_profane: IntGauge,
pub lyrics_source: IntGaugeVec,
pub ai_slop_detection: IntGaugeVec,
pub process_duration: Histogram,
pub process_check_interval_seconds: Gauge,
pub process_users_checked: IntCounter,
Expand Down Expand Up @@ -111,6 +112,14 @@ impl PrometheusClient {
)
.context("Failed to register lyrics_source metric")?,

ai_slop_detection: register_int_gauge_vec_with_registry!(
"ai_slop_detection_total",
"AI Detection Providers",
&["provider"],
registry
)
.context("Failed to register ai_slop_detection metric")?,

process_duration: register_histogram_with_registry!(
"process_duration_seconds",
"Processing duration in seconds",
Expand Down
25 changes: 25 additions & 0 deletions src/metrics/prometheus_collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub async fn collect(client: &PrometheusClient, app: &App) -> anyhow::Result<()>
lyrics_musixmatch,
lyrics_lrclib,
lyrics_analyzed,
ai_slop_spotify_ai_blocker,
ai_slop_soul_over_ai,
ai_slop_shlabs,
ai_slop_human_made,
} = UserService::get_stats(app.db(), None).await?;

let tick_health_status = utils::tick_health().await;
Expand Down Expand Up @@ -87,6 +91,27 @@ pub async fn collect(client: &PrometheusClient, app: &App) -> anyhow::Result<()>
.with_label_values(&["lrclib"])
.set(lyrics_lrclib);

client
.metrics()
.ai_slop_detection
.with_label_values(&["soul_over_ai"])
.set(ai_slop_soul_over_ai);
client
.metrics()
.ai_slop_detection
.with_label_values(&["spotify_ai_blocker"])
.set(ai_slop_spotify_ai_blocker);
client
.metrics()
.ai_slop_detection
.with_label_values(&["shlabs"])
.set(ai_slop_shlabs);
client
.metrics()
.ai_slop_detection
.with_label_values(&["human_made"])
.set(ai_slop_human_made);

client
.metrics()
.ticks
Expand Down
10 changes: 9 additions & 1 deletion src/queue/track_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub async fn consume(data: TrackCheckQueueTask, app: Data<&'static App>) -> anyh
.context("Check lyrics failed")?;

UserService::increase_stats_query(user_state.user_id())
.checked_lyrics(res.profane, res.provider)
.checked_lyrics(res.profane, res.provider.as_ref())
.exec(app.db())
.await?;

Expand Down Expand Up @@ -276,6 +276,14 @@ pub async fn check_ai_slop(
.is_track_ai(&mut app.redis_conn().await?, track)
.await?;

if let Err(err) = UserService::increase_stats_query(state.user_id())
.ai_slop(ai_detection_result.provider.as_ref())
.exec(app.db())
.await
{
tracing::error!(error = ?err, "Failed to increase ai_slop metric");
}

if !ai_detection_result.prediction.is_track_ai() {
return Ok(AISlopCheckResult {
is_ai_slop: false,
Expand Down
51 changes: 49 additions & 2 deletions src/services/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use sea_orm::{

use crate::entity::prelude::*;
use crate::lyrics;
use crate::services::ai_slop_detection;
use crate::utils::Clock;

pub struct UserStatsIncreaseQueryBuilder(UpdateMany<UserEntity>);
Expand Down Expand Up @@ -46,8 +47,7 @@ impl UserStatsIncreaseQueryBuilder {
self
}

#[allow(clippy::needless_pass_by_value)]
pub fn checked_lyrics(mut self, profane: bool, provider: Option<lyrics::Provider>) -> Self {
pub fn checked_lyrics(mut self, profane: bool, provider: Option<&lyrics::Provider>) -> Self {
self.0 = self
.0
.col_expr(
Expand All @@ -71,6 +71,21 @@ impl UserStatsIncreaseQueryBuilder {
self
}

pub fn ai_slop(mut self, provider: Option<&ai_slop_detection::Provider>) -> Self {
let col = match provider {
Some(ai_slop_detection::Provider::SpotifyAIBlocker) => {
UserColumn::AISlopSpotifyAIBlocker
},
Some(ai_slop_detection::Provider::SoulOverAI) => UserColumn::AISlopSoulOverAI,
Some(ai_slop_detection::Provider::SHLabs) => UserColumn::AISlopSHLabs,
None => UserColumn::AISlopHumanMade,
};

self.0 = self.0.col_expr(col, Expr::col(col).add(1));
Comment thread
vtvz marked this conversation as resolved.

self
}

pub fn analyzed_lyrics(mut self) -> Self {
self.0 = self.0.col_expr(
UserColumn::LyricsAnalyzed,
Expand Down Expand Up @@ -99,6 +114,10 @@ pub struct UserStats {
pub lyrics_musixmatch: i64,
pub lyrics_lrclib: i64,
pub lyrics_analyzed: i64,
pub ai_slop_spotify_ai_blocker: i64,
pub ai_slop_soul_over_ai: i64,
pub ai_slop_shlabs: i64,
pub ai_slop_human_made: i64,
}

pub struct UserService;
Expand Down Expand Up @@ -479,6 +498,34 @@ impl UserService {
]),
"lyrics_analyzed",
)
.expr_as(
Func::coalesce([
UserColumn::AISlopSpotifyAIBlocker.sum().cast_as(bigint()),
Expr::val(0).into(),
]),
"ai_slop_spotify_ai_blocker",
)
.expr_as(
Func::coalesce([
UserColumn::AISlopSoulOverAI.sum().cast_as(bigint()),
Expr::val(0).into(),
]),
"ai_slop_soul_over_ai",
)
.expr_as(
Func::coalesce([
UserColumn::AISlopSHLabs.sum().cast_as(bigint()),
Expr::val(0).into(),
]),
"ai_slop_shlabs",
)
.expr_as(
Func::coalesce([
UserColumn::AISlopHumanMade.sum().cast_as(bigint()),
Expr::val(0).into(),
]),
"ai_slop_human_made",
)
.into_model::<UserStats>()
.one(db)
.await?
Expand Down
16 changes: 16 additions & 0 deletions src/telegram/actions/admin_users/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ async fn format_user_details(app: &'static App, user_id: &str) -> anyhow::Result
})
.join("\n");

let ai_slop_total_checks = stats.ai_slop_spotify_ai_blocker
+ stats.ai_slop_soul_over_ai
+ stats.ai_slop_shlabs
+ stats.ai_slop_human_made;

let text = formatdoc!(
r#"
👤 <b>User Details</b>
Expand Down Expand Up @@ -157,6 +162,13 @@ async fn format_user_details(app: &'static App, user_id: &str) -> anyhow::Result
• MusixMatch: <code>{lyrics_musixmatch}</code>
• LrcLib: <code>{lyrics_lrclib}</code>

<b>AI Slop Detection:</b>
• Total <code>{ai_slop_total_checks}</code>
• Soul Over AI <code>{ai_slop_soul_over_ai}</code>
• Spotify AI Blocker <code>{ai_slop_spotify_ai_blocker}</code>
• SHLabs <code>{ai_slop_shlabs}</code>
• Human Made <code>{ai_slop_human_made}</code>

<blockquote expandable><b>Languages stats:</b>
{languages}</blockquote>
"#,
Expand Down Expand Up @@ -189,6 +201,10 @@ async fn format_user_details(app: &'static App, user_id: &str) -> anyhow::Result
lyrics_genius = stats.lyrics_genius,
lyrics_musixmatch = stats.lyrics_musixmatch,
lyrics_lrclib = stats.lyrics_lrclib,
ai_slop_spotify_ai_blocker = stats.ai_slop_spotify_ai_blocker,
ai_slop_soul_over_ai = stats.ai_slop_soul_over_ai,
ai_slop_shlabs = stats.ai_slop_shlabs,
ai_slop_human_made = stats.ai_slop_human_made,
);

Ok(text)
Expand Down
Loading
Loading