Compare commits
7 Commits
b136285c00
...
main
Author | SHA1 | Date | |
---|---|---|---|
aaa70c5428
|
|||
45945732c9
|
|||
7b2ea2a93f
|
|||
e168b6f2df
|
|||
2d66b0a200
|
|||
ec3ab8b48b
|
|||
1a7b909efd
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,4 +3,3 @@ todo
|
|||||||
env
|
env
|
||||||
__pycache__
|
__pycache__
|
||||||
redis-data
|
redis-data
|
||||||
docker-compose.yml
|
|
||||||
|
@@ -18,13 +18,23 @@ class PlaylistUpdater:
|
|||||||
|
|
||||||
def _update_tracks(self):
|
def _update_tracks(self):
|
||||||
self._tracks = []
|
self._tracks = []
|
||||||
headers = { 'Authorization': f"Bearer {self._auth_token}" }
|
|
||||||
rnext = f"{self._spotify_api_endpoint}/me/tracks?limit=50"
|
rnext = f"{self._spotify_api_endpoint}/me/tracks?limit=50"
|
||||||
while rnext is not None:
|
while rnext is not None:
|
||||||
r = rq.get(rnext, headers = headers).json()
|
r = self._fetch(rnext);
|
||||||
self._tracks += r['items']
|
self._tracks += r['items']
|
||||||
rnext = r['next']
|
rnext = r['next']
|
||||||
|
|
||||||
|
def _fetch(self, url):
|
||||||
|
headers = { 'Authorization': f"Bearer {self._auth_token}" }
|
||||||
|
while True:
|
||||||
|
r = rq.get(f"{url}", headers=headers)
|
||||||
|
if 200 <= r.status_code <= 299:
|
||||||
|
return r.json()
|
||||||
|
# slow down if spotify is applying rate limiting
|
||||||
|
if r.status_code == 429:
|
||||||
|
time.sleep(30)
|
||||||
|
# don't try again in a crazy fast loop to avoid rate limits
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
def update_playlist(self, playlist_id):
|
def update_playlist(self, playlist_id):
|
||||||
pltracks = []
|
pltracks = []
|
||||||
@@ -34,12 +44,12 @@ class PlaylistUpdater:
|
|||||||
headers = { 'Authorization': f"Bearer {self._auth_token}" }
|
headers = { 'Authorization': f"Bearer {self._auth_token}" }
|
||||||
added_at_dict = {}
|
added_at_dict = {}
|
||||||
|
|
||||||
r = rq.get(f"{self._spotify_api_endpoint}/playlists/{playlist_id}?fields=tracks,snapshot_id", headers = headers).json()
|
r = self._fetch(f"{self._spotify_api_endpoint}/playlists/{playlist_id}?fields=tracks,snapshot_id")
|
||||||
snapshot_id = r['snapshot_id']
|
snapshot_id = r['snapshot_id']
|
||||||
pltracks = [t['track']['id'] for t in r['tracks']['items']]
|
pltracks = [t['track']['id'] for t in r['tracks']['items']]
|
||||||
rnext = r['tracks']['next']
|
rnext = r['tracks']['next']
|
||||||
while rnext is not None:
|
while rnext is not None:
|
||||||
r = rq.get(rnext, headers = headers).json()
|
r = self._fetch(rnext)
|
||||||
for track in r['items']:
|
for track in r['items']:
|
||||||
pltracks.append(track['track']['id'])
|
pltracks.append(track['track']['id'])
|
||||||
rnext = r['next']
|
rnext = r['next']
|
||||||
|
@@ -2,6 +2,7 @@ version: '3.9'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
|
hostname: heartbeats-web
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./web/Dockerfile
|
dockerfile: ./web/Dockerfile
|
||||||
@@ -14,12 +15,14 @@ services:
|
|||||||
- BASE_URL=http://localhost:8464
|
- BASE_URL=http://localhost:8464
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
|
hostname: heartbeats-redis
|
||||||
image: redis
|
image: redis
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- './redis-data:/data'
|
- './redis-data:/data'
|
||||||
|
|
||||||
populater:
|
populater:
|
||||||
|
hostname: heartbeats-populater
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./populater/Dockerfile
|
dockerfile: ./populater/Dockerfile
|
2
populater/src/app.py
Executable file → Normal file
2
populater/src/app.py
Executable file → Normal file
@@ -16,7 +16,7 @@ CLIENT_SECRET = os.environ.get('CLIENT_SECRET', None)
|
|||||||
if CLIENT_SECRET is None:
|
if CLIENT_SECRET is None:
|
||||||
raise ValueError("CLIENT_SECRET cannot be None, set using environment variable")
|
raise ValueError("CLIENT_SECRET cannot be None, set using environment variable")
|
||||||
|
|
||||||
db = Db('redis', 6379, 0, CLIENT_ID, CLIENT_SECRET)
|
db = Db('heartbeats-redis', 6379, 0, CLIENT_ID, CLIENT_SECRET)
|
||||||
|
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
|
16
readme.md
Normal file
16
readme.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# :musical_note: heartbeats :saxophone:
|
||||||
|
|
||||||
|
> Maintain a (potentially) public list of your recently liked songs so people know how cool you are
|
||||||
|
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
|
||||||
|
0. Create a spotify developer account and create a new app
|
||||||
|
0. Create a `.env` file with your spotify secrets:
|
||||||
|
|
||||||
|
```
|
||||||
|
CLIENT_ID=<client_id>
|
||||||
|
CLIENT_SECRET=<client_secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
0. Run `docker compose up -d` to start
|
@@ -28,7 +28,7 @@ BASE_URL = os.environ.get('BASE_URL', None)
|
|||||||
if BASE_URL is None:
|
if BASE_URL is None:
|
||||||
raise ValueError("BASE_URL cannot be None, set using environtment variable")
|
raise ValueError("BASE_URL cannot be None, set using environtment variable")
|
||||||
|
|
||||||
db = Db('redis', 6379, 0, CLIENT_ID, CLIENT_SECRET)
|
db = Db('heartbeats-redis', 6379, 0, CLIENT_ID, CLIENT_SECRET)
|
||||||
|
|
||||||
SPOTIFY_AUTHORIZE_ENDPOINT = os.environ.get('SPOTIFY_AUTHORIZE_ENDPOINT', 'https://accounts.spotify.com/authorize?')
|
SPOTIFY_AUTHORIZE_ENDPOINT = os.environ.get('SPOTIFY_AUTHORIZE_ENDPOINT', 'https://accounts.spotify.com/authorize?')
|
||||||
SPOTIFY_TOKEN_ENDPOINT = os.environ.get('SPOTIFY_AUTHORIZE_ENDPOINT', 'https://accounts.spotify.com/api/token')
|
SPOTIFY_TOKEN_ENDPOINT = os.environ.get('SPOTIFY_AUTHORIZE_ENDPOINT', 'https://accounts.spotify.com/api/token')
|
||||||
|
@@ -3,12 +3,16 @@
|
|||||||
<style>
|
<style>
|
||||||
@import url("https://styles.alv.cx/colors/gruvbox.css");
|
@import url("https://styles.alv.cx/colors/gruvbox.css");
|
||||||
@import url("https://styles.alv.cx/base.css");
|
@import url("https://styles.alv.cx/base.css");
|
||||||
@import url("https://styles.alv.cx/modules/darkmode.css");
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--light: var(--colorscheme-light);
|
--colorscheme-dark: #000;
|
||||||
|
--fg: var(--colorscheme-light);
|
||||||
|
--fg-lc: var(--colorscheme-light-darker);
|
||||||
|
--bg: var(--colorscheme-dark);
|
||||||
|
--bg-lc: var(--colorscheme-dark-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.messagetypeinfo { background-color: var(--blue); }
|
.messagetypeinfo { background-color: var(--blue); }
|
||||||
.messagetypeerror { background-color: var(--red); }
|
.messagetypeerror { background-color: var(--red); }
|
||||||
.messagetypewarning { background-color: var(--yellow); }
|
.messagetypewarning { background-color: var(--yellow); }
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
</select></label></label> <br><br>
|
</select></label></label> <br><br>
|
||||||
<label for="pname">name: </label>
|
<label for="pname">name: </label>
|
||||||
<input type="text" id="pname" name="pname" placeholder="playlist_name"><br><br>number of tracks/days
|
<input type="text" id="pname" name="pname" placeholder="playlist_name"><br><br>number of tracks/days
|
||||||
<input type="number" id="count" name="count" value="25" style="width: 5em;">
|
<input type="number" id="count" name="count" value="28" style="width: 5em;">
|
||||||
<select id="counttype" name="counttype">
|
<select id="counttype" name="counttype">
|
||||||
<option value="days">days</option>
|
<option value="days">days</option>
|
||||||
<option value="tracks">tracks</option>
|
<option value="tracks">tracks</option>
|
||||||
|
Reference in New Issue
Block a user