Skip to content

Commit 481eab7

Browse files
authored
Remove history (#32)
* add "delete_history" command * polishing up delete-history; repeat command multiple times since only 200 history items can be retrieved at once * use ytmusicapi 1.5.2
1 parent 46ccdd4 commit 481eab7

File tree

3 files changed

+54
-11
lines changed

3 files changed

+54
-11
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
# ytmusicapi auth files
12
headers_auth.json
3+
oauth.json
4+
browser.json
25

36
# Byte-compiled / optimized / DLL files
47
__pycache__/

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
module_name = "ytmusic-deleter"
1616

17-
install_reqs = ["click", "ytmusicapi >= 1.5.0", "enlighten"]
17+
install_reqs = ["click", "ytmusicapi >= 1.5.2", "enlighten"]
1818

1919
develop_reqs = [
2020
"pre-commit",

ytmusic_deleter/cli.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
from json import JSONDecodeError
21
import logging
32
import os
43
import re
54
import sys
5+
from json import JSONDecodeError
66
from pathlib import Path
77
from random import shuffle as unsort
88
from time import strftime
9-
import ytmusicapi
109

1110
import click
1211
import enlighten
12+
import ytmusicapi
1313
from ytmusic_deleter import constants as const
1414
from ytmusicapi import YTMusic
1515

@@ -21,7 +21,7 @@ def ensure_auth(credential_dir):
2121
global youtube_auth
2222
oauth_file_path: str = str(Path(credential_dir) / const.OAUTH_FILENAME)
2323
try:
24-
logging.info(f'Attempting authentication with: {oauth_file_path}')
24+
logging.info(f"Attempting authentication with: {oauth_file_path}")
2525
youtube_auth = YTMusic(oauth_file_path)
2626
logging.info(f'Authenticated with: {oauth_file_path}"')
2727
except JSONDecodeError:
@@ -335,7 +335,9 @@ def unlike_all(ctx):
335335
)
336336
for track in your_likes["tracks"]:
337337
artist = (
338-
track["artists"][0]["name"] if "artists" in track else const.UNKNOWN_ARTIST
338+
track["artists"][0]["name"]
339+
if track.get("artists") # Using `get` ensures key exists and isn't []
340+
else const.UNKNOWN_ARTIST
339341
)
340342
title = track["title"]
341343
logging.info(f"Processing track: {artist} - {title}")
@@ -375,11 +377,11 @@ def delete_playlists(ctx):
375377
response = youtube_auth.delete_playlist(playlist["playlistId"])
376378
if response:
377379
logging.info(
378-
f"\tRemoved playlist \"{playlist['title']}\" from your library."
380+
f"\tRemoved playlist {playlist['title']!r} from your library."
379381
)
380382
else:
381383
logging.error(
382-
f"\tFailed to remove playlist \"{playlist['title']}\" from your library."
384+
f"\tFailed to remove playlist {playlist['title']!r} from your library."
383385
)
384386
except Exception:
385387
logging.error(
@@ -389,14 +391,52 @@ def delete_playlists(ctx):
389391
logging.info("Finished deleting all playlists")
390392

391393

394+
@cli.command
395+
@click.pass_context
396+
def delete_history(ctx):
397+
"""
398+
Delete your play history. This does not currently work with brand accounts.
399+
The API can only retrieve 200 history items at a time, so the process will appear to
400+
start over and repeat multiple times if necessary until all history is deleted.
401+
"""
402+
logging.info("Begin deleting history...")
403+
try:
404+
history_items = youtube_auth.get_history()
405+
except Exception as e:
406+
if str(e) == "None":
407+
logging.info("History is empty, nothing to delete.")
408+
else:
409+
logging.exception(e)
410+
return False
411+
global progress_bar
412+
progress_bar = manager.counter(
413+
total=len(history_items),
414+
desc="History Items Deleted",
415+
unit="items",
416+
enabled=not ctx.obj["STATIC_PROGRESS"],
417+
)
418+
logging.info(f"Found {len(history_items)} history items to delete.")
419+
for item in history_items:
420+
artist = (
421+
item["artists"][0]["name"]
422+
if item.get("artists") # Using `get` ensures key exists and isn't []
423+
else const.UNKNOWN_ARTIST
424+
)
425+
logging.info(f"Processing {artist} - {item['title']}")
426+
youtube_auth.remove_history_items(item["feedbackToken"])
427+
update_progress(ctx)
428+
ctx.invoke(delete_history) # repeat until history is empty
429+
430+
392431
@cli.command()
393432
@click.pass_context
394433
def delete_all(ctx):
395-
"""Executes delete-uploads, remove-library, unlike-all, and delete_playlists"""
434+
"""Executes delete-uploads, remove-library, delete-playlists, unlike-all, and delete-history"""
396435
ctx.invoke(delete_uploads)
397436
ctx.invoke(remove_library)
398-
ctx.invoke(unlike_all)
399437
ctx.invoke(delete_playlists)
438+
ctx.invoke(unlike_all)
439+
ctx.invoke(delete_history)
400440

401441

402442
@cli.command()
@@ -432,7 +472,7 @@ def sort_playlist(ctx, shuffle, playlist_titles):
432472
global progress_bar
433473
progress_bar = manager.counter(
434474
total=len(desired_tracklist),
435-
desc=f'\'{selected_playlist["title"]}\' Tracks {"Shuffled" if shuffle else "Sorted"}',
475+
desc=f'{selected_playlist["title"]!r} Tracks {"Shuffled" if shuffle else "Sorted"}',
436476
unit="tracks",
437477
enabled=not ctx.obj["STATIC_PROGRESS"],
438478
)
@@ -473,7 +513,7 @@ def sort_playlist(ctx, shuffle, playlist_titles):
473513
not_found_playlists.append(title)
474514
if not_found_playlists:
475515
raise click.BadParameter(
476-
f'No playlists found named "{", ".join(not_found_playlists)}". Double-check your playlist name(s) '
516+
f'No playlists found named {", ".join(not_found_playlists)!r}. Double-check your playlist name(s) '
477517
'(or surround them "with quotes") and try again.'
478518
)
479519

0 commit comments

Comments
 (0)