1
- from json import JSONDecodeError
2
1
import logging
3
2
import os
4
3
import re
5
4
import sys
5
+ from json import JSONDecodeError
6
6
from pathlib import Path
7
7
from random import shuffle as unsort
8
8
from time import strftime
9
- import ytmusicapi
10
9
11
10
import click
12
11
import enlighten
12
+ import ytmusicapi
13
13
from ytmusic_deleter import constants as const
14
14
from ytmusicapi import YTMusic
15
15
@@ -21,7 +21,7 @@ def ensure_auth(credential_dir):
21
21
global youtube_auth
22
22
oauth_file_path : str = str (Path (credential_dir ) / const .OAUTH_FILENAME )
23
23
try :
24
- logging .info (f' Attempting authentication with: { oauth_file_path } ' )
24
+ logging .info (f" Attempting authentication with: { oauth_file_path } " )
25
25
youtube_auth = YTMusic (oauth_file_path )
26
26
logging .info (f'Authenticated with: { oauth_file_path } "' )
27
27
except JSONDecodeError :
@@ -335,7 +335,9 @@ def unlike_all(ctx):
335
335
)
336
336
for track in your_likes ["tracks" ]:
337
337
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
339
341
)
340
342
title = track ["title" ]
341
343
logging .info (f"Processing track: { artist } - { title } " )
@@ -375,11 +377,11 @@ def delete_playlists(ctx):
375
377
response = youtube_auth .delete_playlist (playlist ["playlistId" ])
376
378
if response :
377
379
logging .info (
378
- f"\t Removed playlist \" { playlist ['title' ]} \" from your library."
380
+ f"\t Removed playlist { playlist ['title' ]!r } from your library."
379
381
)
380
382
else :
381
383
logging .error (
382
- f"\t Failed to remove playlist \" { playlist ['title' ]} \" from your library."
384
+ f"\t Failed to remove playlist { playlist ['title' ]!r } from your library."
383
385
)
384
386
except Exception :
385
387
logging .error (
@@ -389,14 +391,52 @@ def delete_playlists(ctx):
389
391
logging .info ("Finished deleting all playlists" )
390
392
391
393
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
+
392
431
@cli .command ()
393
432
@click .pass_context
394
433
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 """
396
435
ctx .invoke (delete_uploads )
397
436
ctx .invoke (remove_library )
398
- ctx .invoke (unlike_all )
399
437
ctx .invoke (delete_playlists )
438
+ ctx .invoke (unlike_all )
439
+ ctx .invoke (delete_history )
400
440
401
441
402
442
@cli .command ()
@@ -432,7 +472,7 @@ def sort_playlist(ctx, shuffle, playlist_titles):
432
472
global progress_bar
433
473
progress_bar = manager .counter (
434
474
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" } ' ,
436
476
unit = "tracks" ,
437
477
enabled = not ctx .obj ["STATIC_PROGRESS" ],
438
478
)
@@ -473,7 +513,7 @@ def sort_playlist(ctx, shuffle, playlist_titles):
473
513
not_found_playlists .append (title )
474
514
if not_found_playlists :
475
515
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) '
477
517
'(or surround them "with quotes") and try again.'
478
518
)
479
519
0 commit comments