audio streaming app plyr.fm

fix: stop DMing users about copyright flags (#941)

copyright scan notifications were DMing both the artist and admin
on every flag. removed the notification block from _store_scan_result,
the send_copyright_notification method, and the dead _emit_copyright_label
function.

scans still run and store results — they just don't notify anyone.
admin DMs for new tracks, flagged images, and user reports are unchanged.

refs #702

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

authored by zzstoatzz.io

Claude Opus 4.6 and committed by
GitHub
b0ca5c10 bda1479a

+1 -169
+1 -57
backend/src/backend/_internal/moderation.py
··· 1 1 """moderation service integration for copyright scanning.""" 2 2 3 3 import logging 4 - from datetime import UTC, datetime 5 4 from typing import Any 6 5 7 6 import logfire 8 - from sqlalchemy import select 9 - from sqlalchemy.orm import joinedload 10 7 11 8 from backend._internal.clients.moderation import get_moderation_client 12 - from backend._internal.notifications import notification_service 13 9 from backend.config import settings 14 - from backend.models import CopyrightScan, Track 10 + from backend.models import CopyrightScan 15 11 from backend.utilities.database import db_session 16 12 17 13 logger = logging.getLogger(__name__) ··· 81 77 highest_score=scan.highest_score, 82 78 match_count=len(scan.matches), 83 79 ) 84 - 85 - # send notification if flagged (see #702) 86 - if result.is_flagged: 87 - track = await db.scalar( 88 - select(Track) 89 - .options(joinedload(Track.artist)) 90 - .where(Track.id == track_id) 91 - ) 92 - if track and track.artist: 93 - ( 94 - artist_result, 95 - admin_result, 96 - ) = await notification_service.send_copyright_notification( 97 - track_id=track_id, 98 - track_title=track.title, 99 - artist_did=track.artist_did, 100 - artist_handle=track.artist.handle, 101 - highest_score=scan.highest_score, 102 - matches=scan.matches, 103 - ) 104 - # mark as notified if at least one succeeded 105 - if (artist_result and artist_result.success) or ( 106 - admin_result and admin_result.success 107 - ): 108 - scan.notified_at = datetime.now(UTC) 109 - await db.commit() 110 - 111 - 112 - async def _emit_copyright_label( 113 - uri: str, 114 - cid: str | None, 115 - track_id: int | None = None, 116 - track_title: str | None = None, 117 - artist_handle: str | None = None, 118 - artist_did: str | None = None, 119 - highest_score: int | None = None, 120 - matches: list[dict[str, Any]] | None = None, 121 - ) -> None: 122 - """emit a copyright-violation label to the ATProto labeler service.""" 123 - context: dict[str, Any] | None = None 124 - if track_id or track_title or artist_handle or matches: 125 - context = { 126 - "track_id": track_id, 127 - "track_title": track_title, 128 - "artist_handle": artist_handle, 129 - "artist_did": artist_did, 130 - "highest_score": highest_score, 131 - "matches": matches, 132 - } 133 - 134 - client = get_moderation_client() 135 - await client.emit_label(uri=uri, cid=cid, context=context) 136 80 137 81 138 82 async def _store_scan_error(track_id: int, error: str) -> None:
-112
backend/src/backend/_internal/notifications.py
··· 153 153 error_type=error_type, 154 154 ) 155 155 156 - async def send_copyright_notification( 157 - self, 158 - track_id: int, 159 - track_title: str, 160 - artist_did: str, 161 - artist_handle: str, 162 - highest_score: int, 163 - matches: list[dict], 164 - ) -> tuple[NotificationResult | None, NotificationResult | None]: 165 - """send notification about a copyright flag to both artist and admin. 166 - 167 - returns (artist_result, admin_result) tuple with details of each attempt. 168 - """ 169 - with logfire.span( 170 - "copyright_notification", 171 - track_id=track_id, 172 - track_title=track_title, 173 - artist_did=artist_did, 174 - artist_handle=artist_handle, 175 - highest_score=highest_score, 176 - match_count=len(matches), 177 - ) as span: 178 - if not self.dm_client: 179 - logfire.warn("dm client not authenticated, skipping notification") 180 - return None, None 181 - 182 - # format match info 183 - match_count = len(matches) 184 - primary_match = None 185 - if matches: 186 - m = matches[0] 187 - primary_match = ( 188 - f"{m.get('title', 'Unknown')} by {m.get('artist', 'Unknown')}" 189 - ) 190 - 191 - # build track URL if available 192 - track_url = None 193 - frontend_url = settings.frontend.url 194 - if frontend_url and "localhost" not in frontend_url: 195 - track_url = f"{frontend_url}/track/{track_id}" 196 - 197 - # message for the artist (uploader) 198 - artist_message = ( 199 - f"⚠️ copyright notice for your track on {settings.app.name}\n\n" 200 - f"track: '{track_title}'\n" 201 - ) 202 - if primary_match: 203 - artist_message += f"potential match: {primary_match}\n" 204 - artist_message += ( 205 - "\nif you believe this is an error, please reply to this message. " 206 - "otherwise, the track may be removed after review." 207 - ) 208 - 209 - # message for admin 210 - admin_message = ( 211 - f"🚨 copyright flag on {settings.app.name}\n\n" 212 - f"track: '{track_title}'\n" 213 - f"artist: @{artist_handle}\n" 214 - f"matches: {match_count}\n" 215 - ) 216 - if primary_match: 217 - admin_message += f"primary: {primary_match}\n" 218 - if track_url: 219 - admin_message += f"\n{track_url}" 220 - 221 - # send to artist 222 - artist_result = await self._send_dm_to_did(artist_did, artist_message) 223 - span.set_attribute("artist_success", artist_result.success) 224 - if not artist_result.success: 225 - span.set_attribute("artist_error_type", artist_result.error_type) 226 - logfire.warn( 227 - "failed to notify artist", 228 - artist_handle=artist_handle, 229 - error_type=artist_result.error_type, 230 - error=artist_result.error, 231 - ) 232 - 233 - # send to admin 234 - admin_result = None 235 - if self.recipient_did: 236 - admin_result = await self._send_dm_to_did( 237 - self.recipient_did, admin_message 238 - ) 239 - span.set_attribute("admin_success", admin_result.success) 240 - if not admin_result.success: 241 - span.set_attribute("admin_error_type", admin_result.error_type) 242 - logfire.warn( 243 - "failed to notify admin", 244 - error_type=admin_result.error_type, 245 - error=admin_result.error, 246 - ) 247 - 248 - # summary 249 - any_success = artist_result.success or ( 250 - admin_result and admin_result.success 251 - ) 252 - span.set_attribute("any_success", any_success) 253 - 254 - if artist_result.success: 255 - logfire.info( 256 - "sent copyright notification to artist", 257 - artist_handle=artist_handle, 258 - track_id=track_id, 259 - ) 260 - if admin_result and admin_result.success: 261 - logfire.info( 262 - "sent copyright notification to admin", 263 - track_id=track_id, 264 - ) 265 - 266 - return artist_result, admin_result 267 - 268 156 async def send_image_flag_notification( 269 157 self, 270 158 image_id: str,