Skip to content

Instantly share code, notes, and snippets.

@xtex404
Created April 20, 2022 23:53
Show Gist options
  • Select an option

  • Save xtex404/3c4c91b09c21b064a61471f55fb2fd6d to your computer and use it in GitHub Desktop.

Select an option

Save xtex404/3c4c91b09c21b064a61471f55fb2fd6d to your computer and use it in GitHub Desktop.
garbage python script that creates playlist files from my beatsaber favorites, previous full combos, and previously finished songs. don't use this.
#!venv/Scripts/python
#
# super janky quickly thrown together python script that'll create playlists from beatsaber favorites. it'll also
# create playlists from songs i've passed before and got fullcombos on.
#
# it also creates text files of the bsr request keys for the songs, so I can use them as source lists for my streamer.bot
# that fills my request queue if i need some suggestions. whatever.
#
# this code is terrible. it's not optimized. you shouldn't use it. one day i'll rewrite this to not suck, but i'm lazy
# today and I just wanna play. i was so lazy i didn't feel like trying to figure out how to parse the protobuf song cache
# that already exists, so I cheat and grab the JSON version. this is gross. i'm so sorry.
#
# seriously. don't run this unless you understand what it's doing -- and you change the filenames/etc, because it uses my
# settings and not yours.
#
import base64
import datetime
import json
import os
import sys
import zipfile
from pathlib import Path
import pytz
import requests
from dateutil.parser import parse as parsedate
# from http.client import HTTPConnection # py3
#
# log = logging.getLogger('urllib3')
# log.setLevel(logging.DEBUG)
#
# # logging from urllib3 to console
# ch = logging.StreamHandler()
# ch.setLevel(logging.DEBUG)
# log.addHandler(ch)
#
# # print statements from `http.client.HTTPConnection` to console/stdout
# HTTPConnection.debuglevel = 1
utc = pytz.utc
mytz = pytz.timezone('America/New_York')
MIN_AGE_SEC = 86400
MAX_AGE_SEC = 246400
BS_DIR = Path('D:/SteamLibrary/steamapps/common/Beat Saber')
BS_FAVORITES_PLAYLIST_FILE = BS_DIR / 'Playlists/xedlock_favorites.bplist'
BS_PASSED_PLAYLIST_FILE = BS_DIR / 'Playlists/xedlock_passed.bplist'
BS_FULLCOMBO_PLAYLIST_FILE = BS_DIR / 'Playlists/xedlock_fullcombo.bplist'
PLAYLIST_PNG_IMAGE = Path('C:/Users/xedlock/OneDrive/AppData/streaming/assets/logos/xedlock/rainbow/xx256.png')
BS_SONGS_CACHE_FILE = BS_DIR / 'UserData/SongCache/SongDetails_Full.json'
BS_SONGS_CACHE_FILE_SMALL = BS_DIR / 'UserData/SongCache/SongDetails.json'
BS_SONGS_KEY_FILE = BS_DIR / 'UserData/SongCache/BSR2Hash.json'
BS_SONGS_TITLE_FILE = BS_DIR / 'UserData/SongCache/BSR2Titles.json'
DOWNLOAD_PATH = Path('C:/Users/xedlock/AppData/LocalLow/Temp')
BS_SONGS_DOWNLOAD_FILE = DOWNLOAD_PATH / '004SongDetails.zip'
BS_SONGS_DOWNLOAD_ETAG = DOWNLOAD_PATH / '004SongDetails.zip.etag'
BS_DATA_URL = 'https://github.com/andruzzzhka/BeatSaberScrappedData/raw/master/combinedScrappedData.zip'
def refresh_data_cache():
force_refresh = False
if BS_SONGS_CACHE_FILE.exists():
modified_time = utc.localize(datetime.datetime.fromtimestamp(BS_SONGS_CACHE_FILE.stat().st_mtime))
else:
modified_time = utc.localize(datetime.datetime.fromtimestamp(165888000))
force_refresh = True
modified_time_http = modified_time.strftime('%a, %d %b %Y %H:%M:%S GMT')
time_now = utc.localize(datetime.datetime.now())
elapsed = round(time_now.timestamp() - modified_time.timestamp())
if not force_refresh and elapsed < MIN_AGE_SEC:
print(f'"{BS_SONGS_CACHE_FILE}" was modified {elapsed} seconds ago (less than {MIN_AGE_SEC}).')
return True
if elapsed < MAX_AGE_SEC:
if BS_SONGS_DOWNLOAD_ETAG.exists():
with open(BS_SONGS_DOWNLOAD_ETAG, 'r') as f:
my_etag = f.readline().strip()
else:
my_etag = None
else:
my_etag = None
force_refresh = True
# make the request
headers = dict()
if modified_time_http is not None:
headers['If-Modified-Since'] = modified_time_http
if my_etag is not None:
headers['If-None-Match'] = my_etag
req = requests.head(url=BS_DATA_URL, headers=headers, allow_redirects=True)
their_etag = None
if req.status_code == 200:
their_etag = req.headers.get('Etag', None)
if their_etag:
# there's surely a better way to do this?
if their_etag.startswith('W/"'):
their_etag = their_etag.replace('W/"', '')
their_etag = their_etag.replace('"', '')
elif req.status_code == 304 and not force_refresh:
print(f"URL returned status code 304 - not modified.")
return True
if my_etag == their_etag and not force_refresh:
print(f"ETag identical, file appears to have not been modified?")
return True
# okay. let's get it then?
print(f"Retrieving song data from remote cache...")
req = requests.get(url=BS_DATA_URL, headers=headers, allow_redirects=True)
if req.status_code == 200:
with open(BS_SONGS_DOWNLOAD_FILE, 'wb') as f:
f.write(req.content)
their_date = req.headers.get('Date', None)
if their_date:
their_modified_date = parsedate(their_date)
print(f'Modified Date: {their_modified_date}')
their_modified_date_ts = their_modified_date.timestamp()
# os.utime(BS_SONGS_CACHE_FILE, (their_modified_date_ts,their_modified_date_ts))
else:
their_modified_date_ts = None
their_etag = req.headers.get('Etag', None)
if their_etag:
# there's surely a better way to do this?
if their_etag.startswith('W/"'):
their_etag = their_etag.replace('W/"', '')
their_etag = their_etag.replace('"', '')
with open(BS_SONGS_DOWNLOAD_ETAG, 'w') as f:
f.write(their_etag.strip())
else:
BS_SONGS_DOWNLOAD_ETAG.unlink()
# now let's unzip the file
print("Unzipping data file...")
raw_data = None
with zipfile.ZipFile(BS_SONGS_DOWNLOAD_FILE, mode='r') as z:
file_list = z.namelist()
if len(file_list) != 1:
print("unexpected files in zipfile. aborting.")
sys.exit(1)
with z.open(file_list[0]) as zz:
raw_data = zz.read()
print("Parsing source cache file...")
json_data = json.loads(raw_data)
bsr_data = dict()
bsr_key_lookup = dict()
bsr_title_lookup = dict()
out_dict = dict()
for R in json_data:
bhash = R['Hash'].upper()
bkey = R['Key'].lower()
bsr_key_lookup[bkey] = bhash
del R['Hash']
bsr_data[bhash] = R
bsr_title_lookup[bkey] = R['SongName']
with open(BS_SONGS_CACHE_FILE, 'w') as of:
json.dump(bsr_data, of)
with open(BS_SONGS_KEY_FILE, 'w') as of:
json.dump(bsr_key_lookup, of, sort_keys=True)
with open(BS_SONGS_TITLE_FILE, 'w') as of:
json.dump(bsr_title_lookup, of, sort_keys=True)
# gross, but i'm lazy
bsr_data_small = dict()
for k, d in bsr_data.items():
del d['Diffs']
del d['Chars']
bsr_data_small[k] = d
with open(BS_SONGS_CACHE_FILE_SMALL, 'w') as of:
json.dump(bsr_data_small, of, sort_keys=True)
os.utime(BS_SONGS_CACHE_FILE, (their_modified_date_ts, their_modified_date_ts))
os.utime(BS_SONGS_KEY_FILE, (their_modified_date_ts, their_modified_date_ts))
os.utime(BS_SONGS_CACHE_FILE_SMALL, (their_modified_date_ts, their_modified_date_ts))
os.utime(BS_SONGS_TITLE_FILE, (their_modified_date_ts, their_modified_date_ts))
return True
elif req.status_code == 304:
print(f"Delayed response to unmodified file, but OK.")
return True
else:
return False
return True
if refresh_data_cache() is False:
sys.exit(0)
BS_PLAYERDATA = Path('C:/Users/xedlock/AppData/LocalLow/Hyperbolic Magnetism/Beat Saber/PlayerData.dat')
print(f"Reading Beat Saber favorites..")
with open(BS_PLAYERDATA, 'r') as f:
player_data = json.load(f)
favorites = player_data['localPlayers'][0]['favoritesLevelIds']
played_songs = player_data['localPlayers'][0]['levelsStatsData']
#
# favorites
#
print(f"Found {len(favorites)} favorites songs in PlayerData.dat")
pdata = dict()
pdata['playlistTitle'] = 'XFavorites'
pdata['playlistAuthor'] = 'xedlock'
pdata['playlistDescription'] = 'Playlist generated from Beat Saber favorites'
pdata['customData'] = dict()
pdata['customData']['AllowDuplicates'] = False
song_list = list()
with open(BS_SONGS_CACHE_FILE_SMALL, 'r') as f:
song_data = json.load(f)
# # can't figure out what time format this is supposed to use -- every file is different different.
# # 2022-04-18T19:31:44.083174-04:00
# now_time_string = datetime.datetime.now().astimezone().isoformat()
# # 2022-04-18T23:31:33Z
# now_time_string = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
# # 2022-04-18T23:31:57.339634+00:00
now_time_string = utc.localize(datetime.datetime.utcnow()).isoformat()
bsr_keys = list()
print(f"Creating playlist ...")
for s in favorites:
this_entry: dict = dict()
if s.startswith('custom_level_'):
the_hash = s.replace('custom_level_', '').upper()
the_song = song_data.get(the_hash, None)
if the_song:
custom_data: dict = dict()
this_entry['songName'] = the_song['SongName']
if the_song.get('SongSubName', None):
this_entry['songSubName'] = the_song['SongSubName']
if the_song.get('SongAuthorName', None):
this_entry['songAuthorName'] = the_song.get('SongAuthorName', None)
this_entry['levelAuthorName'] = the_song.get('LevelAuthorName', None)
this_entry['key'] = the_song['Key']
bsr_keys.append(the_song['Key'].lower())
this_entry['hash'] = the_hash
this_entry['level_id'] = s
# if the_song.get('Bpm', None):
# this_entry['bpm'] = the_song.get('Bpm')
# if the_song.get('Downvotes', None):
# this_entry['downvotes'] = the_song.get('Downvotes')
# if the_song.get('Upvotes', None):
# this_entry['upvotes'] = the_song.get('Upvotes')
custom_data['name'] = the_song['SongName']
if the_song.get('Uploader', None):
custom_data['uploader'] = the_song.get('Uploader')
this_entry['customData'] = custom_data
if the_song.get('Uploaded', None):
this_entry['uploaded'] = the_song.get('Uploaded', None)
this_entry['dateAdded'] = now_time_string
song_list.append(this_entry)
print(f" * {this_entry['songName']}")
else:
print(f"!! Cannot find song level: {s}")
else:
this_entry['songName'] = s
this_entry['levelAuthorName'] = ''
this_entry['level_id'] = s
song_list.append(this_entry)
sorted_list = sorted(song_list, key=lambda i: (i['songName'].lower()))
pdata['songs'] = sorted_list
print(f"Adding cover image to playlist ({PLAYLIST_PNG_IMAGE})")
if PLAYLIST_PNG_IMAGE.is_file():
with open(PLAYLIST_PNG_IMAGE, 'rb') as img_file:
base64_data = base64.b64encode(img_file.read()).decode('utf-8')
if len(base64_data) > 64:
pdata['image'] = f"data:image/png;base64,{base64_data}"
print(f"Writing new playlist: {BS_FAVORITES_PLAYLIST_FILE}")
with open(BS_FAVORITES_PLAYLIST_FILE, 'w') as of:
json.dump(pdata, of, indent=4)
key_list_file = Path(str(BS_FAVORITES_PLAYLIST_FILE).replace('.bplist', '.bsrkeys.txt'))
print(f"Writing new playlist key_list: {key_list_file}")
bsr_keys.sort()
with open(key_list_file, 'w') as of:
for k in bsr_keys:
of.write(f"{k}\n")
print(f"Wrote {len(sorted_list)} songs to {BS_FAVORITES_PLAYLIST_FILE}")
print("done.")
# ######################################################################################################################
# all played
#
print(f"Found {len(played_songs)} played songs in PlayerData.dat")
pdata = dict()
pdata['playlistTitle'] = 'XPassed'
pdata['playlistAuthor'] = 'xedlock'
pdata['playlistDescription'] = 'Playlist generated from Beat Saber PlayerData played'
pdata['customData'] = dict()
pdata['customData']['AllowDuplicates'] = False
song_list = list()
# with open(BS_SONGS_CACHE_FILE_SMALL, 'r') as f:
# song_data = json.load(f)
# # can't figure out what time format this is supposed to use -- every file is different different.
# # 2022-04-18T19:31:44.083174-04:00
# now_time_string = datetime.datetime.now().astimezone().isoformat()
# # 2022-04-18T23:31:33Z
# now_time_string = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
# # 2022-04-18T23:31:57.339634+00:00
now_time_string = utc.localize(datetime.datetime.utcnow()).isoformat()
playlist = dict()
bsr_keys = list()
print(f"Creating playlist ...")
for song_entry in played_songs:
this_entry: dict = dict()
high_score = song_entry.get('highScore', None)
valid_score = song_entry.get('validScore', None)
level_id = song_entry.get('levelId', None)
# skip this if it wasn't finished
if high_score is None or valid_score is None or level_id is None:
continue
if not isinstance(high_score, int):
continue
if high_score < 50000:
continue
if not valid_score:
continue
characteristic = song_entry.get('beatmapCharacteristicName', 'Standard')
difficulty = song_entry.get('difficulty', 0)
if difficulty == 5:
difficulty_name = 'Expert+'
elif difficulty == 4:
difficulty_name = 'Expert'
elif difficulty == 3:
difficulty_name = 'Hard'
elif difficulty == 2:
difficulty_name = 'Normal'
elif difficulty == 1:
difficulty_name = 'Easy'
else:
difficulty = 0
difficulty_name = None
if level_id in playlist and difficulty > 0 and difficulty_name is not None:
# this already exists, so we're just adding a difficulty
playlist_entry = playlist[level_id]
difficulty_found = False
difficulties = playlist_entry.get('difficulties', list())
if difficulties and isinstance(difficulties, list):
for diffs in playlist_entry['difficulties']:
diffname = diffs.get('name', None)
if not diffname:
continue
if diffname.lower() == difficulty_name.lower():
difficulty_found = True
if not difficulty_found:
new_difficulty = dict()
new_difficulty['characteristic'] = characteristic
new_difficulty['name'] = difficulty_name
difficulties.append(new_difficulty)
playlist[level_id]['difficulties'] = difficulties
continue
if level_id.startswith('custom_level_'):
the_hash = level_id.replace('custom_level_', '').upper()
the_song = song_data.get(the_hash, None)
if the_song:
custom_data: dict = dict()
difficulties = list()
this_entry['songName'] = the_song['SongName']
if the_song.get('SongSubName', None):
this_entry['songSubName'] = the_song['SongSubName']
if the_song.get('SongAuthorName', None):
this_entry['songAuthorName'] = the_song.get('SongAuthorName', None)
this_entry['levelAuthorName'] = the_song.get('LevelAuthorName', None)
if difficulty_name is not None:
difficulty = dict()
difficulty['characteristic'] = characteristic
difficulty['name'] = difficulty_name
difficulties.append(difficulty)
this_entry['difficulties'] = difficulties
this_entry['key'] = the_song['Key']
bsr_keys.append(the_song['Key'].lower())
this_entry['hash'] = the_hash
this_entry['level_id'] = s
# if the_song.get('Bpm', None):
# this_entry['bpm'] = the_song.get('Bpm')
# if the_song.get('Downvotes', None):
# this_entry['downvotes'] = the_song.get('Downvotes')
# if the_song.get('Upvotes', None):
# this_entry['upvotes'] = the_song.get('Upvotes')
custom_data['name'] = the_song['SongName']
if the_song.get('Uploader', None):
custom_data['uploader'] = the_song.get('Uploader')
if the_song.get('Uploaded', None):
this_entry['uploaded'] = the_song.get('Uploaded', None)
this_entry['customData'] = custom_data
this_entry['dateAdded'] = now_time_string
playlist[level_id] = this_entry
print(f" * {this_entry['songName']}")
else:
print(f"!! Cannot find song level: {s}")
else:
this_entry['songName'] = s
this_entry['levelAuthorName'] = ''
this_entry['level_id'] = s
custom_data = dict()
difficulties = list()
if difficulty_name is not None:
difficulty = dict()
difficulty['characteristic'] = characteristic
difficulty['name'] = difficulty_name
difficulties.append(difficulty)
this_entry['difficulties'] = difficulties
this_entry['customData'] = custom_data
playlist[level_id] = this_entry
# convert my playlist dict into a list
song_list = list()
for key, data in playlist.items():
# if 'difficulties' in data:
# if len(data['difficulties']) == 1:
# if data['difficulties'][0]['name'] == 'Easy':
# continue
song_list.append(data)
sorted_list = sorted(song_list, key=lambda i: (i['songName'].lower()))
pdata['songs'] = sorted_list
print(f"Adding cover image to playlist ({PLAYLIST_PNG_IMAGE})")
if PLAYLIST_PNG_IMAGE.is_file():
with open(PLAYLIST_PNG_IMAGE, 'rb') as img_file:
base64_data = base64.b64encode(img_file.read()).decode('utf-8')
if len(base64_data) > 64:
pdata['image'] = f"data:image/png;base64,{base64_data}"
print(f"Writing new playlist: {BS_PASSED_PLAYLIST_FILE}")
with open(BS_PASSED_PLAYLIST_FILE, 'w') as of:
json.dump(pdata, of, indent=4)
key_list_file = Path(str(BS_PASSED_PLAYLIST_FILE).replace('.bplist', '.bsrkeys.txt'))
print(f"Writing new playlist key_list: {key_list_file}")
bsr_keys.sort()
with open(key_list_file, 'w') as of:
for k in bsr_keys:
of.write(f"{k}\n")
print(f"Wrote {len(sorted_list)} songs to {BS_PASSED_PLAYLIST_FILE}")
print("done.")
# ######################################################################################################################
# full combo (yes, this code is a duplicate, yes, it's gross.
#
print(f"Found {len(played_songs)} played songs in PlayerData.dat")
pdata = dict()
pdata['playlistTitle'] = 'XFullCombo'
pdata['playlistAuthor'] = 'xedlock'
pdata['playlistDescription'] = 'Playlist generated from Beat Saber PlayerData played'
pdata['customData'] = dict()
pdata['customData']['AllowDuplicates'] = False
song_list = list()
# with open(BS_SONGS_CACHE_FILE_SMALL, 'r') as f:
# song_data = json.load(f)
# # can't figure out what time format this is supposed to use -- every file is different different.
# # 2022-04-18T19:31:44.083174-04:00
# now_time_string = datetime.datetime.now().astimezone().isoformat()
# # 2022-04-18T23:31:33Z
# now_time_string = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
# # 2022-04-18T23:31:57.339634+00:00
now_time_string = utc.localize(datetime.datetime.utcnow()).isoformat()
playlist = dict()
bsr_keys = list()
print(f"Creating playlist ...")
for song_entry in played_songs:
this_entry: dict = dict()
high_score = song_entry.get('highScore', None)
valid_score = song_entry.get('validScore', None)
level_id = song_entry.get('levelId', None)
full_combo = song_entry.get('fullCombo', False)
# skip this if it wasn't finished
if high_score is None or valid_score is None or level_id is None:
continue
if not isinstance(high_score, int):
continue
if high_score < 50000:
continue
if not full_combo:
continue
if not valid_score:
continue
characteristic = song_entry.get('beatmapCharacteristicName', 'Standard')
difficulty = song_entry.get('difficulty', 0)
if difficulty == 5:
difficulty_name = 'Expert+'
elif difficulty == 4:
difficulty_name = 'Expert'
elif difficulty == 3:
difficulty_name = 'Hard'
elif difficulty == 2:
difficulty_name = 'Normal'
elif difficulty == 1:
difficulty_name = 'Easy'
else:
difficulty = 0
difficulty_name = None
if level_id in playlist and difficulty > 0 and difficulty_name is not None:
# this already exists, so we're just adding a difficulty
playlist_entry = playlist[level_id]
difficulty_found = False
difficulties = playlist_entry.get('difficulties', list())
if difficulties and isinstance(difficulties, list):
for diffs in playlist_entry['difficulties']:
diffname = diffs.get('name', None)
if not diffname:
continue
if diffname.lower() == difficulty_name.lower():
difficulty_found = True
if not difficulty_found:
new_difficulty = dict()
new_difficulty['characteristic'] = characteristic
new_difficulty['name'] = difficulty_name
difficulties.append(new_difficulty)
playlist[level_id]['difficulties'] = difficulties
continue
if level_id.startswith('custom_level_'):
the_hash = level_id.replace('custom_level_', '').upper()
the_song = song_data.get(the_hash, None)
if the_song:
custom_data: dict = dict()
difficulties = list()
this_entry['songName'] = the_song['SongName']
if the_song.get('SongSubName', None):
this_entry['songSubName'] = the_song['SongSubName']
if the_song.get('SongAuthorName', None):
this_entry['songAuthorName'] = the_song.get('SongAuthorName', None)
this_entry['levelAuthorName'] = the_song.get('LevelAuthorName', None)
if difficulty_name is not None:
difficulty = dict()
difficulty['characteristic'] = characteristic
difficulty['name'] = difficulty_name
difficulties.append(difficulty)
this_entry['difficulties'] = difficulties
this_entry['key'] = the_song['Key']
bsr_keys.append(the_song['Key'].lower())
this_entry['hash'] = the_hash
this_entry['level_id'] = s
# if the_song.get('Bpm', None):
# this_entry['bpm'] = the_song.get('Bpm')
# if the_song.get('Downvotes', None):
# this_entry['downvotes'] = the_song.get('Downvotes')
# if the_song.get('Upvotes', None):
# this_entry['upvotes'] = the_song.get('Upvotes')
custom_data['name'] = the_song['SongName']
if the_song.get('Uploader', None):
custom_data['uploader'] = the_song.get('Uploader')
if the_song.get('Uploaded', None):
this_entry['uploaded'] = the_song.get('Uploaded', None)
this_entry['customData'] = custom_data
this_entry['dateAdded'] = now_time_string
playlist[level_id] = this_entry
print(f" * {this_entry['songName']}")
else:
print(f"!! Cannot find song level: {s}")
else:
this_entry['songName'] = s
this_entry['levelAuthorName'] = ''
this_entry['level_id'] = s
custom_data = dict()
difficulties = list()
if difficulty_name is not None:
difficulty = dict()
difficulty['characteristic'] = characteristic
difficulty['name'] = difficulty_name
difficulties.append(difficulty)
this_entry['difficulties'] = difficulties
this_entry['customData'] = custom_data
playlist[level_id] = this_entry
# convert my playlist dict into a list
song_list = list()
for key, data in playlist.items():
# if 'difficulties' in data:
# if len(data['difficulties']) == 1:
# if data['difficulties'][0]['name'] == 'Easy':
# continue
song_list.append(data)
sorted_list = sorted(song_list, key=lambda i: (i['songName'].lower()))
pdata['songs'] = sorted_list
print(f"Adding cover image to playlist ({PLAYLIST_PNG_IMAGE})")
if PLAYLIST_PNG_IMAGE.is_file():
with open(PLAYLIST_PNG_IMAGE, 'rb') as img_file:
base64_data = base64.b64encode(img_file.read()).decode('utf-8')
if len(base64_data) > 64:
pdata['image'] = f"data:image/png;base64,{base64_data}"
print(f"Writing new playlist: {BS_FULLCOMBO_PLAYLIST_FILE}")
with open(BS_FULLCOMBO_PLAYLIST_FILE, 'w') as of:
json.dump(pdata, of, indent=4)
key_list_file = Path(str(BS_FULLCOMBO_PLAYLIST_FILE).replace('.bplist', '.bsrkeys.txt'))
print(f"Writing new playlist key_list: {key_list_file}")
bsr_keys.sort()
with open(key_list_file, 'w') as of:
for k in bsr_keys:
of.write(f"{k}\n")
print(f"Wrote {len(sorted_list)} songs to {BS_FULLCOMBO_PLAYLIST_FILE}")
print("done.")
@xtex404
Copy link
Author

xtex404 commented Apr 20, 2022

seriously. this is so bad. but someone asked me how i did it.. and.. I was drunk one night and threw up on my keyboard, and here's the result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment