Initial commit

This commit is contained in:
Nic
2024-12-07 12:43:57 +01:00
commit 086c50f070
3 changed files with 345 additions and 0 deletions

257
.gitignore vendored Normal file
View File

@@ -0,0 +1,257 @@
# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,windows
# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode,macos,windows
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,macos,windows
.cache-*
*.csv

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# spotify-fetch
Dieses Python-Skript kann genutzt werden, um die eigene Liste aller
Lieblingssongs von Spotify abzufragen. Dazu wird die Library
`spotipy` genutzt, um die Authentisierung gegenüber der Spotify API
zu vereinfachen.
Es werden die `CLIENT_ID`, das `CLIENT_SECRET` und eine `Callback URL` für
die Spotify App benötigt. Die App kann im
[Spotify Developer Portal](https://developer.spotify.com) erstellt werden.
Diese drei Parameter müssen vorab als Environment Variables definiert werden:
```bash
export SPOTIPY_CLIENT_ID='client id'
export SPOTIPY_CLIENT_SECRET='client secret'
export SPOTIPY_CALLBACK_URL='http://localhost:8888/callback'
```
Die Callback URL muss mit der in der App angegebenen Callback URL
übereinstimmen! spotipy fordert zur Eingabe des Callback Tokens auf. Dieser
befindet sich in der sich öffnenden URL nach der Zeichenfolge `callback?`
und kann einfach kopiert werden. (Auf diese Weise wird Spotify ein Webservice
simuliert.)

65
fetch.py Normal file
View File

@@ -0,0 +1,65 @@
import csv
import os
import spotipy
import spotipy.util as util
# get credentials and callback URL from environment variables
# HINT: callback URL has to match Spotify App settings
client_id: str = os.environ.get("SPOTIPY_CLIENT_ID")
client_secret: str = os.environ.get("SPOTIPY_CLIENT_SECRET")
redirect_uri: str = os.environ.get("SPOTIPY_REDIRECT_URI")
# don't edit scope, but edit user name!
scope: str = "user-library-read"
username: str = "hntngprty"
# get user auth token
token: str = util.prompt_for_user_token(
username,
scope,
client_id=client_id,
client_secret=client_secret,
redirect_uri=redirect_uri,
)
# raw responses from Spotify API
items: list[dict] = []
# response elements reduced to (artist, title) tuples
tracks: list[tuple[str, str]] = []
shall_continue: bool = True
i: int = 0
if token:
while shall_continue:
sp = spotipy.Spotify(auth=token)
results = sp.current_user_saved_tracks(offset=i, limit=50)
# break if response is empty
if len(results["items"]) == 0:
break
# add items; increase offset
print(results)
items += results["items"]
i += 50
# reduce API responses to just artists and tracks
for item in items:
track: str = item["track"]
artist: str = track["artists"][0]["name"]
title: str = track["name"]
# add track
tracks.append((artist, title))
# output artist and track
print(f"{artist} - {title}")
# final step: write tracks to tracks.csv file
with open("tracks.csv", "w", encoding="utf8") as file:
writer = csv.writer(file, quotechar='"')
writer.writerow(("Artist", "Title"))
writer.writerows(tracks)