webclient / app /services /youtube_moderator.py
ariansyahdedy's picture
fix keep comment
27e17be
import re
import time
import os
import logging
from typing import List, Dict, Optional, Set, Tuple
import google_auth_oauthlib.flow
import googleapiclient.discovery
import googleapiclient.errors
from google_auth_oauthlib.flow import Flow
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from fastapi import FastAPI, Request, Form, File, UploadFile, HTTPException, Depends
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.security import OAuth2PasswordBearer
from google.oauth2.credentials import Credentials
from pydantic import BaseModel
from app.services.gambling_filter import GamblingFilter
manual_overrides = {}
def keep_comment(comment_id: str, video_id: str):
# Mark this comment as manually kept
manual_overrides[(video_id, comment_id)] = "safe"
def get_credentials_from_session(session) -> Credentials:
"""Utility to build a Credentials object from stored session data."""
creds_data = session.get("credentials")
if not creds_data:
return None
return Credentials(
token=creds_data["token"],
refresh_token=creds_data["refresh_token"],
token_uri=creds_data["token_uri"],
client_id=creds_data["client_id"],
client_secret=creds_data["client_secret"],
scopes=creds_data["scopes"]
)
class YouTubeCommentModerator:
def __init__(self,
client_secrets_path: str = "./app/client_secret.json",
gambling_filter: Optional[GamblingFilter] = None):
"""
Initialize the YouTube Comment Moderator with configurable settings.
:param client_secrets_path: Path to OAuth 2.0 client secrets file
:param gambling_filter: Optional pre-configured GamblingFilter instance
"""
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
self.logger = logging.getLogger(__name__)
# YouTube service
self.youtube_service = None
# Gambling Filter
self.gambling_filter = gambling_filter or GamblingFilter()
def moderate_video_comments(self, video_id: str, threshold: float = 0.55) -> Dict:
if not self.youtube_service:
self.logger.error("YouTube service not authenticated.")
return {"error": "Not authenticated"}
try:
comments = []
request = self.youtube_service.commentThreads().list(
part="snippet",
videoId=video_id,
maxResults=100,
textFormat="plainText"
)
response = request.execute()
moderation_results = {
"total_comments": 0,
"gambling_comments": [],
"safe_comments": [],
"moderation_metrics": []
}
while request is not None:
for item in response.get("items", []):
comment_id = item["snippet"]["topLevelComment"]["id"]
comment_snippet = item["snippet"]["topLevelComment"]["snippet"]
comment_text = comment_snippet["textDisplay"]
# Check for manual override first
if manual_overrides.get((video_id, comment_id)) == "safe":
# The user previously pressed "Keep" - skip the gambling filter
is_gambling = False
metrics = {"confidence_score": 0.0}
else:
# Normal path - filter it
is_gambling, metrics = self.gambling_filter.is_gambling_comment(comment_text, threshold)
comment_info = {
"id": comment_id,
"text": comment_text,
"author": comment_snippet["authorDisplayName"],
"is_gambling": is_gambling,
"metrics": metrics
}
moderation_results["total_comments"] += 1
if is_gambling:
moderation_results["gambling_comments"].append(comment_info)
else:
moderation_results["safe_comments"].append(comment_info)
metrics["original_text"] = comment_text
moderation_results["moderation_metrics"].append(metrics)
# Handle pagination if available
request = self.youtube_service.commentThreads().list_next(request, response)
if request:
response = request.execute()
else:
break
return moderation_results
except Exception as e:
self.logger.error(f"Error moderating comments: {e}")
return {"error": str(e)}
def delete_comment(self, comment_id: str) -> bool:
"""
Delete a specific comment.
:param comment_id: YouTube comment ID
:return: Boolean indicating successful deletion
"""
try:
# self.youtube_service.comments().delete(id=comment_id).execute()
self.youtube_service.comments().setModerationStatus(
id=comment_id,
moderationStatus="rejected"
).execute()
self.logger.info(f"Comment {comment_id} deleted successfully.")
return True
except Exception as e:
self.logger.error(f"Failed to delete comment {comment_id}: {e}")
return False
def get_channel_videos(self, max_results: int = 50) -> List[Dict]:
"""
Retrieve videos from authenticated user's channel.
:param max_results: Maximum number of videos to retrieve
:return: List of video details
"""
if not self.youtube_service:
self.logger.error("YouTube service not authenticated.")
return []
try:
request = self.youtube_service.search().list(
part="snippet",
channelId=self._get_channel_id(),
maxResults=max_results,
type="video"
)
response = request.execute()
videos = []
for item in response.get("items", []):
video_info = {
"id": item["id"]["videoId"],
"title": item["snippet"]["title"],
"thumbnail": item["snippet"]["thumbnails"]["default"]["url"]
}
videos.append(video_info)
return videos
except Exception as e:
self.logger.error(f"Error retrieving videos: {e}")
return []
def _get_channel_id(self) -> Optional[str]:
"""
Retrieve the authenticated user's channel ID.
:return: Channel ID or None
"""
try:
request = self.youtube_service.channels().list(part="id", mine=True)
response = request.execute()
return response["items"][0]["id"]
except Exception as e:
self.logger.error(f"Error retrieving channel ID: {e}")
return None