This commit is contained in:
afkarxyz
2025-10-12 00:23:26 +07:00
parent f6f238361c
commit 3eda3245ca
12 changed files with 346 additions and 89 deletions
+183 -71
View File
@@ -13,15 +13,17 @@ from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
QLabel, QFileDialog, QListWidget, QTextEdit, QTabWidget, QButtonGroup, QRadioButton, QLabel, QFileDialog, QListWidget, QTextEdit, QTabWidget, QButtonGroup, QRadioButton,
QAbstractItemView, QProgressBar, QCheckBox, QDialog, QAbstractItemView, QProgressBar, QCheckBox, QDialog,
QDialogButtonBox, QComboBox, QStyledItemDelegate QDialogButtonBox, QComboBox, QStyledItemDelegate, QMessageBox
) )
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl, QTimer, QTime, QSettings, QSize from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl, QTimer, QTime, QSettings, QSize
from PyQt6.QtGui import QIcon, QTextCursor, QDesktopServices, QPixmap, QBrush from PyQt6.QtGui import QIcon, QTextCursor, QDesktopServices, QPixmap, QBrush, QPainter, QColor
from PyQt6.QtSvg import QSvgRenderer
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from getMetadata import get_filtered_data, parse_uri, SpotifyInvalidUrlException from getMetadata import get_filtered_data, parse_uri, SpotifyInvalidUrlException
from tidalDL import TidalDownloader from tidalDL import TidalDownloader
from deezerDL import DeezerDownloader from deezerDL import DeezerDownloader
from getSecret import scrape_and_save
@dataclass @dataclass
class Track: class Track:
@@ -35,6 +37,25 @@ class Track:
isrc: str = "" isrc: str = ""
release_date: str = "" release_date: str = ""
class SecretScrapeWorker(QThread):
finished = pyqtSignal(bool, str)
progress = pyqtSignal(str)
def run(self):
try:
self.progress.emit("Fixing error...")
self.progress.emit("Please wait, this may take a moment...")
success, message = scrape_and_save(progress_callback=self.progress.emit)
if success:
self.finished.emit(True, "Fixed successfully!")
else:
self.finished.emit(False, message)
except Exception as e:
self.finished.emit(False, f"Error: {str(e)}")
class MetadataFetchWorker(QThread): class MetadataFetchWorker(QThread):
finished = pyqtSignal(dict) finished = pyqtSignal(dict)
error = pyqtSignal(str) error = pyqtSignal(str)
@@ -392,8 +413,8 @@ class ServiceComboBox(QComboBox):
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
self.services = [ self.services = [
{'id': 'tidal', 'name': 'Tidal', 'icon': 'tidal.png', 'online': False}, {'id': 'tidal', 'name': 'Tidal', 'icon': 'icons/tidal.png', 'online': False},
{'id': 'deezer', 'name': 'Deezer', 'icon': 'deezer.png', 'online': False} {'id': 'deezer', 'name': 'Deezer', 'icon': 'icons/deezer.png', 'online': False}
] ]
for service in self.services: for service in self.services:
@@ -455,7 +476,7 @@ class ServiceComboBox(QComboBox):
class SpotiFLACGUI(QWidget): class SpotiFLACGUI(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.current_version = "4.8" self.current_version = "4.9"
self.tracks = [] self.tracks = []
self.all_tracks = [] self.all_tracks = []
self.successful_downloads = [] self.successful_downloads = []
@@ -530,6 +551,7 @@ class SpotiFLACGUI(QWidget):
def reset_ui(self): def reset_ui(self):
self.track_list.clear() self.track_list.clear()
self.track_list.show()
self.log_output.clear() self.log_output.clear()
self.progress_bar.setValue(0) self.progress_bar.setValue(0)
self.progress_bar.hide() self.progress_bar.hide()
@@ -543,12 +565,32 @@ class SpotiFLACGUI(QWidget):
if hasattr(self, 'search_widget'): if hasattr(self, 'search_widget'):
self.search_widget.hide() self.search_widget.hide()
def get_themed_icon(self, icon_name):
icon_path = os.path.join(os.path.dirname(__file__), "icons", icon_name)
if not os.path.exists(icon_path):
return QIcon()
with open(icon_path, 'r') as f:
svg_content = f.read()
svg_content = svg_content.replace('currentColor', self.current_theme_color)
renderer = QSvgRenderer(svg_content.encode())
pixmap = QPixmap(16, 16)
pixmap.fill(QColor(0, 0, 0, 0))
painter = QPainter(pixmap)
renderer.render(painter)
painter.end()
return QIcon(pixmap)
def initUI(self): def initUI(self):
self.setWindowTitle('SpotiFLAC') self.setWindowTitle('SpotiFLAC')
self.setFixedWidth(650) self.setFixedWidth(650)
self.setMinimumHeight(350) self.setMinimumHeight(350)
icon_path = os.path.join(os.path.dirname(__file__), "icon.svg") icon_path = os.path.join(os.path.dirname(__file__), "icons", "icon.svg")
if os.path.exists(icon_path): if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path)) self.setWindowIcon(QIcon(icon_path))
@@ -766,42 +808,42 @@ class SpotiFLACGUI(QWidget):
def setup_track_buttons(self): def setup_track_buttons(self):
self.btn_layout = QHBoxLayout() self.btn_layout = QHBoxLayout()
self.download_selected_btn = QPushButton('Download Selected') self.download_btn = QPushButton(' Download')
self.download_all_btn = QPushButton('Download All') self.download_btn.setIcon(self.get_themed_icon('download.svg'))
self.remove_btn = QPushButton('Remove Selected') self.delete_btn = QPushButton(' Delete')
self.clear_btn = QPushButton('Clear') self.delete_btn.setIcon(self.get_themed_icon('trash.svg'))
for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: for btn in [self.download_btn, self.delete_btn]:
btn.setMinimumWidth(120) btn.setFixedWidth(120)
btn.setCursor(Qt.CursorShape.PointingHandCursor) btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.download_selected_btn.clicked.connect(self.download_selected) self.download_btn.clicked.connect(self.download_tracks_action)
self.download_all_btn.clicked.connect(self.download_all) self.delete_btn.clicked.connect(self.delete_tracks)
self.remove_btn.clicked.connect(self.remove_selected_tracks)
self.clear_btn.clicked.connect(self.clear_tracks)
self.btn_layout.addStretch() self.btn_layout.addStretch()
for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: self.btn_layout.addWidget(self.download_btn)
self.btn_layout.addWidget(btn, 1) self.btn_layout.addWidget(self.delete_btn)
self.btn_layout.addStretch() self.btn_layout.addStretch()
self.single_track_container = QWidget() self.single_track_container = QWidget()
single_track_layout = QHBoxLayout(self.single_track_container) single_track_layout = QHBoxLayout(self.single_track_container)
single_track_layout.setContentsMargins(0, 0, 0, 0) single_track_layout.setContentsMargins(0, 0, 0, 0)
self.single_download_btn = QPushButton('Download') self.single_download_btn = QPushButton(' Download')
self.single_clear_btn = QPushButton('Clear') self.single_download_btn.setIcon(self.get_themed_icon('download.svg'))
self.single_delete_btn = QPushButton(' Delete')
self.single_delete_btn.setIcon(self.get_themed_icon('trash.svg'))
for btn in [self.single_download_btn, self.single_clear_btn]: for btn in [self.single_download_btn, self.single_delete_btn]:
btn.setFixedWidth(120) btn.setFixedWidth(120)
btn.setCursor(Qt.CursorShape.PointingHandCursor) btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.single_download_btn.clicked.connect(self.download_all) self.single_download_btn.clicked.connect(self.download_tracks_action)
self.single_clear_btn.clicked.connect(self.clear_tracks) self.single_delete_btn.clicked.connect(self.delete_tracks)
single_track_layout.addStretch() single_track_layout.addStretch()
single_track_layout.addWidget(self.single_download_btn) single_track_layout.addWidget(self.single_download_btn)
single_track_layout.addWidget(self.single_clear_btn) single_track_layout.addWidget(self.single_delete_btn)
single_track_layout.addStretch() single_track_layout.addStretch()
self.single_track_container.hide() self.single_track_container.hide()
@@ -815,6 +857,18 @@ class SpotiFLACGUI(QWidget):
self.log_output.setReadOnly(True) self.log_output.setReadOnly(True)
process_layout.addWidget(self.log_output) process_layout.addWidget(self.log_output)
fix_error_layout = QHBoxLayout()
fix_error_layout.addStretch()
self.fix_error_btn = QPushButton(' Fix Error')
self.fix_error_btn.setIcon(self.get_themed_icon('tool.svg'))
self.fix_error_btn.setFixedWidth(120)
self.fix_error_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.fix_error_btn.clicked.connect(self.fix_error_action)
self.fix_error_btn.hide()
fix_error_layout.addWidget(self.fix_error_btn)
fix_error_layout.addStretch()
process_layout.addLayout(fix_error_layout)
progress_time_layout = QVBoxLayout() progress_time_layout = QVBoxLayout()
progress_time_layout.setSpacing(2) progress_time_layout.setSpacing(2)
@@ -840,7 +894,8 @@ class SpotiFLACGUI(QWidget):
self.stop_btn.clicked.connect(self.stop_download) self.stop_btn.clicked.connect(self.stop_download)
self.pause_resume_btn.clicked.connect(self.toggle_pause_resume) self.pause_resume_btn.clicked.connect(self.toggle_pause_resume)
self.remove_successful_btn = QPushButton('Remove Finished Songs') self.remove_successful_btn = QPushButton(' Remove Finished Tracks')
self.remove_successful_btn.setIcon(self.get_themed_icon('circle-x.svg'))
self.remove_successful_btn.setFixedWidth(200) self.remove_successful_btn.setFixedWidth(200)
self.remove_successful_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.remove_successful_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.remove_successful_btn.clicked.connect(self.remove_successful_downloads) self.remove_successful_btn.clicked.connect(self.remove_successful_downloads)
@@ -1214,6 +1269,25 @@ class SpotiFLACGUI(QWidget):
} }
) )
self.refresh_button_icons()
def refresh_button_icons(self):
if hasattr(self, 'download_btn'):
self.download_btn.setIcon(self.get_themed_icon('download.svg'))
if hasattr(self, 'delete_btn'):
self.delete_btn.setIcon(self.get_themed_icon('trash.svg'))
if hasattr(self, 'single_download_btn'):
self.single_download_btn.setIcon(self.get_themed_icon('download.svg'))
if hasattr(self, 'single_delete_btn'):
self.single_delete_btn.setIcon(self.get_themed_icon('trash.svg'))
if hasattr(self, 'fix_error_btn'):
self.fix_error_btn.setIcon(self.get_themed_icon('tool.svg'))
if hasattr(self, 'remove_successful_btn'):
self.remove_successful_btn.setIcon(self.get_themed_icon('circle-x.svg'))
def setup_about_tab(self): def setup_about_tab(self):
about_tab = QWidget() about_tab = QWidget()
about_layout = QVBoxLayout() about_layout = QVBoxLayout()
@@ -1319,6 +1393,9 @@ class SpotiFLACGUI(QWidget):
return return
try: try:
if hasattr(self, 'fix_error_btn') and self.fix_error_btn.isVisible():
self.fix_error_btn.hide()
self.reset_state() self.reset_state()
self.reset_ui() self.reset_ui()
@@ -1356,6 +1433,39 @@ class SpotiFLACGUI(QWidget):
def on_metadata_error(self, error_message): def on_metadata_error(self, error_message):
self.log_output.append(f'Error: {error_message}') self.log_output.append(f'Error: {error_message}')
if "Failed to get raw data" in error_message or "Failed to fetch secrets" in error_message or "Failed to get access token" in error_message:
if not hasattr(self, 'fix_error_btn') or not self.fix_error_btn.isVisible():
self.show_fix_error_button()
def show_fix_error_button(self):
if hasattr(self, 'fix_error_btn'):
self.fix_error_btn.show()
def fix_error_action(self):
self.fix_error_btn.setEnabled(False)
self.fix_error_btn.setText("Fixing...")
self.scrape_worker = SecretScrapeWorker()
self.scrape_worker.progress.connect(lambda msg: self.log_output.append(msg))
self.scrape_worker.finished.connect(self.on_scrape_finished)
self.scrape_worker.start()
def on_scrape_finished(self, success, message):
self.log_output.append(message)
if hasattr(self, 'fix_error_btn'):
self.fix_error_btn.setEnabled(True)
self.fix_error_btn.setText("Fix Error")
if success:
self.fix_error_btn.hide()
if success:
url = self.spotify_url.text().strip()
if url:
self.log_output.append("Retrying fetch...")
QTimer.singleShot(1000, self.fetch_tracks)
def handle_track_metadata(self, track_data): def handle_track_metadata(self, track_data):
track_id = track_data["external_urls"].split("/")[-1] track_id = track_data["external_urls"].split("/")[-1]
@@ -1604,37 +1714,27 @@ class SpotiFLACGUI(QWidget):
def update_button_states(self): def update_button_states(self):
if self.is_single_track: if self.is_single_track:
for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: for btn in [self.download_btn, self.delete_btn]:
btn.hide() btn.hide()
self.single_track_container.show() self.single_track_container.show()
self.single_download_btn.setEnabled(True) self.single_download_btn.setEnabled(True)
self.single_clear_btn.setEnabled(True) self.single_delete_btn.setEnabled(True)
else: else:
self.single_track_container.hide() self.single_track_container.hide()
self.download_selected_btn.show() self.download_btn.show()
self.download_all_btn.show() self.delete_btn.show()
self.remove_btn.show()
self.clear_btn.show()
self.download_all_btn.setText('Download All') self.download_btn.setEnabled(True)
self.clear_btn.setText('Clear') self.delete_btn.setEnabled(True)
self.download_all_btn.setMinimumWidth(120)
self.clear_btn.setMinimumWidth(120)
self.download_selected_btn.setEnabled(True)
self.download_all_btn.setEnabled(True)
def hide_track_buttons(self): def hide_track_buttons(self):
buttons = [ buttons = [
self.download_selected_btn, self.download_btn,
self.download_all_btn, self.delete_btn
self.remove_btn,
self.clear_btn
] ]
for btn in buttons: for btn in buttons:
btn.hide() btn.hide()
@@ -1642,24 +1742,28 @@ class SpotiFLACGUI(QWidget):
if hasattr(self, 'single_track_container'): if hasattr(self, 'single_track_container'):
self.single_track_container.hide() self.single_track_container.hide()
def download_selected(self): def download_tracks_action(self):
if self.is_single_track: if self.is_single_track:
self.download_all() self.start_download([0])
else: else:
selected_items = self.track_list.selectedItems() selected_items = self.track_list.selectedItems()
if not selected_items: if not selected_items:
self.log_output.append('Warning: Please select tracks to download.') reply = QMessageBox.question(
return self,
selected_indices = [self.track_list.row(item) for item in selected_items] 'Confirm Download All',
self.download_tracks(selected_indices) f'No tracks selected. Download all {len(self.tracks)} tracks?',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
def download_all(self): if reply == QMessageBox.StandardButton.Yes:
if self.is_single_track: self.start_download(range(len(self.tracks)))
self.download_tracks([0])
else: else:
self.download_tracks(range(len(self.tracks))) selected_indices = [self.track_list.row(item) for item in selected_items]
self.start_download(selected_indices)
def download_tracks(self, indices): def start_download(self, indices):
self.log_output.clear() self.log_output.clear()
raw_outpath = self.output_dir.text().strip() raw_outpath = self.output_dir.text().strip()
outpath = os.path.normpath(raw_outpath) outpath = os.path.normpath(raw_outpath)
@@ -1703,13 +1807,12 @@ class SpotiFLACGUI(QWidget):
self.update_ui_for_download_start() self.update_ui_for_download_start()
def update_ui_for_download_start(self): def update_ui_for_download_start(self):
self.download_selected_btn.setEnabled(False) self.download_btn.setEnabled(False)
self.download_all_btn.setEnabled(False)
if hasattr(self, 'single_download_btn'): if hasattr(self, 'single_download_btn'):
self.single_download_btn.setEnabled(False) self.single_download_btn.setEnabled(False)
if hasattr(self, 'single_clear_btn'): if hasattr(self, 'single_delete_btn'):
self.single_clear_btn.setEnabled(False) self.single_delete_btn.setEnabled(False)
self.stop_btn.show() self.stop_btn.show()
self.pause_resume_btn.show() self.pause_resume_btn.show()
@@ -1747,13 +1850,12 @@ class SpotiFLACGUI(QWidget):
else: else:
self.remove_successful_btn.hide() self.remove_successful_btn.hide()
self.download_selected_btn.setEnabled(True) self.download_btn.setEnabled(True)
self.download_all_btn.setEnabled(True)
if hasattr(self, 'single_download_btn'): if hasattr(self, 'single_download_btn'):
self.single_download_btn.setEnabled(True) self.single_download_btn.setEnabled(True)
if hasattr(self, 'single_clear_btn'): if hasattr(self, 'single_delete_btn'):
self.single_clear_btn.setEnabled(True) self.single_delete_btn.setEnabled(True)
if success: if success:
self.log_output.append(f"\nStatus: {message}") self.log_output.append(f"\nStatus: {message}")
@@ -1830,11 +1932,27 @@ class SpotiFLACGUI(QWidget):
self.remove_successful_btn.hide() self.remove_successful_btn.hide()
def remove_selected_tracks(self): def delete_tracks(self):
if not self.is_single_track: if self.is_single_track:
self.reset_state()
self.reset_ui()
else:
selected_items = self.track_list.selectedItems() selected_items = self.track_list.selectedItems()
selected_indices = [self.track_list.row(item) for item in selected_items]
if not selected_items:
reply = QMessageBox.question(
self,
'Confirm Delete All',
f'No tracks selected. Delete all {len(self.tracks)} tracks?',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.reset_state()
self.reset_ui()
else:
selected_indices = [self.track_list.row(item) for item in selected_items]
tracks_to_remove = [self.tracks[i] for i in selected_indices] tracks_to_remove = [self.tracks[i] for i in selected_indices]
for track in tracks_to_remove: for track in tracks_to_remove:
@@ -1843,13 +1961,7 @@ class SpotiFLACGUI(QWidget):
if track in self.all_tracks: if track in self.all_tracks:
self.all_tracks.remove(track) self.all_tracks.remove(track)
self.update_track_list_display() self.update_track_list_display()
def clear_tracks(self):
self.reset_state()
self.reset_ui()
self.tab_widget.setCurrentIndex(0) self.tab_widget.setCurrentIndex(0)
def start_timer(self): def start_timer(self):
+17 -3
View File
@@ -1,5 +1,6 @@
from time import sleep from time import sleep
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from pathlib import Path
import requests import requests
import json import json
import time import time
@@ -14,19 +15,32 @@ def get_random_user_agent():
# https://github.com/xyloflake/spot-secrets-go # https://github.com/xyloflake/spot-secrets-go
def generate_totp(): def generate_totp():
url = "https://raw.githubusercontent.com/afkarxyz/secretBytes/refs/heads/main/secrets/secretBytes.json" local_path = Path.home() / ".spotify-secret" / "secretBytes.json"
used_local = False
try: try:
url = "https://raw.githubusercontent.com/afkarxyz/secretBytes/refs/heads/main/secrets/secretBytes.json"
resp = requests.get(url, timeout=10) resp = requests.get(url, timeout=10)
if resp.status_code != 200: if resp.status_code != 200:
raise Exception(f"Failed to fetch TOTP secrets from GitHub. Status: {resp.status_code}") raise Exception(f"GitHub fetch failed with status: {resp.status_code}")
secrets_list = resp.json() secrets_list = resp.json()
except Exception as github_error:
try:
if local_path.exists():
with open(local_path, 'r') as f:
secrets_list = json.load(f)
used_local = True
else:
raise Exception(f"GitHub failed ({github_error}) and no local file found at {local_path}")
except Exception as local_error:
raise Exception(f"Failed to fetch secrets from both GitHub and local: {local_error}")
try:
latest_entry = max(secrets_list, key=lambda x: x["version"]) latest_entry = max(secrets_list, key=lambda x: x["version"])
version = latest_entry["version"] version = latest_entry["version"]
secret_cipher = latest_entry["secret"] secret_cipher = latest_entry["secret"]
except Exception as e: except Exception as e:
raise Exception(f"Failed to fetch secrets from GitHub: {str(e)}") raise Exception(f"Failed to process secrets: {str(e)}")
processed = [byte ^ ((i % 33) + 9) for i, byte in enumerate(secret_cipher)] processed = [byte ^ ((i % 33) + 9) for i, byte in enumerate(secret_cipher)]
processed_str = "".join(map(str, processed)) processed_str = "".join(map(str, processed))
+111
View File
@@ -0,0 +1,111 @@
import json
import time
from pathlib import Path
from DrissionPage import ChromiumPage, ChromiumOptions
def summarise(caps):
real = {}
for cap in caps:
sec = cap.get("secret")
if not sec or not isinstance(sec, str):
continue
ver = cap.get("version") or cap.get("obj", {}).get("version")
if ver and ver != 0:
real[str(int(ver))] = sec
if not real:
return False, "No secrets found."
versions = sorted(int(k) for k in real.keys())
secret_bytes = [
{"version": v, "secret": [ord(c) for c in real[str(v)]]}
for v in versions
]
secrets_dir = Path.home() / ".spotify-secret"
secrets_dir.mkdir(exist_ok=True)
output_file = secrets_dir / "secretBytes.json"
with open(output_file, "w") as f:
json.dump(secret_bytes, f, indent=2)
return True, f"Saved to: {output_file}"
def grab_live(progress_callback=None):
"""
Grab secrets from Spotify web player
Args:
progress_callback: Optional callback function to report progress
Returns:
list: Captured secrets
"""
def emit_progress(msg):
if progress_callback:
progress_callback(msg)
else:
print(msg)
stealth = """(()=>{
Object.defineProperty(navigator,'webdriver',{get:()=>false});
Object.defineProperty(navigator,'languages',{get:()=>['en-US','en']});
Object.defineProperty(navigator,'plugins',{get:()=>[1,2,3,4,5]});
window.chrome={runtime:{}};
const q=navigator.permissions.query;
navigator.permissions.query=p=>p.name==='notifications'?Promise.resolve({state:Notification.permission}):q(p);
const g=WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter=function(p){
if(p===37445)return'Intel Inc.';if(p===37446)return'Intel Iris OpenGL Engine';return g.call(this,p);
};
})();"""
hook = """(()=>{if(globalThis.__secretHookInstalled)return;
globalThis.__secretHookInstalled=true;globalThis.__captures=[];
Object.defineProperty(Object.prototype,'secret',{configurable:true,set:function(v){
try{__captures.push({secret:v,version:this.version,obj:this});}catch(e){}
Object.defineProperty(this,'secret',{value:v,writable:true,configurable:true,enumerable:true});}});
})();"""
co = ChromiumOptions()
co.headless(True)
co.set_argument('--disable-blink-features=AutomationControlled')
co.set_argument('--no-sandbox')
page = ChromiumPage(addr_or_opts=co)
try:
page.run_cdp('Page.addScriptToEvaluateOnNewDocument', source=stealth)
page.run_cdp('Page.addScriptToEvaluateOnNewDocument', source=hook)
emit_progress("Opening Spotify...")
page.get("https://open.spotify.com")
time.sleep(3)
caps = page.run_js("return globalThis.__captures || []")
for c in caps:
if isinstance(c, dict) and c.get("secret") and c.get("version"):
emit_progress(f"Secret({int(c['version'])}): {c['secret']}")
return caps or []
finally:
page.quit()
def scrape_and_save(progress_callback=None):
"""
Main function to scrape secrets and save to file
Args:
progress_callback: Optional callback function to report progress
Returns:
tuple: (success: bool, message: str)
"""
try:
caps = grab_live(progress_callback)
return summarise(caps)
except Exception as e:
return False, f"Error: {str(e)}"
def main():
success, message = scrape_and_save()
print(message)
return 0 if success else 1
if __name__ == "__main__":
import sys
sys.exit(main())
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
</svg>

After

Width:  |  Height:  |  Size: 402 B

+1
View File
@@ -6,3 +6,4 @@ mutagen
pyotp pyotp
packaging packaging
pyinstaller pyinstaller
DrissionPage