+4
moderation/src/db.rs
+4
moderation/src/db.rs
···
95
FingerprintNoise,
96
/// Legal cover version or remix
97
CoverVersion,
98
/// Other reason (see resolution_notes)
99
Other,
100
}
···
107
Self::Licensed => "licensed",
108
Self::FingerprintNoise => "fingerprint noise",
109
Self::CoverVersion => "cover/remix",
110
Self::Other => "other",
111
}
112
}
···
118
"licensed" => Some(Self::Licensed),
119
"fingerprint_noise" => Some(Self::FingerprintNoise),
120
"cover_version" => Some(Self::CoverVersion),
121
"other" => Some(Self::Other),
122
_ => None,
123
}
···
95
FingerprintNoise,
96
/// Legal cover version or remix
97
CoverVersion,
98
+
/// Content was deleted from plyr.fm
99
+
ContentDeleted,
100
/// Other reason (see resolution_notes)
101
Other,
102
}
···
109
Self::Licensed => "licensed",
110
Self::FingerprintNoise => "fingerprint noise",
111
Self::CoverVersion => "cover/remix",
112
+
Self::ContentDeleted => "content deleted",
113
Self::Other => "other",
114
}
115
}
···
121
"licensed" => Some(Self::Licensed),
122
"fingerprint_noise" => Some(Self::FingerprintNoise),
123
"cover_version" => Some(Self::CoverVersion),
124
+
"content_deleted" => Some(Self::ContentDeleted),
125
"other" => Some(Self::Other),
126
_ => None,
127
}
+66
-1
scripts/moderation_loop.py
+66
-1
scripts/moderation_loop.py
···
116
)
117
118
119
@dataclass
120
class ModClient:
121
base_url: str
···
203
204
dm = DMClient(settings.bot_handle, settings.bot_password, settings.recipient_handle)
205
mod = ModClient(settings.moderation_service_url, settings.moderation_auth_token)
206
207
try:
208
await dm.setup()
···
215
216
console.print(f"[bold]{len(pending)} pending flags[/bold]")
217
218
-
# analyze flags
219
if limit:
220
pending = pending[:limit]
221
···
268
269
finally:
270
await mod.close()
271
272
273
def main() -> None:
···
116
)
117
118
119
+
@dataclass
120
+
class PlyrClient:
121
+
"""client for checking track existence in plyr.fm."""
122
+
123
+
env: str = "prod"
124
+
_client: httpx.AsyncClient = field(init=False, repr=False)
125
+
126
+
def __post_init__(self) -> None:
127
+
base_url = {
128
+
"prod": "https://api.plyr.fm",
129
+
"staging": "https://api-stg.plyr.fm",
130
+
"dev": "http://localhost:8001",
131
+
}.get(self.env, "https://api.plyr.fm")
132
+
self._client = httpx.AsyncClient(base_url=base_url, timeout=10.0)
133
+
134
+
async def close(self) -> None:
135
+
await self._client.aclose()
136
+
137
+
async def track_exists(self, track_id: int) -> bool:
138
+
"""check if a track exists (returns False if 404)."""
139
+
try:
140
+
r = await self._client.get(f"/tracks/{track_id}")
141
+
return r.status_code == 200
142
+
except Exception:
143
+
return True # assume exists on error (don't accidentally delete labels)
144
+
145
+
146
@dataclass
147
class ModClient:
148
base_url: str
···
230
231
dm = DMClient(settings.bot_handle, settings.bot_password, settings.recipient_handle)
232
mod = ModClient(settings.moderation_service_url, settings.moderation_auth_token)
233
+
plyr = PlyrClient(env=env)
234
235
try:
236
await dm.setup()
···
243
244
console.print(f"[bold]{len(pending)} pending flags[/bold]")
245
246
+
# check for deleted tracks and auto-resolve them
247
+
console.print("[dim]checking for deleted tracks...[/dim]")
248
+
active_flags = []
249
+
deleted_count = 0
250
+
for flag in pending:
251
+
track_id = flag.get("context", {}).get("track_id")
252
+
if track_id and not await plyr.track_exists(track_id):
253
+
# track was deleted - resolve the flag
254
+
if not dry_run:
255
+
try:
256
+
await mod.resolve(
257
+
flag["uri"], "content_deleted", "track no longer exists"
258
+
)
259
+
console.print(
260
+
f" [yellow]⌫[/yellow] deleted: {flag['uri'][-40:]}"
261
+
)
262
+
deleted_count += 1
263
+
except Exception as e:
264
+
console.print(f" [red]✗[/red] {e}")
265
+
active_flags.append(flag)
266
+
else:
267
+
console.print(
268
+
f" [yellow]would resolve deleted:[/yellow] {flag['uri'][-40:]}"
269
+
)
270
+
deleted_count += 1
271
+
else:
272
+
active_flags.append(flag)
273
+
274
+
if deleted_count > 0:
275
+
console.print(f"[yellow]{deleted_count} deleted tracks resolved[/yellow]")
276
+
277
+
pending = active_flags
278
+
if not pending:
279
+
console.print("[green]all flags were for deleted tracks[/green]")
280
+
return
281
+
282
+
# analyze remaining flags
283
if limit:
284
pending = pending[:limit]
285
···
332
333
finally:
334
await mod.close()
335
+
await plyr.close()
336
337
338
def main() -> None: