{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1764927628,
"owner": "cachix",
"repo": "devenv",
"rev": "247d7027f91368054fb0eefbd755a73d42b66fee",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1764712249,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "3b279e4317ccfa4865356387935310531357d919",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1765016596,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "548fc44fca28a5e81c5d6b846e555e6b9c2a5a3c",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1762808025,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1764580874,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": [
"git-hooks"
]
}
}
},
"root": "root",
"version": 7
}
{ pkgs, lib, config, inputs, ... }: {
env.GREET = "SpotDL devenv";
packages = [pkgs.spotdl pkgs.python313Packages.spotipy pkgs.python313Packages.flask pkgs.openssl];
scripts.hello.exec = ''echo hello from $GREET ! '';
scripts.craftcerts.exec = ''
mkdir -p ssl # Use -p to avoid error if the directory exists
cd ssl
if [ ! -f ../openssl.cnf ]; then
echo "Please create 'openssl.cnf' in the parent directory."
exit 1
fi
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.cert -days 365 -nodes -config ../openssl.cnf >/dev/null
'';
scripts.download.exec = ''
while true; do
url=$(python ./geturl.py) # Capture the output of geturl.py
if [ $? -eq 0 ]; then
spotdl "$url"
break
fi
echo "Error downloading, retrying..."
sleep 2
done
'';
enterShell = ''
hello
craftcerts
download
'';
}
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend
from flask import Flask, request, redirect
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import subprocess
import os
import time
from datetime import datetime
app = Flask(__name__)
client_id = "c0020cc0e05245efb2ddb61b7045e4f2"
client_secret = "2e2e7c98f849403fbacc219c1f01c17d"
redirect_uri = "https://localhost:8888/callback"
sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read")
@app.route('/')
def index():
return "Welcome! Login to Spotify"
@app.route('/login')
def login():
return redirect(sp_oauth.get_authorize_url())
@app.route('/callback')
def callback():
code = request.args.get('code')
token_info = sp_oauth.get_access_token(code)
access_token = token_info['access_token']
sp = spotipy.Spotify(auth=access_token)
print("Fetching liked songs...")
liked_songs = []
results = sp.current_user_saved_tracks()
while results:
for item in results['items']:
liked_songs.append(item['track']['id'])
if results['next']:
results = sp.next(results)
time.sleep(1)
else:
break
playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S")
user_id = sp.me()['id']
new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False)
print(f"Created playlist: {playlist_name}")
for i in range(0, len(liked_songs), 100):
batch = liked_songs[i:i + 100]
sp.playlist_add_items(new_playlist['id'], batch)
print(f"Added {len(batch)} songs...")
time.sleep(1)
playlist_url = new_playlist['external_urls']['spotify']
print(f"Playlist URL: {playlist_url}")
# Write playlist URL for the download script
with open("last_playlist_url.txt", "w") as f:
f.write(playlist_url)
return f"{playlist_url}"
if __name__ == "__main__":
print("Starting Flask server...")
os.execvp('flask', [
'flask',
'run',
'--host=0.0.0.0',
'--port=8888',
'--cert=ssl/server.cert',
'--key=ssl/server.key'
])
from flask import Flask, request, redirect
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import threading
import os
import sys
import time # Import time module
from datetime import datetime
app = Flask(__name__)
client_id = "c0020cc0e05245efb2ddb61b7045e4f2"
client_secret = "2e2e7c98f849403fbacc219c1f01c17d"
redirect_uri = "https://localhost:8888/callback"
sp_oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope="playlist-modify-private user-library-read")
@app.route('/')
def index():
return "Welcome! Login to Spotify"
@app.route('/login')
def login():
return redirect(sp_oauth.get_authorize_url())
@app.route('/callback')
def callback():
code = request.args.get('code')
token_info = sp_oauth.get_access_token(code)
access_token = token_info['access_token']
with open("access_token.txt", "w") as f:
f.write(access_token)
print("Authorization complete. You can close this window.", file=sys.stderr)
return "Authorization complete. You can close this window."
def create_playlist():
if not os.path.exists("access_token.txt"):
print("No access token found.", file=sys.stderr)
return
with open("access_token.txt", "r") as f:
access_token = f.read().strip()
sp = spotipy.Spotify(auth=access_token)
print("Fetching liked songs...", file=sys.stderr)
liked_songs = []
results = sp.current_user_saved_tracks()
while results:
for item in results['items']:
liked_songs.append(item['track']['id'])
if results['next']:
results = sp.next(results)
else:
break
playlist_name = datetime.now().strftime("Liked Songs Collection - %Y-%m-%d %H-%M-%S")
user_id = sp.me()['id']
new_playlist = sp.user_playlist_create(user_id, playlist_name, public=False)
print(f"Created playlist: {playlist_name}", file=sys.stderr)
for i in range(0, len(liked_songs), 100):
batch = liked_songs[i:i + 100]
sp.playlist_add_items(new_playlist['id'], batch)
print(f"Added {len(batch)} songs...", file=sys.stderr)
playlist_url = new_playlist['external_urls']['spotify']
print(f"Playlist URL: {playlist_url}", file=sys.stderr)
return playlist_url
def run_flask():
app.run(ssl_context=('ssl/server.cert', 'ssl/server.key'), host="0.0.0.0", port=8888)
def main():
# Start the Flask app in a background thread
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
# Inform the user to authorize access via the URL shown in the terminal
print("Please authorize access via the following URL:", sp_oauth.get_authorize_url())
# Wait until the access token is available
while not os.path.exists("access_token.txt"):
time.sleep(1) # Sleep for a while, then check again
# Now that we have the token, proceed with creating the playlist
playlist_url = create_playlist()
print(f"Playlist URL returned: {playlist_url}")
if __name__ == "__main__":
main()
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = US
ST = California
L = San Francisco
O = Example Company
OU = IT Department
CN = localhost
emailAddress = email@example.com
To implement HTTPS using Let's Encrypt and ngrok while modifying your Flask application, follow these structured steps:
## Step 1: Install and Set Up ngrok
1. **Download ngrok**:
- Go to the [ngrok website](https://ngrok.com/download) and download the appropriate version for your OS.
- Install ngrok by following the instructions on the site.
2. **Authenticate ngrok**:
- Sign up for a free account at ngrok.com.
- After signing up, you’ll receive an authtoken. Run the following command to set it up:
```bash
ngrok config add-authtoken
```
## Step 2: Modify Flask to Accept HTTPS
Update your Flask application to use ngrok to tunnel HTTPS traffic:
1. **Change `run_flask()` to bind to HTTP**:
This is so ngrok can handle the SSL termination. Update the endpoint by removing the `ssl_context`:
```python
def run_flask():
app.run(host="0.0.0.0", port=8888)
```
2. **Expose the Flask app via ngrok**:
You can run ngrok to expose your Flask app by executing:
```bash
ngrok http 8888
```
This will give you a public HTTPS URL that tunnels to your local Flask app.
## Step 3: Modify the Development Environment (devenv.nix)
You’ll need to add the ngrok installation to your `devenv.nix` configuration, if it's not already installed.
1. **Edit `devenv.nix`**:
```nix
{ pkgs ? import {} }:
pkgs.mkShell {
buildInputs = [
pkgs.ngrok
];
shellHook = ''
# Set up ngrok to run automatically
ngrok http 8888 &
'';
}
```
This snippet sets up ngrok to run in the background whenever you start your development environment.
## Step 4: Implement the PKI Chain in `geturl.py`
To implement an HTTPS PKI chain, consider using the Let's Encrypt certificate. For local development, you can't use Let's Encrypt directly without a publicly reachable domain, but you can still prepare your code as if you're using secure connections.
1. **Modify `geturl.py`**:
Assuming your application logic has not changed, implement a function to fetch the ngrok URL:
```python
import os
import requests
import threading
import time
def get_ngrok_url():
response = requests.get("http://localhost:4040/api/tunnels")
tunnels = response.json()["tunnels"]
for tunnel in tunnels:
if tunnel["name"] == "http":
return tunnel["public_url"]
return None
def create_playlist():
# Your existing code to create a playlist
...
def run_flask():
app.run(host="0.0.0.0", port=8888)
def main():
# Start Flask in a background thread
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
# Wait for ngrok to be ready
time.sleep(5) # Allow some time for ngrok to initialize
ngrok_url = get_ngrok_url()
print(f"Ngrok URL: {ngrok_url}")
print("Please authorize access via the following URL:", sp_oauth.get_authorize_url())
while not os.path.exists("access_token.txt"):
time.sleep(1)
playlist_url = create_playlist()
print(f"Playlist URL returned: {playlist_url}")
if __name__ == "__main__":
main()
```
In this code:
- `get_ngrok_url()` fetches the ngrok tunnel URL dynamically.
- You can then safely use this URL to inform other services of your HTTPS endpoint.
## Summary
This setup will let you run a local Flask application using HTTPS through an ngrok tunnel. You process the playlist creation and output the URL correctly while working in a secure environment. If you later transition to production, you can directly implement Let's Encrypt for your domain, maintaining the same architecture with minimal changes.