diff --git a/README.md b/README.md index 6c5918f..053be0e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ random scripts (the repo name was recommended by github) *These scripts where once used to automate silly tasks* +# DOCUMENTATION +Visit [docs.jonasjones.dev/turbo-octo-potato](https://docs.jonasjones.dev/turbo-octo-potato) for the documentation to these scripts + # Use at your own risk! These are mostly undocumented and potentially unsafe/hacky/silly scripts that I can't bother to keep around in my Downloads folder. diff --git a/discographymaker.py b/spotify_scripts/discographymaker.py similarity index 97% rename from discographymaker.py rename to spotify_scripts/discographymaker.py index fa4fd85..9349c59 100644 --- a/discographymaker.py +++ b/spotify_scripts/discographymaker.py @@ -1,5 +1,9 @@ -import spotipy from dotenv import load_dotenv +import spotipy +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import top_lib load_dotenv() diff --git a/spotify_scripts/getlastfmartists.py b/spotify_scripts/getlastfmartists.py new file mode 100644 index 0000000..3d6538f --- /dev/null +++ b/spotify_scripts/getlastfmartists.py @@ -0,0 +1,34 @@ +import pylast, time + +# Replace with your Last.fm API credentials +API_KEY = "" +API_SECRET = "" +USERNAME = "Jonas_Jones" +PASSWORD = "" # Leave this empty if you're not using a password + +# Create a Last.fm network object +network = pylast.LastFMNetwork( + api_key=API_KEY, api_secret=API_SECRET, username=USERNAME, password_hash=PASSWORD +) + +def get_listened_artists(): + user = network.get_user(USERNAME) + recent_tracks = user.get_recent_tracks(limit=None) # Fetch all recent tracks + listened_artists = set() + for track in recent_tracks: + artist = track.track.artist + if not artist in listened_artists: + print(artist) + listened_artists.add(artist) + + return listened_artists + +if __name__ == "__main__": + start = time.time() + listened_artists = get_listened_artists() + end = time.time() + print("Listened Artists:") + for artist in listened_artists: + print(artist) + + print("Time elapsed: " + str(end - start) + " seconds") diff --git a/spotify_scripts/getlastfmartistsslower.py b/spotify_scripts/getlastfmartistsslower.py new file mode 100644 index 0000000..843bd7d --- /dev/null +++ b/spotify_scripts/getlastfmartistsslower.py @@ -0,0 +1,39 @@ +import pylast + +# Replace with your Last.fm API credentials +API_KEY = "" +API_SECRET = "" +USERNAME = "Jonas_Jones" +PASSWORD = "" # Leave this empty if you're not using a password + +# Create a Last.fm network object +network = pylast.LastFMNetwork( + api_key=API_KEY, api_secret=API_SECRET, username=USERNAME, password_hash=PASSWORD +) + +def get_listened_artists(limit_per_batch=100): + user = network.get_user(USERNAME) + + page = 1 + listened_artists = set() + + while True: + recent_tracks = user.get_recent_tracks(limit=limit_per_batch, page=page) + if not recent_tracks: + break + + for track in recent_tracks: + artist = track.track.artist + listened_artists.add(artist) + + page += 1 + + return listened_artists + +if __name__ == "__main__": + listened_artists = get_listened_artists() + + print("Listened Artists:") + for artist in listened_artists: + print(artist) + diff --git a/getlasttracksp.py b/spotify_scripts/getlasttracksp.py similarity index 100% rename from getlasttracksp.py rename to spotify_scripts/getlasttracksp.py diff --git a/intro_outro_playlist_maker.py b/spotify_scripts/intro_outro_playlist_maker.py similarity index 96% rename from intro_outro_playlist_maker.py rename to spotify_scripts/intro_outro_playlist_maker.py index b715b47..267be2c 100644 --- a/intro_outro_playlist_maker.py +++ b/spotify_scripts/intro_outro_playlist_maker.py @@ -1,7 +1,10 @@ '''Make a playlist of all the intro/outro songs from your followed artists''' -import os from dotenv import load_dotenv +import spotipy +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import top_lib # WARNING: THIS WILL RATELIMIT THE SHIT OUT OF YOUR ACCOUNT diff --git a/kprofilesfetch.py b/spotify_scripts/kprofilesfetch.py similarity index 98% rename from kprofilesfetch.py rename to spotify_scripts/kprofilesfetch.py index ac27856..c5d73f4 100644 --- a/kprofilesfetch.py +++ b/spotify_scripts/kprofilesfetch.py @@ -1,10 +1,12 @@ '''Fetch the monthly comeback/debut/release pages on kprofiles.com''' -import os -import sys import datetime import requests import dotenv +import spotipy +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import top_lib dotenv.load_dotenv() diff --git a/lastfm-scrobble-fetch.py b/spotify_scripts/lastfm-scrobble-fetch.py similarity index 100% rename from lastfm-scrobble-fetch.py rename to spotify_scripts/lastfm-scrobble-fetch.py diff --git a/spotify_scripts/likedsongsync2-oldfix.py b/spotify_scripts/likedsongsync2-oldfix.py new file mode 100644 index 0000000..da28890 --- /dev/null +++ b/spotify_scripts/likedsongsync2-oldfix.py @@ -0,0 +1,203 @@ +import pylast, spotipy, sys, os, time +from spotipy.oauth2 import SpotifyOAuth +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +# Define your Last.fm API credentials +LASTFM_API_KEY = os.getenv("LASTFM_API_KEY") +LASTFM_API_SECRET = os.getenv("LASTFM_API_SECRET") +LASTFM_USERNAME = os.getenv("LASTFM_USERNAME") +LASTFM_PASSWORD_HASH = os.getenv("LASTFM_PASSWORD_HASH") + +# Define your Spotify API credentials +SPOTIPY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID") +SPOTIPY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET") +SPOTIPY_REDIRECT_URI = os.getenv("SPOTIFY_REDIRECT_URI") + +# Define your playlist IDs +LIKEDSONGPLAYLIST_ID = os.getenv("LIKEDSONGPLAYLIST_ID") + +def progress_bar(current, total, last_time_stamp=time.time(), etastr=None): + current = total if current > total else current + this_timestamp = time.time() + width = os.get_terminal_size().columns + progress = round((current/total)*width) + total_num_len = len(str(total)) + if width < 2* total_num_len + 15: + return f"{current}/{total}", this_timestamp + else: + current_spacer = " "*(total_num_len-len(str(current))) + if etastr: + eta = etastr + else: + eta = str(round((total - current)* (this_timestamp - last_time_stamp)/60)) + "s" + percent = str(round(current/total*100)) + percent = " "*(3-len(percent)) + percent + progress_bar_length = width - 2*total_num_len - 13 - len(str(eta)) - len(percent) + progress_bar_progress = round((current/total)*progress_bar_length) + progress_bar_spacer = " "*(progress_bar_length-progress_bar_progress) + return f"[{current_spacer}{current}/{total}|{percent}%|ETA: {eta}|{'='*progress_bar_progress}>{progress_bar_spacer}]", this_timestamp + +def verboseprint(message, end="\n"): + if VERBOSE_LOGGING: + print(message, end=end) + +def handle_playlist_part_return(playlist_part, all_songs): + for item in playlist_part["items"]: + track_uri = item["track"]["uri"] + track_name = item["track"]["name"] + artist_name = item["track"]["artists"][0]["name"] + all_songs.append((track_uri, track_name, artist_name)) + + return all_songs + +def get_all_songs_from_playlist(playlist_id): + verboseprint("Fetching songs from the liked songs playlist...") + all_songs = [] + limit = 100 # Adjust the limit based on your needs + offset = 0 + last_time_stamp = time.time() + + while True: + playlist_part = sp.playlist_items(playlist_id, limit=limit, offset=offset) + if not playlist_part["items"]: + break + all_songs = handle_playlist_part_return(playlist_part, all_songs) + + progress_print, last_time_stamp = progress_bar(offset+limit, playlist_part["total"], last_time_stamp) + verboseprint(progress_print, end="\r") + + offset += limit + verboseprint("") + return all_songs + +def get_all_liked_songs(): + verboseprint("Fetching liked songs...") + verboseprint("This may take a while... (the API only allows for 50 songs per request for the liked songs)") + all_liked_songs = [] + limit = 50 # Adjust the limit based on your needs + offset = 0 + last_time_stamp = time.time() + + while True: + liked_songs_chunk = sp.current_user_saved_tracks(limit=limit, offset=offset) + if not liked_songs_chunk["items"]: + break + all_liked_songs = handle_playlist_part_return(liked_songs_chunk, all_liked_songs) + + progress_print, last_time_stamp = progress_bar(offset+limit, liked_songs_chunk["total"], last_time_stamp) + verboseprint(progress_print, end="\r") + + offset += limit + verboseprint("") + return all_liked_songs + +def is_track_in_playlist(playlist_song_list, track_uri): + playlist_tracks = playlist_song_list + for item in playlist_tracks: + if item[0] == track_uri: + return True + return False + +def add_track_to_playlist(playlist_id, track_uri): + sp.playlist_add_items(playlist_id, [track_uri]) + +def remove_unliked_tracks(primary_playlist_id, liked_tracks): + verboseprint("Removing unliked tracks from the primary playlist...") + liked_track_uris = [track_uri for track_uri, _, _ in liked_tracks] + primary_playlist_tracks = get_all_songs_from_playlist(primary_playlist_id) + + #tracks_to_remove = [track_uri for track_uri in primary_playlist_tracks if track_uri not in liked_track_uris] + + tracks_to_remove = [] + for track_uri, _, _ in primary_playlist_tracks: + if track_uri not in liked_track_uris: + tracks_to_remove.append(track_uri) + + if tracks_to_remove: + sp.playlist_remove_all_occurrences_of_items(primary_playlist_id, tracks_to_remove) + print(f"Removed {len(tracks_to_remove)} unliked track(s) from the primary playlist.") + print(f"Track URIs: {tracks_to_remove}") + +if __name__ == "__main__": + + # Parse command-line arguments + VERBOSE_LOGGING = "-v" in sys.argv or "--verbose" in sys.argv + FORCE_RESYNC_ALL = "-f" in sys.argv or "--force-all" in sys.argv + + try: + SKIPSONGS = int(sys.argv[sys.argv.index("--skip") + 1]) if "--skip" in sys.argv else int(sys.argv[sys.argv.index("-s") + 1]) if "-s" in sys.argv else 0 + except: + print("[--skip/-s] Require a number to be set.") + print("E.g.: --skip 88") + exit() + + verboseprint("Authenticating Spotify...") + + # Create a Spotipy instance with authentication + sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET, redirect_uri=SPOTIPY_REDIRECT_URI, scope="user-library-read playlist-modify-public playlist-modify-private")) + + verboseprint("Authenticating Last.fm...") + + # Set up Last.fm network + network = pylast.LastFMNetwork(api_key=LASTFM_API_KEY, api_secret=LASTFM_API_SECRET, username=LASTFM_USERNAME, password_hash=LASTFM_PASSWORD_HASH) + + # Main loop for syncing liked songs + liked_songs = get_all_liked_songs() + + #remove_unliked_tracks(LIKEDSONGPLAYLIST_ID, liked_songs) + + liked_songs_playlist_songs = get_all_songs_from_playlist(LIKEDSONGPLAYLIST_ID) + + if not FORCE_RESYNC_ALL: + verboseprint("Syncing only new songs...") + liked_songs = [x for x in liked_songs if x not in liked_songs_playlist_songs] + print(liked_songs) + if len(liked_songs) == 0: + print("Nothing to do.") + exit() + print(f"Number of liked tracks: {len(liked_songs)}") + print(f"Number of playlist songs: {len(liked_songs_playlist_songs)}") + print(f"Skipping the first {SKIPSONGS} songs...") + tracknr = 0 + last_time_stamp = time.time() + for track_uri, track_name, artist_name in liked_songs: + tracknr += 1 + + def loop_do(last_time_stamp): + track = sp.track(track_uri) + fm_track = network.get_track(artist_name, track_name) + fm_track.love() + fm_track.add_tags(("awesome", "favorite")) + + if not is_track_in_playlist(liked_songs_playlist_songs, track_uri): + add_track_to_playlist(LIKEDSONGPLAYLIST_ID, track_uri) + if VERBOSE_LOGGING: + verboseprint("[" + f"%{4 + len(str(len(liked_songs)))*2}s" % (f"{tracknr}/{len(liked_songs)}|+]") + "%30.32s %s" % (track['artists'][0]['name'], track['name'])) + #verboseprint("%-10s %15s" % (f"ETA:{round((((int(len(liked_songs))-tracknr)*0.75)/60))}min", f"[{tracknr}/{int(len(liked_songs))}|+]") + "%30.32s %s" % (track['artists'][0]['name'], track['name'])) + elif VERBOSE_LOGGING: + verboseprint("[" + f"%{2 + len(str(len(liked_songs)))*2}s" % (f"{tracknr}/{len(liked_songs)}]") + "%32.32s %s" % (track['artists'][0]['name'], track['name'])) + #verboseprint("%-10s %13s" % (f"ETA:{round((((int(len(liked_songs))-tracknr)*0.75)/60))}min", f"[{tracknr}/{int(len(liked_songs))}]") + "%32.32s %s" % (track['artists'][0]['name'], track['name'])) + verboseprint('#'* os.get_terminal_size().columns, end="\r") + + progress_print, last_time_stamp = progress_bar(tracknr, len(liked_songs), etastr=str(round((((int(len(liked_songs))-tracknr)*0.75)/60)))+"min") + verboseprint(progress_print, end="\r") + return last_time_stamp + # Loop until the API call succeeds + while tracknr > SKIPSONGS: + try: + last_time_stamp = loop_do(last_time_stamp) + break + except KeyboardInterrupt: # Allow the user to interrupt the script + exit() + except spotipy.SpotifyException as e: + if e.http_status == 429: + time.sleep(30) + verboseprint(" ]") + verboseprint("WARN:RATELIMIT EXCEEDED] Waiting 30 seconds to proceed...") + else: + print(e.http_status) + except e: + continue diff --git a/likedsongsync2.py b/spotify_scripts/likedsongsync2.py similarity index 99% rename from likedsongsync2.py rename to spotify_scripts/likedsongsync2.py index b816bdc..e63c180 100644 --- a/likedsongsync2.py +++ b/spotify_scripts/likedsongsync2.py @@ -1,11 +1,12 @@ '''A script to sync your liked songs from Spotify to Last.fm and a Spotify playlist that can be made public (unlike the built-in liked songs playlist).''' +import time +from dotenv import load_dotenv +import spotipy import sys import os -import time -import spotipy -from dotenv import load_dotenv +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import top_lib # load .env file diff --git a/playlist_description_update.py b/spotify_scripts/playlist_description_update.py similarity index 94% rename from playlist_description_update.py rename to spotify_scripts/playlist_description_update.py index 02130c5..d985d0f 100644 --- a/playlist_description_update.py +++ b/spotify_scripts/playlist_description_update.py @@ -1,7 +1,11 @@ import spotipy from spotipy.oauth2 import SpotifyOAuth -import hashlib, os +import hashlib from dotenv import load_dotenv +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import top_lib def encrypt_playlist_name(name): diff --git a/tabletop_achievements/README.md b/tabletop_achievements/README.md new file mode 100644 index 0000000..97fee45 --- /dev/null +++ b/tabletop_achievements/README.md @@ -0,0 +1,3 @@ +# Tabletop Simulator Achievement Scripts + +You can find the documentation for these scripts and how to use them at [docs.jonasjones.dev/tabletop_achievement_scripts](https://docs.jonasjones.dev/tabletop_achievement_scripts) \ No newline at end of file diff --git a/tabletop_achievements/tabletop_fliptable_loop.py b/tabletop_achievements/tabletop_fliptable_loop.py new file mode 100644 index 0000000..eeec09c --- /dev/null +++ b/tabletop_achievements/tabletop_fliptable_loop.py @@ -0,0 +1,38 @@ +import pyautogui +import time + +# The position of the "Flip" button at the top of the screen +FIRSTCLICK_X = 2300 +FIRSTCLICK_Y = 70 + +# The timeout for UI elements to load (in seconds) +INBETWEENSLEEPTIME = 0.2 + +# The position of the "Yes" button of the "FLip table?" prompt +SECONDCLICK_X = 1760 +SECONDCLICK_Y = 1140 + +# The timeout set by the game until the "Flip" button becomes clickable again (in seconds) +TIMEOUTSLEEPTIME = 2 + +# The amount of iterations depending on the achievement +ITERATIONS = 100 + +# initialize the counter +count = 0 + +time.sleep(3) # delay to focus the game after launching the script + +while count < ITERATIONS: + # Click the "Flip" button + pyautogui.click(FIRSTCLICK_X, FIRSTCLICK_Y) + time.sleep(INBETWEENSLEEPTIME) + + # Click the confirmation button + pyautogui.click(SECONDCLICK_X, SECONDCLICK_Y) + + # update the counter and display it + count += 1 + print(count, end="\r") + + time.sleep(TIMEOUTSLEEPTIME) # Wait for 3 seconds before repeating diff --git a/tabletop_achievements/tabletop_savetochest_loop.py b/tabletop_achievements/tabletop_savetochest_loop.py new file mode 100644 index 0000000..689b8fd --- /dev/null +++ b/tabletop_achievements/tabletop_savetochest_loop.py @@ -0,0 +1,56 @@ +import pyautogui +import time +import os + +# The position of the right click to open the context menu for the object +RIGHTCLICK_X = 1870 +RIGHTCLICK_Y = 675 + +# The position of the "Save Object" option in the context menu +SAVECONTEXTMENUCLICK_X = 2030 +SAVECONTEXTMENUCLICK_Y = 875 + +# The position of the name input field in the "Save Object" poup +NAMEFIELDCLICK_X = 1700 +NAMEFIELDCLICK_Y = 1110 + +# The position of the save button in the "Save Object" popup +SAVECLICK_X = 1770 +SAVECLICK_Y = 1200 + +# The timeout for UI elements to load (in seconds) +TIMEOUTTIME = 0.15 + +# initialize the counter +count = 0 + + +time.sleep(3) # delay to focus the game after launching the script + +while count < 100: + # Right click for context menu + pyautogui.moveTo(RIGHTCLICK_X, RIGHTCLICK_Y) + pyautogui.click(RIGHTCLICK_X, RIGHTCLICK_Y, button='right') + time.sleep(TIMEOUTTIME) + + # Click on the "Save Object" option in the context menu + pyautogui.click(SAVECONTEXTMENUCLICK_X, SAVECONTEXTMENUCLICK_Y) + time.sleep(TIMEOUTTIME) + + # Click on the name input field to focus it + pyautogui.click(NAMEFIELDCLICK_X, NAMEFIELDCLICK_Y) + time.sleep(TIMEOUTTIME) + + # Get the current Unix timestamp + timestamp = str(int(time.time())) + + # Enter the timestamp as key presses + pyautogui.write(timestamp) + + # Click on the save button + pyautogui.click(SAVECLICK_X, SAVECLICK_Y) + time.sleep(TIMEOUTTIME) + + # update the counter and display it + count += 1 + print(count, end='\r') diff --git a/tabletop_achievements/tabletop_tint_loop.py b/tabletop_achievements/tabletop_tint_loop.py new file mode 100644 index 0000000..fe24913 --- /dev/null +++ b/tabletop_achievements/tabletop_tint_loop.py @@ -0,0 +1,52 @@ +import pyautogui +import time +import random + +# The position of the right-click to open the context menu on an object +RIGHTCLICK_X = 1890 +RIGHTCLICK_Y = 970 + +# The position of the "Color Tint" button in the context menu +FIRSTCLICK_X = 2055 +FIRSTCLICK_Y = 1225 + +# The position of the upper left corner of the color-selector square +SECONDCLICK_XMIN = 600 +SECONDCLICK_YMIN = 600 + +# The position of the lower right corner of the color-selector square +SECONDCLICK_XMAX = 1100 +SECONDCLICK_YMAX = 1100 + +# The position of the "Apply" button of the color selector +CONFIRMCLICK_X = 760 +CONFIRMCLICK_Y = 1550 + +# The timeout for UI elements to load (in seconds) +SLEEPTIME = 0.15 + +# initialize the counter +count = 0 + +time.sleep(3) # delay to focus the game after launching the script + +# Move to and rightclick on the object +pyautogui.moveTo(RIGHTCLICK_X, RIGHTCLICK_Y) +pyautogui.click(RIGHTCLICK_X, RIGHTCLICK_Y, button='right') + +while count < 1000: + # Click on the "Color Tint" option + pyautogui.click(FIRSTCLICK_X, FIRSTCLICK_Y) + time.sleep(SLEEPTIME) + + # Generate random x and y coordinates + x = random.randint(SECONDCLICK_XMIN, SECONDCLICK_XMAX) + y = random.randint(SECONDCLICK_YMIN, SECONDCLICK_YMAX) + + # Click at the random coordinates + pyautogui.click(x, y) + time.sleep(SLEEPTIME) + + # Click on the "Apply" button + pyautogui.click(CONFIRMCLICK_X, CONFIRMCLICK_Y) + time.sleep(SLEEPTIME)