rearange files and updateing files

This commit is contained in:
SumGuyV5 committed 2026-09-11 20:08:26 +00:00
1 parent 20acb4bc9f
commit 2de2c461e2
28 files changed
+285 -188

No files matched your search

+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env python3
import requests
import os
import package.linux as linux
import time
from package.database import Database
import uploader
from uploader import CategoryId
import youtube_hashtags
import package.transcribe_video as transcribe_video
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
DB = None
def transcribe(id: str):
"""Transcribes the Video File."""
video_file = f"save/{DB.table}/{id}/{id}.mp4"
# Check if the video file exists
if not os.path.exists(video_file):
print(f"Error: File not found: {video_file}")
return False
# no need to continue if srt transcribe file already exists
if os.path.exists(f"save/{DB.table}/{id}/transcribe_{id}.srt"):
print(f"video already transcribed:")
return True
transcribe_video.extract_audio(video_file, f"save/{DB.table}/{id}/temp_{id}_audio.wav")
transcribe_video.transcribe_to_srt(f"save/{DB.table}/{id}/temp_{id}_audio.wav", f"save/{DB.table}/{id}/", f"transcribe_{id}")
return True
def top_hashtags(id: str):
""""Hashtags from transcribed SRT file."""
file_srt = f"save/{DB.table}/{id}/transcribe_{id}.srt"
# if srt transcribe file not exists
if not os.path.exists(file_srt):
print(f"Transcribe file not found {file_srt}")
return []
tags = youtube_hashtags.get_top_hashtags(file_srt)
return tags
def download():
"""Find all undownload videos and download them."""
undownloaded = DB.get_undownloaded()
print(f"Download {DB.table}...")
if DB.table == "videos":
for record_id, record_date, title, game_name, downloaded, uploaded_yt, chat_upload_yt in undownloaded:
print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title}")
output = linux.run_command(f"TwitchDownloaderCLI videodownload --id {record_id} -o save/videos/{record_id}/{record_id}.mp4 --collision Overwrite")
output2 = linux.run_command(f"TwitchDownloaderCLI chatdownload --id {record_id} -o save/videos/{record_id}/{record_id}_chat.json -E --collision Overwrite")
time.sleep(1)
if output['success'] is True:
print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title} | was successful")
DB.mark_as_downloaded(record_id)
else:
print(f"ID: {record_id} | Download Process failed. {output['stdout']}. Error: {output['stderr']}")
elif DB.table == "clips":
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt, uploaded_shorts_yt in undownloaded:
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
# Uses standard clipdownload directive
output = linux.run_command(f"TwitchDownloaderCLI clipdownload --id {slug} -o save/clips/{slug}/{slug}.mp4 --collision Overwrite")
time.sleep(1)
if output['success'] is True:
print(f"Slug: {slug} | Was successfully downloaded.")
DB.mark_as_downloaded(slug)
else:
print(f"Slug: {slug} | Download Process failed. {output['stdout']}. Error: {output['stderr']}")
print(f"Finished Downloading {DB.table}...")
def upload():
"""Loops over the downloaded videos entries and uploaded them to youtube."""
print(f"Uploading {DB.table}...")
unuploaded = DB.get_unuploaded()
twitch_datetime = " Live on Twitch Every Friday and Sunday @7:30 ET https://twitch.tv/teampgp"
file_path = ""
title = ""
description = ""
categoryId = CategoryId.GAMING
privatcyStatus = 'private'
base_tags = ['gaming', 'TeamPGP', 'twitch', 'Level1Techs']
upload_queue = []
if DB.table == "videos":
for record_id, record_date, title, game_name, downloaded, uploaded_yt, chats_upload_yt in unuploaded:
print(f"ID: {record_id} | Date: {record_date} | Game: {game_name} | Title: {title}")
file_path = f"save/videos/{record_id}/{record_id}.mp4"
description = f"Game: {game_name}, on {record_date}, #VODS {twitch_datetime}"
tags = list(base_tags)
tags.extend([f'{game_name}', 'twitch_vods', 'vods'])
tags.extend(top_hashtags(record_id))
upload_queue.append([record_id, file_path, title, categoryId, description, privatcyStatus, tags])
elif DB.table == "clips":
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt, shorts_uploaded_yt in unuploaded:
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
transcribe(slug)
file_path = f"save/clips/{slug}/{slug}.mp4"
description = f"Game: {game_name}, Cliped By: {clip_by}, on {record_date}, #Shorts #Clips {twitch_datetime}"
tags = list(base_tags)
tags.extend([f'{game_name}', 'twitch_clips', 'clips', f'{clip_by}', 'shorts'])
tags.extend(top_hashtags(slug))
upload_queue.append([slug, file_path, title, categoryId, description, privatcyStatus, tags])
for db_id, file_path, title, categoryId, description, privatcyStatus, tags in upload_queue:
output = uploader.upload_video(file_path, title, categoryId, description, privatcyStatus, tags)
if output is True:
print(f"Title: {title} | Was successfully uploaded.")
DB.mark_as_uploaded(db_id)
else:
print(f"Title: {title} | Download Process failed.")
def create_chats():
pass
def create_shorts():
pass
def get_vod_ids_simplified(channel_name: str):
"""Queries Twitch's public endpoint directly for trending clips."""
session = requests.Session()
url = "https://gql.twitch.tv/gql"
# Case-preserved headers to prevent 400 Bad Request errors
session.headers = {
"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "text/plain"
}
videos_query_string = """
query GetChannelVideos($login: String!, $limit: Int!, $after: Cursor) {
user(login: $login) {
videos(first: $limit, types: [ARCHIVE], after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
title
publishedAt
game {
displayName
}
}
}
}
}
}
"""
clips_query_string = """
query GetChannelClips($login: String!, $limit: Int!, $after: Cursor) {
user(login: $login) {
clips(first: $limit, criteria: { period: ALL_TIME }, after: $after) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
slug
title
createdAt
viewCount
game {
displayName
}
curator {
login
}
}
}
}
}
}
"""
video_ids = []
has_next_page = True
cursor = None
limit = 50
query_string = ""
operation_name = ""
if DB.table == "videos":
query_string = videos_query_string
limit = 100
operation_name = "GetChannelVideos"
elif DB.table == "clips":
query_string = clips_query_string
limit = 40
operation_name = "GetChannelClips"
while has_next_page:
time.sleep(1)
# A simplified query string that hardcodes the ARCHIVE type filter into the request structure
payload = [{
"operationName": operation_name,
"query": query_string,
"variables": {
"login": channel_name.lower(),
"limit": limit,
"after": cursor
}
}]
try:
# Prepping ensures Python does not rewrite the Client-ID header case
req = requests.Request('POST', url, json=payload)
#req = requests.Request('POST', url, data=json.dumps(payload))
prepped = session.prepare_request(req)
response = session.send(prepped)
response.raise_for_status()
data = response.json()
# Pull out the target index array dictionary object
result = data[0] if isinstance(data, list) else data
if "errors" in result:
print(f"Twitch GraphQL Error: {result['errors']}")
return []
user_data = result.get('data', {}).get('user', {})
if not user_data:
print(f"Channel '{channel_name}' not found.")
return []
edges = user_data.get(DB.table, {}).get('edges', [])
# --- BREAK CONDITION 1: Stop if Twitch returns no more data items ---
if not edges or len(edges) == 0:
print("No more items returned by the server. Ending pagination loop.")
break
last_edge_cursor = None
print(f"--- Processing {DB.table} for {channel_name} ---")
for edge in edges:
last_edge_cursor = edge.get("cursor")
node = edge.get('node', {})
if not node:
continue
game_info = node.get('game')
game_name = game_info.get('displayName') if game_info else "Unknown/No Category"
# FIXED: Switched fields to use safe .get() metrics to completely prevent KeyErrors
node_id = node.get('id')
node_title = node.get('title', 'No Title')
if DB.table == "videos":
published_at = node.get('publishedAt')
print(f"ID: {node_id} | Date: {published_at} | Game: {game_name} | Title: {node_title}")
DB.insert_videos_record(node_id, published_at, node_title, game_name)
if node_id:
video_ids.append(node_id)
elif DB.table == "clips":
slug = node.get('slug')
created_at = node.get('createdAt')
view_count = node.get('viewCount', 0)
curator_info = node.get('curator')
clip_by = curator_info.get('login') if curator_info else "Unknown Creator"
print(f"Slug: {slug} | Date: {created_at} | Game: {game_name} | By: {clip_by} | Views: {view_count} | Title: {node_title}")
DB.insert_clips_record(slug, created_at, node_title, game_name, clip_by, int(view_count))
if slug:
video_ids.append(slug)
# --- CORRECTED PAGINATION ENGINE FOR BOTH TABLES ---
page_info = user_data.get(DB.table, {}).get('pageInfo', {})
has_next_page = page_info.get("hasNextPage", False)
next_cursor = page_info.get("endCursor") or last_edge_cursor
if not next_cursor or next_cursor == cursor:
print("Cursor did not advance or is null. Safely terminating loop.")
break
cursor = next_cursor
except Exception as e:
print(f"An unexpected error occurred: {e}")
if 'response' in locals():
print(f"Server Response Text: {response.text}")
return []
return video_ids
if __name__ == "__main__":
tables = ["clips"]
for table in tables:
DB = Database(table)
get_vod_ids_simplified(CHANNEL_NAME)
#download()
#upload()
#DB.close_database()
+197
View File
@@ -0,0 +1,197 @@
#!/usr/bin/env python3
import requests
import subprocess
from datetime import datetime
from package.database import Database
import uploader
from uploader import CategoryId
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
DB = Database("clips")
def run_linux_command(command: str):
"""Executes a Linux command, waits for completion, and returns output."""
try:
result = subprocess.run(
command, shell=True, check=True, capture_output=True, text=True
)
return {"success": True, "stdout": result.stdout, "stderr": result.stderr}
except subprocess.CalledProcessError as e:
return {"success": False, "stdout": e.stdout, "stderr": e.stderr}
def transcribe(slug: str):
"""Transcribes the Video File."""
video_file = f"save/clips/{slug}/{slug}.mp4"
# Check if the video file exists
if not os.path.exists(video_file):
print(f"Error: File not found: {video_file}")
return False
# no need to continue if srt transcribe file already exists
if os.path.exists(f"save/clips/{slug}/transcribe_{slug}.srt"):
print(f"video already transcribed:")
return True
import transcribe_video
transcribe_video.extract_audio(video_file, f"save/clips/{slug}/temp_{slug}_audio.wav")
transcribe_video.transcribe_to_srt(f"save/clips/{slug}/temp_{slug}_audio.wav", f"save/clips/{slug}/", f"transcribe_{slug}")
return True
def top_hashtags(slug: str):
file_srt = f"save/clips/{slug}/transcribe_{slug}.srt"
# if srt transcribe file not exists
if not os.path.exists(file_srt):
print(f"Transcribe file not found {file_srt}")
return []
import youtube_hashtags
return youtube_hashtags.get_top_hashtags(file_srt)
def download_clips():
"""Loops over undownloaded metadata entries to write files down locally."""
undownloaded_clips = DB.get_undownloaded()
print("Downloading Clips...")
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt, uploaded_shorts_yt in undownloaded_clips:
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
# Uses standard clipdownload directive
output = run_linux_command(f"TwitchDownloaderCLI clipdownload --id {slug} -o save/clips/{slug}/{slug}.mp4")
if output['success'] is True:
print(f"Slug: {slug} | Was successfully downloaded.")
DB.mark_as_downloaded(slug)
else:
print(f"Slug: {slug} | Download Process failed. {output['stdout']}. Error: {output['stderr']}")
print("Finished Downloading Clips...")
def upload_clips():
"""Loops over the downloaded videos entries and uploaded them to youtube."""
print(f"Uploading Clips...")
unuploaded_clips = DB.get_unuploaded()
categoryId = CategoryId.GAMING
for slug, record_date, title, game_name, clip_by, views, downloaded, uploaded_yt in unuploaded_clips:
print(f"Slug: {slug} | Date: {record_date} | Game: {game_name} | By: {clip_by} | Views: {views} | Title: {title}")
file_path = f"save/clips/{slug}/{slug}.mp4"
title = title
description = f"Game: {game_name}, Cliped By: {clip_by}, on {record_date}, #Shorts #Clips #Twitch Every Friday and Sunday @7:30 EST https://twitch.tv/teampgp"
categoryId = CategoryId.GAMING
privatcyStatus = 'private'
tags = ['shorts', 'gaming', 'TeamPGP', f'{game_name}', f'{clip_by}', 'twitch_clips', 'clips', 'Level1Techs', 'twitch']
tags.extend(top_hashtags({slug}))
output = uploader.upload_video(file_path, title, categoryId, description, privatcyStatus, tags)
if output is True:
print(f"Slug: {slug} | Was successfully uploaded.")
DB.mark_as_uploaded(slug)
else:
print(f"Slug: {slug} | Upload Process failed.")
def get_channel_clips(channel_name: str):
"""Queries Twitch's public endpoint directly for trending clips."""
session = requests.Session()
url = "https://gql.twitch.tv/gql"
session.headers = {
"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "text/plain"
}
# Twitch GQL schema for channel discovery clips
query_string = """
query GetChannelClips($login: String!, $limit: Int!) {
user(login: $login) {
clips(first: $limit, criteria: { period: ALL_TIME }) {
edges {
node {
slug
title
createdAt
viewCount
game {
displayName
}
curator {
login
}
}
}
}
}
}
"""
payload = [{
"operationName": "GetChannelClips",
"query": query_string,
"variables": {
"login": channel_name.lower(),
"limit": 40
}
}]
try:
req = requests.Request('POST', url, json=payload)
prepped = session.prepare_request(req)
response = session.send(prepped)
response.raise_for_status()
data = response.json()
result = data[0] if isinstance(data, list) else data
if "errors" in result:
print(f"Twitch GraphQL Error: {result['errors']}")
return []
user_data = result['data']['user']
if not user_data:
print(f"Channel '{channel_name}' not found.")
return []
edges = user_data['clips']['edges']
slugs = []
print(f"--- Top Clips for {channel_name} ---")
for edge in edges:
node = edge['node']
game_info = node.get('game')
game_name = game_info.get('displayName') if game_info else "Unknown/No Category"
slug_id = node['slug']
# Safe extraction in case the curator account was deleted/missing
curator_info = node.get('curator')
clip_by = curator_info.get('login') if curator_info else "Unknown Creator"
print(f"Slug: {node['slug']} | Date: {node['createdAt']} | Game: {game_name} | By: {clip_by} | Views: {node['viewCount']} | Title: {node['title']}")
DB.insert_clips_record(node['slug'], node['createdAt'], node['title'], game_name, clip_by, int(node['viewCount']))
slugs.append(slug_id)
return slugs
except Exception as e:
print(f"An unexpected error occurred: {e}")
return []
if __name__ == "__main__":
get_channel_clips(CHANNEL_NAME)
download_clips()
upload_clips()
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
import json
import os
import requests
# The target Twitch streamer username
TWITCH_USERNAME = "SumGuyV5"
def load_secrets(filepath="twitch_secrets.json"):
"""Loads client credentials and potential manual token from JSON file."""
if not os.path.exists(filepath):
raise FileNotFoundError(f"Missing credential file: '{filepath}'")
with open(filepath, "r") as file:
secrets = json.load(file)
if "client_id" not in secrets or "client_secret" not in secrets:
raise KeyError("JSON file must contain 'client_id' and 'client_secret'.")
return secrets["client_id"], secrets["client_secret"], secrets.get("manual_token")
def get_app_access_token(client_id, client_secret):
"""Generates an App Access Token using the correct Twitch ID server."""
auth_url = "https://twitch.tv" # FIXED: Correct auth endpoint
payload = {
"client_id": client_id,
"client_secret": client_secret,
"grant_type": "client_credentials"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(auth_url, data=payload, headers=headers)
response.raise_for_status()
return response.json()["access_token"]
def get_user_id(username, headers):
"""Retrieves the unique numerical Twitch User ID from Helix."""
url = f"https://twitch.tv{username}" # FIXED: Endpoint & parameter
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json().get("data")
if data and len(data) > 0:
return data[0]["id"] # FIXED: Helix data array returns user dictionaries
else:
raise ValueError(f"Twitch user '{username}' not found.")
def get_channel_vods(user_id, headers, limit=10):
"""Fetches past broadcasts (VODs) using valid Helix syntax."""
# FIXED: Restructured URL to use correct endpoint and standard query parameters
url = f"https://twitch.tv{user_id}&type=archive&first={limit}"
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
def main():
try:
# 1. Load credentials from external JSON file
client_id, client_secret, manual_token = load_secrets("twitch_secrets.json")
# 2. Assign or generate OAuth Access Token
if manual_token:
print("Using manual access token from JSON config file...")
access_token = manual_token
else:
print("No manual token found. Attempting to contact Twitch Auth Server...")
access_token = get_app_access_token(client_id, client_secret)
# 3. Setup Headers required by Twitch Helix API
headers = {
"Client-ID": client_id,
"Authorization": f"Bearer {access_token}"
}
# 4. Translate Username to User ID
user_id = get_user_id(TWITCH_USERNAME, headers)
print(f"Successfully retrieved ID for {TWITCH_USERNAME}: {user_id}\n")
# 5. Fetch and Print VOD details
vods = get_channel_vods(user_id, headers, limit=5)
if not vods:
print(f"No VODs found for {TWITCH_USERNAME}.")
return
print(f"--- Latest VODs for {TWITCH_USERNAME} ---")
for vod in vods:
print(f"Title: {vod['title']}")
print(f"URL: {vod['url']}")
print(f"Published At: {vod['published_at']}")
print(f"Duration: {vod['duration']}")
print(f"Views: {vod['view_count']}")
print("-" * 40)
except (FileNotFoundError, KeyError) as config_err:
print(f"Configuration Error: {config_err}")
except requests.exceptions.HTTPError as err:
print(f"HTTP Error detail: {err.response.text if err.response else err}")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()
+191
View File
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
import os
import requests
import sqlite3
import subprocess
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
def download_clips():
undownload_items = get_undownloaded_records()
print("Download Clips...")
# Updated unpack loop to include clip_by
for record_id, record_date, title, gamename, clip_by, downloaded in undownload_items:
print(f"Slug: {record_id} | Date: {record_date} | Game: {gamename} | By: {clip_by} | Title: {title}")
# Ensure output directory exists before running CLI tools
os.makedirs(f"save/clips/{record_id}", exist_ok=True)
# Uses the 'clipdownload' command
output = run_linux_command(f"TwitchDownloaderCLI clipdownload --id {record_id} -o save/clips/{record_id}/{record_id}.mp4")
if output["success"] is True:
print(f"Slug: {record_id} | Date: {record_date} | Game: {gamename} | By: {clip_by} | Title: {title} | was successful")
mark_as_downloaded(record_id)
else:
print(f"Slug: {record_id} | Date: {record_date} | Game: {gamename} | By: {clip_by} | Title: {title} | was Failed")
if not output["success"]: print(f"-> Clip Download Error: {output['stderr']}")
def run_linux_command(command: str):
"""Executes a Linux command, waits for completion, and returns output."""
try:
result = subprocess.run(
command, shell=True, check=True, capture_output=True, text=True
)
return {"success": True, "stdout": result.stdout, "stderr": result.stderr}
except subprocess.CalledProcessError as e:
return {"success": False, "stdout": e.stdout, "stderr": e.stderr}
def mark_as_downloaded(record_id: str):
"""Updates the downloaded status to True (1) for a specific clip slug."""
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
cursor.execute(
"UPDATE clips SET downloaded = 1 WHERE id = ?",
(record_id,)
)
conn.commit()
conn.close()
def get_undownloaded_records():
"""Retrieves all rows where downloaded status is False (0)."""
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
# Added clip_by to the SELECT fields
cursor.execute(
"SELECT id, date, title, gamename, clip_by, downloaded, uploaded FROM clips WHERE downloaded = 0"
)
records = cursor.fetchall()
conn.close()
return records
def insert_record(record_id: str, record_date: str, title: str, gamename: str, clip_by: str):
"""Inserts a clip record including its text slug string and creator username."""
conn = sqlite3.connect("database.db")
cursor = conn.cursor()
# Added clip_by TEXT NOT NULL field to table definition
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS clips (
id TEXT PRIMARY KEY,
date TEXT NOT NULL,
title TEXT NOT NULL,
gamename TEXT NOT NULL,
clip_by TEXT NOT NULL,
downloaded INTEGER NOT NULL,
uploaded INTEGER NOT NULL
)
"""
)
# Handled database migration gracefully if running script on an older DB missing the column
try:
cursor.execute(
"INSERT OR IGNORE INTO clips (id, date, title, gamename, clip_by, downloaded, uploaded) VALUES (?, ?, ?, ?, ?, ?, ?)",
(str(record_id), str(record_date), title, gamename, clip_by, 0, 0),
)
except sqlite3.OperationalError as e:
if "has no column named clip_by" in str(e):
print("Upgrading database schema to support creator names...")
cursor.execute("ALTER TABLE clips ADD COLUMN clip_by TEXT DEFAULT 'Unknown'")
cursor.execute(
"INSERT OR IGNORE INTO clips (id, date, title, gamename, clip_by, downloaded, uploaded) VALUES (?, ?, ?, ?, ?, ?, ?)",
(str(record_id), str(record_date), title, gamename, clip_by, 0, 0),
)
else:
raise e
conn.commit()
conn.close()
def get_clip_slugs(channel_name):
session = requests.Session()
url = "https://twitch.tv"
session.headers = {
"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "text/plain"
}
# Query updated to fetch the curator's login (the user who clipped it)
query_string = """
query GetChannelClips($login: String!, $limit: Int!) {
user(login: $login) {
clips(first: $limit, criteria: { period: ALL_TIME, sort: VIEWS_DESC }) {
edges {
node {
slug
title
createdAt
game {
displayName
}
curator {
login
}
}
}
}
}
}
"""
payload = [{
"operationName": "GetChannelClips",
"query": query_string,
"variables": {
"login": channel_name.lower(),
"limit": 30
}
}]
try:
req = requests.Request('POST', url, json=payload)
prepped = session.prepare_request(req)
response = session.send(prepped)
response.raise_for_status()
data = response.json()
result = data[0] if isinstance(data, list) else data
if "errors" in result:
print(f"Twitch GraphQL Error: {result['errors']}")
return []
user_data = result['data']['user']
if not user_data:
print(f"Channel '{channel_name}' not found.")
return []
edges = user_data['clips']['edges']
clip_slugs = []
print(f"--- Top Clips for {channel_name} ---")
for edge in edges:
node = edge['node']
game_info = node.get('game')
game_name = game_info.get('displayName') if game_info else "Unknown/No Category"
# Safe extraction in case the curator account was deleted/missing
curator_info = node.get('curator')
clip_by = curator_info.get('login') if curator_info else "Unknown Creator"
print(f"Slug: {node['slug']} | Date: {node['createdAt']} | Game: {game_name} | By: {clip_by} | Title: {node['title']}")
# Passing clip_by to database writer block
insert_record(node['slug'], node['createdAt'], node['title'], game_name, clip_by)
clip_slugs.append(node['slug'])
return clip_slugs
except Exception as e:
print(f"❌ An unexpected error occurred: {e}")
return []
if __name__ == "__main__":
get_clip_slugs(CHANNEL_NAME)
download_clips()
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
import json
import os
import requests
CHANNEL_NAME = 'teampgp' # Replace with the streamer's username
DB_FILE = "twitch_vods.json"
def get_vod_data(channel_name):
session = requests.Session()
url = "https://twitch.tv"
session.headers = {
"Client-ID": "kimne78kx3ncx6brgo4mv6wki5h1ko",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "text/plain"
}
query_string = """
query GetChannelVideos($login: String!, $limit: Int!) {
user(login: $login) {
videos(first: $limit, types: [ARCHIVE]) {
edges {
node {
id
title
publishedAt
}
}
}
}
}
"""
payload = [{
"operationName": "GetChannelVideos",
"query": query_string,
"variables": {
"login": channel_name.lower(),
"limit": 30
}
}]
try:
req = requests.Request('POST', url, json=payload)
prepped = session.prepare_request(req)
response = session.send(prepped)
response.raise_for_status()
data = response.json()
result = data if isinstance(data, list) else data
if "errors" in result:
print(f"Twitch GraphQL Error: {result['errors']}")
return []
user_data = result['data']['user']
if not user_data:
print(f"Channel '{channel_name}' not found.")
return []
return user_data['videos']['edges']
except Exception as e:
print(f"Failed to fetch data: {e}")
return []
def store_in_nosql_format(edges):
# Load existing NoSQL database if it exists, otherwise start fresh
if os.path.exists(DB_FILE):
with open(DB_FILE, 'r', encoding='utf-8') as f:
try:
db = json.load(f)
except json.JSONDecodeError:
db = {}
else:
db = {}
new_records_count = 0
# Process and structure data using VOD ID as the key
for edge in edges:
node = edge['node']
vod_id = node['id']
# This structures the document under the specific ID key
db[vod_id] = {
"title": node['title'],
"published_at": node['publishedAt'],
"channel": CHANNEL_NAME.lower()
}
new_records_count += 1
# Save the updated document store back to the disk
with open(DB_FILE, 'w', encoding='utf-8') as f:
json.dump(db, f, indent=4, ensure_ascii=False)
print(f"Successfully processed {new_records_count} VOD documents into '{DB_FILE}'.")
if __name__ == "__main__":
vod_edges = get_vod_data(CHANNEL_NAME)
if vod_edges:
store_in_nosql_format(vod_edges)