Files

250 lines
7.7 KiB
Python
Raw Permalink Normal View History

"""
scripts/resolve_youtube_verified_oac.py
Resolves the remaining unresolved YouTube channels using a curated canonical
handle dictionary and enforces a strict database safety floor.
"""
import json
import re
import sys
import time
from pathlib import Path
import requests
ROOT_DIR = Path(__file__).resolve().parent.parent
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from core.database import get_connection
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
# Curated handles for artists where guessing @artistname fails
CANONICAL_HANDLES = {
"Pusha T": "kingpush",
"Flatbush Zombies": "FlatbushZombiesTV",
"Larry June": "LarryJuneTFM",
"Curren$y": "CurrensyJetLife",
"Jeezy": "JeezyOfficial",
"Smoke DZA": "SmokeDZA",
"Jay-Z": "JayZVEVO",
"Juvenile": "JuvenileVEVO",
"Bun B": "BunBVEVO",
"Young Buck": "YoungBuckVEVO",
"Twista": "TwistaVEVO",
"Cam'ron": "CamronVEVO",
"Styles P": "StylesPVEVO",
"Chingy": "ChingyVEVO",
"Kool G Rap": "KoolGRapOfficial",
"KRS-One": "KRSOneOfficial",
"The Roots": "TheRootsVEVO",
"Inspectah Deck": "InspectahDeckVEVO",
"Juicy J": "JuicyJVEVO",
"Schoolboy Q": "SchoolboyQVEVO",
"Ab-Soul": "TopDawgEnt",
"Quavo": "QuavoOfficial",
"Slim Jxmmi": "RaeSremmurd",
"Rich Homie Quan": "RichHomieQuanVEVO",
"Key Glock": "KeyGlockVEVO",
"Kevin Gates": "kevingatesTV",
"Boosie Badazz": "BoosieBadazzVEVO",
"King Von": "KingVon",
"Trippie Redd": "TrippieRedd",
"Ski Mask the Slump God": "SkiMaskTheSlumpGodVEVO",
"Cordae": "cordae",
"Mick Jenkins": "MickJenkinsVEVO",
"Benny The Butcher": "BennyTheButcherVEVO",
"Boldy James": "BoldyJamesVEVO",
"Roc Marciano": "RocMarciano",
"Maxo Kream": "MaxoKreamVEVO",
"Big K.R.I.T.": "BigKRITVEVO",
"Jay Worthy": "JayWorthyVEVO",
"Veeze": "VeezeOfficial",
"YN Jay": "YNJay",
"Fredo Santana": "FredoSantana300",
"Jay Critch": "JayCritchVEVO",
"Saweetie": "OfficialSaweetie",
"Yung Miami": "CityGirlsVEVO",
"Tierra Whack": "TierraWhack",
"Dizzee Rascal": "DizzeeRascalVEVO",
"Digga D": "DiggaD",
"K-Trap": "KTrapOfficial",
"M Huncho": "MHunchoOfficial",
"Nines": "NinesVEVO",
"Potter Payper": "PotterPayperOfficial",
"Rimzee": "RimzeeOfficial",
"MoStack": "MoStackVEVO",
"Stefflon Don": "StefflonDonVEVO",
"Summrs": "SummrsArchive",
"BabyTron": "BabyTronOfficial",
"Luh Tyler": "LuhTylerVEVO",
"Rob49": "Rob49VEVO",
"Hit-Boy": "HitBoyVEVO",
"Lil Skies": "LilSkies",
"Smokepurpp": "SmokepurppVEVO",
"Lil Pump": "LilPump",
"Chris Travis": "ChrisTravisVEVO",
"Yung Lean": "YungLeanVEVO",
"Bladee": "DrainGang",
"Ecco2k": "DrainGang",
"Thaiboy Digital": "DrainGang",
"BBNO$": "bbnomula",
"Jhené Aiko": "JheneAiko",
"Tinashe": "TinasheOfficial",
"Brent Faiyaz": "BrentFaiyaz",
"Lucky Daye": "LuckyDayeVEVO",
"Meechy Darko": "MeechyDarkoVEVO",
"NoCap": "NoCap16_29",
"Rylo Rodriguez": "RyloRodriguezVEVO",
"Lil Baby": "LilBabyVEVO",
"EARTHGANG": "EarthgangVEVO",
"Mobb Deep": "MobbDeepVEVO",
"David Banner": "DavidBannerVEVO",
"Lil Keke": "LilKekeVEVO",
"Reason": "TopDawgEnt",
"Foolio": "JulioFoolio",
"Chika": "ChikaOfficial",
"Fredo": "FredoOfficial",
"AG Club": "AGClubOfficial",
"Pouya": "PouyaKevin",
"BONES": "TeamSESH",
"Erick the Architect": "ErickTheArchitect",
"Shy Glizzy": "ShyGlizzyVEVO",
"Autumn!": "Twinuzis",
"SpaceGhostPurrp": "SpaceGhostPurrp",
"Mos Def": "MosDefVEVO",
"SiR": "TopDawgEnt",
"Homixide Gang": "HomixideGang",
"BabySmoove": "BabySmooveVEVO",
}
def parse_metric(raw_text):
if not raw_text:
return 0
t = str(raw_text).upper().replace(",", "").strip()
match = re.search(r"([\d\.]+)\s*([KMB])?", t)
if not match:
return 0
val = float(match.group(1))
suffix = match.group(2)
if suffix == "K":
val *= 1e3
elif suffix == "M":
val *= 1e6
elif suffix == "B":
val *= 1e9
return int(val)
def extract_subscribers_from_html(html_text):
match = re.search(r"var ytInitialData\s*=\s*({.+?});</script>", html_text)
if not match:
match = re.search(r"window\[\"ytInitialData\"\]\s*=\s*({.+?});", html_text)
if match:
try:
data = json.loads(match.group(1))
header = data.get("header", {})
c4 = header.get("c4TabbedHeaderRenderer", {})
sub_text = c4.get("subscriberCountText", {}).get("simpleText", "")
if not sub_text:
runs = c4.get("subscriberCountText", {}).get("runs", [])
sub_text = "".join(r.get("text", "") for r in runs)
if sub_text:
return parse_metric(sub_text)
vm = (
header.get("pageHeaderRenderer", {})
.get("content", {})
.get("pageHeaderViewModel", {})
.get("metadata", {})
.get("contentMetadataViewModel", {})
.get("metadataRows", [])
)
for row in vm:
for part in row.get("metadataParts", []):
text = part.get("text", {}).get("content", "")
if "subscribers" in text.lower():
return parse_metric(text)
except Exception:
pass
meta_match = re.search(r'([\d\.]+[KMB]?)\s+subscribers', html_text, re.IGNORECASE)
if meta_match:
return parse_metric(meta_match.group(1))
return 0
def fetch_handle_subs(handle):
url = f"https://www.youtube.com/@{handle}"
try:
r = requests.get(url, headers=HEADERS, timeout=10)
if r.status_code == 200:
return extract_subscribers_from_html(r.text)
except Exception:
pass
return 0
def main():
print("=" * 65)
print("HIFFI INTELLIGENCE: CANONICAL YOUTUBE RESOLUTION & SAFETY AUDIT")
print("=" * 65)
conn = get_connection()
cur = conn.cursor()
updated = 0
preserved = 0
for artist, handle in CANONICAL_HANDLES.items():
# Get existing count
cur.execute("""
SELECT t.subscribers_count
FROM telemetry_metrics t
JOIN artists a ON t.artist_id = a.artist_id
WHERE LOWER(a.artist_name) = LOWER(?)
""", (artist,))
row = cur.fetchone()
existing_subs = row[0] if row and row[0] else 0
scraped_subs = fetch_handle_subs(handle)
# Enforce safety floor
if scraped_subs > 0:
if scraped_subs >= existing_subs or existing_subs in [0, 500000]:
cur.execute("""
UPDATE telemetry_metrics
SET subscribers_count = ?
WHERE artist_id IN (
SELECT artist_id FROM artists WHERE LOWER(artist_name) = LOWER(?)
)
""", (scraped_subs, artist))
print(f"[UPDATED] {artist:<22} -> {scraped_subs:,} subs (via @{handle})")
updated += 1
else:
print(f"[PRESERVED] {artist:<22} -> Kept {existing_subs:,} subs (Scraped @{handle} was {scraped_subs:,})")
preserved += 1
else:
print(f"[FAILED] {artist:<22} -> Handle @{handle} returned 0 / 404")
time.sleep(0.4)
conn.commit()
conn.close()
print("=" * 65)
print(f"Complete: Updated {updated} channels | Preserved {preserved} verified baselines.")
if __name__ == "__main__":
main()