├── .env-template ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── categories.py ├── config.py ├── envs.py ├── get_refresh_token.py ├── requirements.txt └── update.py /.env-template: -------------------------------------------------------------------------------- 1 | CLIENT_ID="" 2 | CLIENT_SECRET="" 3 | REDIRECT_URI="http://localhost:5000" 4 | REFRESH_TOKEN="" 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Update my Spotify Playlist 5 | 6 | on: 7 | schedule: 8 | - cron: "47 2 * * *" 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python 3.10 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: "3.10" 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | pip install -r requirements.txt 29 | - name: update playlist 30 | run: | 31 | python update.py 32 | env: 33 | CLIENT_ID: ${{ secrets.CLIENT_ID }} 34 | CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} 35 | REDIRECT_URI: ${{ secrets.REDIRECT_URI }} 36 | REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 YOGESHWARAN R 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # update-my-Spotify-playlist 2 | 3 | Automatically update your Spotify playlists with tracks of your favorite artists and genres using this Python script powered by the Spotify Web API and GitHub Actions. 4 | 5 | ## Introduction 6 | 7 | Update My Spotify Playlist is a Python tool that streamlines your Spotify playlist management by adding your favourite tracks from your favorite artists to your playlist in real-time. 8 | 9 | ## Usage 10 | 11 | 1. Fork this repo 12 | 2. Clone your repo in local 13 | 3. copy `.env-template` into `.env` 14 | 4. Install all Python requirements in `requirements.txt` 15 | 5. Go to the Spotify Developer Dashboard and log in with your Spotify account. 16 | 6. Click on the "Create an App" button and fill out the necessary information, such as the name and description of your application. Use `http://localhost:5000` as redirect URL 17 | 7. Once you've created the app, you'll be taken to the app dashboard. Here, you'll find your client ID, client secret and Redirect URL. Copy this and paste it your `.env` file 18 | 8. Create new playlist in your Spotify Account and copy the playlist id from the url. Paste the playlist id in `.env` file. 19 | 9. Run this Script `get_refresh_token.py` to get refresh token. Follow the instruction in this script. Copy the refresh token into `.env` file. 20 | 10. Run this Script `update.py` for testing. 21 | 11. Add all env variable `CLIENT_ID`, `CLIENT_SECRET`, `REDIRECT_URI`, `REFRESH_TOKEN` in github repository secret. Checkout this [link](https://docs.github.com/en/actions/reference/encrypted-secrets) to add a new repository secret. 22 | 12. By default it updates daily at `2:47 UTC`. You can also change this by changing the cron in `/.github/workflows/main.yml` by using [Cron Generator](https://crontab.guru/). 23 | 24 | ## Playlist customization 25 | 26 | Your playlists can be customized by various category. 27 | 28 | In `config.py`, you can cusomize your playlists. 29 | 30 | `id` is id of your playlist and 31 | `category` is tracks of this playlist are under this category 32 | 33 | Currenly five catgories are available 34 | 35 | - `TopArtist()` 36 | - `TopTracks()` 37 | - `TopMix()` - It mix both TopArtist and TopTracks 38 | - `RecentlyPlayed()` 39 | - `Mix()` 40 | 41 | If you need a playlist with particualar artists, tracks or genre, you can use `Mix()` 42 | In Mix you can mix upto 5 parameter like Artist, Genre and Track. Only 5 parameter are allowed 43 | 44 | ```python 45 | PLAYLISTS = [ 46 | { 47 | 'id': 'xxxxxxxxx' 48 | 'category': Mix( 49 | [ 50 | Artist("7jVv8c5Fj3E9VhNjxT4snq"), 51 | Artist("6M2wZ9GZgrQXHCFfjv46we"), 52 | Artist("1Xyo4u8uXC1ZmMpatF05PJ"), 53 | Genre("pop"), 54 | Track("0HqZX76SFLDz2aW8aiqi7G") 55 | ] 56 | ) 57 | } 58 | ] 59 | ``` 60 | 61 | sample config file, you can have multiple playlists 62 | 63 | ```python 64 | PLAYLISTS = [ 65 | { 66 | 'id': '2bx19B2bwgTf20XWQBxlQo', 67 | 'category': TopMix(time_range=TimeRange.long_term, no_of_tracks_required=30) 68 | }, 69 | { 70 | 'id': '2bx19B2bwgTff20XWQBxlQo', 71 | 'category': TopArtist(time_range=TimeRange.short_term, no_of_tracks_required=30) 72 | }, 73 | { 74 | 'id': '7sLWB2thlLWFcvuExsc77y', 75 | 'category': Mix( 76 | [ 77 | Artist('7dGJo4pcD2V6oG8kP0tJRR'), 78 | Artist('1mYsTxnqsietFxj1OgoGbG'), 79 | Genre('pop') 80 | ], 81 | no_of_tracks_required=20 82 | ) 83 | }, 84 | { 85 | 'id': '23ljfjfwfpwjwjfwjfjfjs', 86 | 'category': RecentlyPlayed(no_of_tracks_required=23) 87 | } 88 | ] 89 | ``` 90 | 91 | ## Contributions 92 | 93 | Contributions are Welcome. Feel free to report bugs in issue and fix some bugs by creating pull requests. Comments, Suggestions, Improvements and Enhancements are always welcome. 94 | 95 | ## License 96 | 97 | Distributed under the MIT license. 98 | 99 | ## Contact 100 | 101 | If you have any questions or feedback about this project, feel free to get in touch with me via [mail](yogeshin247@gmail.com) 102 | 103 | Thank you; 104 | 105 | Made with Music ❤️ 106 | -------------------------------------------------------------------------------- /categories.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from dataclasses import dataclass 3 | 4 | import spotipy 5 | 6 | 7 | @dataclass 8 | class TimeRange: 9 | """Time Range class for ease configuration""" 10 | 11 | long_term = "long_term" 12 | medium_term = "medium_term" 13 | short_term = "short_term" 14 | 15 | 16 | class TopCategoriesBase(ABC): 17 | """Absract base class for Top Contents""" 18 | 19 | def __init__( 20 | self, time_range=TimeRange.medium_term, no_of_tracks_required=10 21 | ) -> None: 22 | self.time_range = time_range 23 | self.no_of_tracks_required = no_of_tracks_required 24 | 25 | 26 | class TopArtist(TopCategoriesBase): 27 | def _get_tracks(self, sp: spotipy.Spotify) -> list: 28 | """Returns URI of tracks based on current user top artists""" 29 | top_artists = sp.current_user_top_artists(limit=5, time_range=self.time_range) 30 | artists_ids = [artist["id"] for artist in top_artists["items"]] 31 | recommenations = sp.recommendations( 32 | seed_artists=artists_ids, limit=self.no_of_tracks_required 33 | )["tracks"] 34 | return [t["uri"] for t in recommenations] 35 | 36 | 37 | class TopTracks(TopCategoriesBase): 38 | def _get_tracks(self, sp: spotipy.Spotify) -> list: 39 | """Returns URI of tracks based on current user top tracks""" 40 | top_tracks = sp.current_user_top_tracks(limit=5, time_range=self.time_range) 41 | tracks_ids = [track["id"] for track in top_tracks["items"]] 42 | recommenations = sp.recommendations( 43 | seed_tracks=tracks_ids, limit=self.no_of_tracks_required 44 | )["tracks"] 45 | return [t["uri"] for t in recommenations] 46 | 47 | 48 | class TopMix(TopCategoriesBase): 49 | def _get_tracks(self, sp: spotipy.Spotify) -> list: 50 | """Returns URI of tracks based on current user top artists and top tracks""" 51 | top_tracks = sp.current_user_top_tracks(limit=3, time_range=self.time_range) 52 | tracks_ids = [track["id"] for track in top_tracks["items"]] 53 | top_artists = sp.current_user_top_artists(limit=2, time_range=self.time_range) 54 | artists_ids = [artist["id"] for artist in top_artists["items"]] 55 | recommenations = sp.recommendations( 56 | seed_tracks=tracks_ids, 57 | limit=self.no_of_tracks_required, 58 | seed_artists=artists_ids, 59 | )["tracks"] 60 | return [t["uri"] for t in recommenations] 61 | 62 | 63 | class RecentlyPlayed: 64 | def __init__(self, no_of_tracks_required=10) -> None: 65 | self.no_of_tracks_required = no_of_tracks_required 66 | 67 | def _get_tracks(self, sp: spotipy.Spotify) -> list: 68 | """Returns URI of tracks based on current user top artists""" 69 | recently_played = sp.current_user_recently_played(limit=5) 70 | tracks_ids = [] 71 | for item in recently_played["items"]: 72 | if item["track"]: 73 | tracks_ids.append(item["track"]["id"]) 74 | recommenations = sp.recommendations( 75 | seed_tracks=tracks_ids, limit=self.no_of_tracks_required 76 | )["tracks"] 77 | return [t["uri"] for t in recommenations] 78 | 79 | 80 | # class TopGenres(TopCategoriesBase): 81 | # def _get_tracks(self, sp: spotipy.Spotify): 82 | # top_artists = sp.current_user_top_artists( 83 | # limit=5, time_range=self.time_range 84 | # ) 85 | # genres = [] 86 | # _genres = [artist["genres"] for artist in top_artists["items"]] 87 | # for g in _genres: 88 | # for i in g: 89 | # genres.append(i) 90 | # recommenations = sp.recommendations( 91 | # seed_genres=genres[:4], limit=self.no_of_tracks_required 92 | # )["tracks"] 93 | # return [t["uri"] for t in recommenations] 94 | 95 | 96 | class Artist: 97 | def __init__(self, id) -> None: 98 | self.id = id 99 | 100 | 101 | class Track: 102 | def __init__(self, id) -> None: 103 | self.id = id 104 | 105 | 106 | class Genre: 107 | def __init__(self, genre) -> None: 108 | self.genre = genre 109 | 110 | 111 | class Mix: 112 | def __init__(self, mix, no_of_tracks_required=30) -> None: 113 | if len(mix) > 5: 114 | raise Exception("Only contains 5 conditions") 115 | else: 116 | self.mix = mix 117 | self.no_of_tracks_required = no_of_tracks_required 118 | 119 | def _get_tracks(self, sp) -> list: 120 | """Returns URI of tracks based on current user top artists""" 121 | tracks = [] 122 | artist = [] 123 | genres = [] 124 | for m in self.mix: 125 | if type(m) == Track: 126 | tracks.append(m.id) 127 | elif type(m) == Artist: 128 | artist.append(m.id) 129 | elif type(m) == Genre: 130 | genres.append(m.genre) 131 | else: 132 | pass 133 | recommenations = sp.recommendations( 134 | limit=self.no_of_tracks_required, 135 | seed_artists=artist, 136 | seed_tracks=tracks, 137 | seed_genres=genres, 138 | )["tracks"] 139 | return [t["uri"] for t in recommenations] 140 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from categories import * 2 | 3 | # Add Your Playlist id and its category 4 | 5 | PLAYLISTS = [ 6 | { 7 | 'id': '2fABBrRXVPr2d6UT4S3f2L', 8 | 'category': RecentlyPlayed(no_of_tracks_required=30) 9 | }, 10 | { 11 | 'id': '7rJuai1MjSx0h99zZIK0Je', 12 | 'category': Mix( 13 | [ 14 | Artist('1mYsTxnqsietFxj1OgoGbG'), 15 | Artist('6IHhDsmQhMdqnKIb2sE41H'), 16 | Artist('6AiX12wXdXFoGJ2vk8zBjy'), 17 | Artist('4zCH9qm4R2DADamUHMCa6O'), 18 | Artist('6CtYzQvENTdGq5LPPsePdV') 19 | ], 20 | no_of_tracks_required=50 21 | ) 22 | }, 23 | { 24 | 'id': '6n559itEfTJWdWpQiPZSxE', 25 | 'category': Mix( 26 | [ 27 | Artist('6M2wZ9GZgrQXHCFfjv46we'), 28 | Artist('5Pwc4xIPtQLFEnJriah9YJ'), 29 | Artist('1Xyo4u8uXC1ZmMpatF05PJ'), 30 | Artist('53XhwfbYqKCa1cC15pYq2q'), 31 | Genre('pop') 32 | ], 33 | no_of_tracks_required=50 34 | ) 35 | }, 36 | { 37 | 'id': '5X8TGKcqbaOyeFf2vxg3YJ', 38 | 'category': TopMix(no_of_tracks_required=30) 39 | }, 40 | { 41 | 'id': '1xOBtxXq8zR5azITxc7xDj', 42 | 'category': Mix( 43 | [ 44 | Artist('1mYsTxnqsietFxj1OgoGbG'), 45 | Artist('6IHhDsmQhMdqnKIb2sE41H'), 46 | Artist('6AiX12wXdXFoGJ2vk8zBjy'), 47 | Artist('6M2wZ9GZgrQXHCFfjv46we'), 48 | Artist('5Pwc4xIPtQLFEnJriah9YJ'), 49 | ], no_of_tracks_required=30 50 | ) 51 | } 52 | 53 | 54 | ] 55 | 56 | 57 | # Available categories 58 | # --------------------- 59 | # 1) TopArtist(time_range=TimeRange.long_term, no_of_tracks_required=34) 60 | # 2) TopTracks(time_range=TimeRange.short_term, no_of_tracks_required=22) 61 | # 3) TopMix() 62 | # 4) RecentlyPlayed() 63 | 64 | # 5) Mix( 65 | # [ 66 | # Artist("7jVv8c5Fj3E9VhNjxT4snq"), 67 | # Artist("6M2wZ9GZgrQXHCFfjv46we"), 68 | # Artist("1Xyo4u8uXC1ZmMpatF05PJ"), 69 | # Genre("pop"), 70 | # Track("0HqZX76SFLDz2aW8aiqi7G"), 71 | # ] 72 | # ) 73 | 74 | # In Mix you can mix upto 5 parameter like Artist, Genre and Track. Only 5 parameter are allowed 75 | # Example 76 | # CATEGORY = Mix( 77 | # [ 78 | # Artist("7jVv8c5Fj3E9VhNjxT4snq"), 79 | # Artist("6M2wZ9GZgrQXHCFfjv46we"), 80 | # Artist("1Xyo4u8uXC1ZmMpatF05PJ"), 81 | # Genre("pop"), 82 | # Track("0HqZX76SFLDz2aW8aiqi7G"), 83 | # ] 84 | # ) 85 | 86 | 87 | ### Example Config File: 88 | 89 | # PLAYLISTS = [ 90 | # { 91 | # 'id': '2bx19B2bwgTf20XWQBxlQo', 92 | # 'category': TopMix(time_range=TimeRange.long_term, no_of_tracks_required=30) 93 | # }, 94 | # { 95 | # 'id': '2bx19B2bwgTff20XWQBxlQo', 96 | # 'category': TopArtist(time_range=TimeRange.short_term, no_of_tracks_required=30) 97 | # }, 98 | # { 99 | # 'id': '7sLWB2thlLWFcvuExsc77y', 100 | # 'category': Mix( 101 | # [ 102 | # Artist('7dGJo4pcD2V6oG8kP0tJRR'), 103 | # Artist('1mYsTxnqsietFxj1OgoGbG'), 104 | # Genre('pop') 105 | # ], 106 | # no_of_tracks_required=20 107 | # ) 108 | # }, 109 | # { 110 | # 'id': '23ljfjfwfpwjwjfwjfjfjs', 111 | # 'category': RecentlyPlayed(no_of_tracks_required=23) 112 | # } 113 | # ] 114 | -------------------------------------------------------------------------------- /envs.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import dotenv 4 | 5 | 6 | dotenv.load_dotenv() 7 | 8 | CLIENT_ID = os.getenv("CLIENT_ID") 9 | CLIENT_SECRET = os.getenv("CLIENT_SECRET") 10 | REDIRECT_URI = os.getenv("REDIRECT_URI") 11 | REFRESH_TOKEN = os.getenv("REFRESH_TOKEN") 12 | SCOPES = "playlist-modify-private playlist-modify-public user-top-read user-read-recently-played" -------------------------------------------------------------------------------- /get_refresh_token.py: -------------------------------------------------------------------------------- 1 | # Script to get refresh token from Spotify 2 | 3 | import base64 4 | import urllib.parse 5 | 6 | import requests 7 | 8 | from config import CLIENT_ID, CLIENT_SECRET, REDIRECT_URI, SCOPES 9 | 10 | authorization_url = f"https://accounts.spotify.com/authorize?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={urllib.parse.quote_plus(SCOPES)}" 11 | 12 | print() 13 | print(authorization_url) 14 | print() 15 | print("Open url in the browser for authorization") 16 | print() 17 | print( 18 | "After authorization, Spotify will redirect you to another URL with authorization code. Copy the authorization code from URL and paste it below to get access token. Copy all tetters after `code=` in URL string" 19 | ) 20 | print() 21 | 22 | authorization_code = input("Authorization code: ") 23 | 24 | auth_header = f"{CLIENT_ID}:{CLIENT_SECRET}" 25 | auth_header_b64 = base64.b64encode(auth_header.encode("ascii")).decode("ascii") 26 | 27 | headers = {"Authorization": f"Basic {auth_header_b64}"} 28 | 29 | data = { 30 | "grant_type": "authorization_code", 31 | "code": authorization_code, 32 | "redirect_uri": REDIRECT_URI, 33 | } 34 | 35 | response = requests.post( 36 | "https://accounts.spotify.com/api/token", headers=headers, data=data 37 | ) 38 | response_data = response.json() 39 | 40 | print() 41 | print(f"Refresh token: {response_data['refresh_token']}") 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | async-timeout==4.0.2 2 | certifi==2022.12.7 3 | charset-normalizer==3.1.0 4 | idna==3.4 5 | python-dotenv==1.0.0 6 | redis==4.5.4 7 | requests==2.29.0 8 | six==1.16.0 9 | spotipy==2.23.0 10 | urllib3==1.26.15 11 | -------------------------------------------------------------------------------- /update.py: -------------------------------------------------------------------------------- 1 | import spotipy 2 | from spotipy.oauth2 import SpotifyOAuth 3 | 4 | from config import PLAYLISTS 5 | from envs import * 6 | 7 | sp = SpotifyOAuth( 8 | client_id=CLIENT_ID, 9 | client_secret=CLIENT_SECRET, 10 | redirect_uri=REDIRECT_URI, 11 | scope=SCOPES, 12 | ) 13 | 14 | token = sp.refresh_access_token(REFRESH_TOKEN) 15 | access_token = token["access_token"] 16 | 17 | spotify = spotipy.Spotify(auth=access_token) 18 | 19 | for playlist in PLAYLISTS: 20 | 21 | uris = playlist['category']._get_tracks(spotify) 22 | 23 | spotify.playlist_replace_items(playlist['id'], uris) 24 | print("Done") 25 | --------------------------------------------------------------------------------