mirror of
https://github.com/Alvin-Zilverstand/novatorem.git
synced 2026-03-06 11:07:09 +01:00
Dynamic readme
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
venv
|
||||||
|
__pycache__
|
||||||
|
.cache*
|
||||||
|
env
|
||||||
|
.env
|
||||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Aphelion | Perihelion
|
||||||
|
:-------------------------:|-------
|
||||||
|
[](https://open.spotify.com/user/omnitenebris)<br>|<div style="text-align: justify">Software Developer currently working on cool projects at [Cast](https://blacktrax.cast-soft.com/showcase/). Everything here is under the MIT License, [info here](https://choosealicense.com/licenses/mit/)!</div><br><p align="center">[](https://novac.dev)</center> [](https://mailhide.io/e/5ck1H)<br>[](https://open.spotify.com/user/omnitenebris) [](https://github.com/novatorem)</p>
|
||||||
|
|
||||||
|
[//]: <> (The ` ` is to have Aphelion take up more space)
|
||||||
44
SetUp.md
Normal file
44
SetUp.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Set Up Guide
|
||||||
|
|
||||||
|
## Spotify
|
||||||
|
|
||||||
|
* Create a [Spotify Application](https://developer.spotify.com/dashboard/applications)
|
||||||
|
* Put aside:
|
||||||
|
* `Client ID`
|
||||||
|
* `Client Secret`
|
||||||
|
* Click on **Edit Settings**
|
||||||
|
* In **Redirect URIs**:
|
||||||
|
* Add `http://localhost/callback/`
|
||||||
|
|
||||||
|
## Refresh Token
|
||||||
|
|
||||||
|
* Navigate to the following URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://accounts.spotify.com/authorize?client_id={SPOTIFY_CLIENT_ID}&response_type=code&scope=user-read-currently-playing,user-read-recently-played&redirect_uri=http://localhost/callback/
|
||||||
|
```
|
||||||
|
|
||||||
|
* After logging in, save the {CODE} portion of: `http://localhost/callback/?code={CODE}`
|
||||||
|
|
||||||
|
* Create a string combining `{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}` (e.g. `5n7o4v5a3t7o5r2e3m1:5a8n7d3r4e2w5n8o2v3a7c5`) and encode into [Base64](https://www.base64encode.org/).
|
||||||
|
|
||||||
|
* Then run a [curl command](https://httpie.org/run) in the form of:
|
||||||
|
```sh
|
||||||
|
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Basic {BASE64}" -d "grant_type=authorization_code&redirect_uri=http://localhost/callback/&code={CODE}" https://accounts.spotify.com/api/token
|
||||||
|
```
|
||||||
|
|
||||||
|
* Save the Refresh token
|
||||||
|
|
||||||
|
## Vercel
|
||||||
|
|
||||||
|
* Register on [Vercel](https://vercel.com/)
|
||||||
|
|
||||||
|
* Create project linked to your github repo
|
||||||
|
|
||||||
|
* Add System Variables:
|
||||||
|
* `https://vercel.com/<YourName>/<ProjectName>/settings/environment-variables`
|
||||||
|
* `SPOTIFY_REFRESH_TOKEN`
|
||||||
|
* `SPOTIFY_CLIENT_ID`
|
||||||
|
* `SPOTIFY_SECRET_ID`
|
||||||
|
|
||||||
|
* Deploy!
|
||||||
3
api/requirements.txt
Normal file
3
api/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
flask==1.1.2
|
||||||
|
requests==2.24.0
|
||||||
|
python-dotenv==0.14.0
|
||||||
120
api/spotify-playing.py
Normal file
120
api/spotify-playing.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from flask import Flask, Response, jsonify, render_template
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
from dotenv import load_dotenv, find_dotenv
|
||||||
|
load_dotenv(find_dotenv())
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
|
||||||
|
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
||||||
|
SPOTIFY_SECRET_ID = os.getenv("SPOTIFY_SECRET_ID")
|
||||||
|
SPOTIFY_REFRESH_TOKEN = os.getenv("SPOTIFY_REFRESH_TOKEN")
|
||||||
|
|
||||||
|
# scope user-read-currently-playing/user-read-recently-played
|
||||||
|
SPOTIFY_URL_REFRESH_TOKEN = "https://accounts.spotify.com/api/token"
|
||||||
|
SPOTIFY_URL_NOW_PLAYING = "https://api.spotify.com/v1/me/player/currently-playing"
|
||||||
|
SPOTIFY_URL_RECENTLY_PLAY = "https://api.spotify.com/v1/me/player/recently-played?limit=10"
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def getAuth():
|
||||||
|
return b64encode(f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_SECRET_ID}".encode()).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
def refreshToken():
|
||||||
|
data = {
|
||||||
|
"grant_type": "refresh_token",
|
||||||
|
"refresh_token": SPOTIFY_REFRESH_TOKEN,
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {"Authorization": "Basic {}".format(getAuth())}
|
||||||
|
|
||||||
|
response = requests.post(SPOTIFY_URL_REFRESH_TOKEN, data=data, headers=headers)
|
||||||
|
return response.json()["access_token"]
|
||||||
|
|
||||||
|
def recentlyPlayed():
|
||||||
|
token = refreshToken()
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
response = requests.get(SPOTIFY_URL_RECENTLY_PLAY, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def nowPlaying():
|
||||||
|
|
||||||
|
token = refreshToken()
|
||||||
|
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
response = requests.get(SPOTIFY_URL_NOW_PLAYING, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def barGen(barCount):
|
||||||
|
barCSS = ""
|
||||||
|
left = 1
|
||||||
|
for i in range(1, barCount + 1):
|
||||||
|
anim = random.randint(1000, 1350)
|
||||||
|
barCSS += ".bar:nth-child({}) {{ left: {}px; animation-duration: {}ms; }}".format(
|
||||||
|
i, left, anim
|
||||||
|
)
|
||||||
|
left += 4
|
||||||
|
|
||||||
|
return barCSS
|
||||||
|
|
||||||
|
def loadImageB64(url):
|
||||||
|
resposne = requests.get(url)
|
||||||
|
return b64encode(resposne.content).decode("ascii")
|
||||||
|
|
||||||
|
def makeSVG(data):
|
||||||
|
barCount = 85
|
||||||
|
contentBar = "".join(["<div class='bar'></div>" for i in range(barCount)])
|
||||||
|
barCSS = barGen(barCount)
|
||||||
|
|
||||||
|
if data == {}:
|
||||||
|
content_bar = ""
|
||||||
|
recent_plays = recentlyPlayed()
|
||||||
|
size_recent_play = len(recent_plays["items"])
|
||||||
|
idx = random.randint(0, size_recent_play - 1)
|
||||||
|
item = recent_plays["items"][idx]["track"]
|
||||||
|
else:
|
||||||
|
item = data["item"]
|
||||||
|
|
||||||
|
img = loadImageB64(item["album"]["images"][1]["url"])
|
||||||
|
artistName = item["artists"][0]["name"].replace("&", "&")
|
||||||
|
songName = item["name"].replace("&", "&")
|
||||||
|
|
||||||
|
dataDict = {
|
||||||
|
"content_bar": contentBar,
|
||||||
|
"css_bar": barCSS,
|
||||||
|
"artist_name": artistName,
|
||||||
|
"song_name": songName,
|
||||||
|
"img": img,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template("spotify.html.j2", **dataDict)
|
||||||
|
|
||||||
|
@app.route("/", defaults={"path": ""})
|
||||||
|
@app.route("/<path:path>")
|
||||||
|
def catch_all(path):
|
||||||
|
|
||||||
|
data = nowPlaying()
|
||||||
|
svg = makeSVG(data)
|
||||||
|
|
||||||
|
resp = Response(svg, mimetype="image/svg+xml")
|
||||||
|
resp.headers["Cache-Control"] = "s-maxage=1"
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
106
api/templates/preview.html
Normal file
106
api/templates/preview.html
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" class="container">
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.art {
|
||||||
|
float: left;
|
||||||
|
width: 33.33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: #121212;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #53b14f;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-play {
|
||||||
|
color: #ff1616;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #b3b3b3;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bars {
|
||||||
|
height: 30px;
|
||||||
|
margin: -20px 0 0 0px;
|
||||||
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
background: #53b14f;
|
||||||
|
bottom: 1px;
|
||||||
|
height: 3px;
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
animation: sound 0ms -800ms linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sound {
|
||||||
|
0% {
|
||||||
|
opacity: .35;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div class="main">
|
||||||
|
<a class="art" href="{}" target="_BLANK">
|
||||||
|
<center>
|
||||||
|
<img src="data:image/png;base64, {{img}}" width="200" height="200" class="cover" />
|
||||||
|
</center>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="text">
|
||||||
|
<div class="artist">Artist</div>
|
||||||
|
<div class="song">Song</div>
|
||||||
|
<div id="bars"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
122
api/templates/spotify.html.j2
Normal file
122
api/templates/spotify.html.j2
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<svg width="480" height="133" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<foreignObject width="480" height="133">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" class="container">
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px 10px 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playing {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #53b14f;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.not-play {
|
||||||
|
color: #ff1616;
|
||||||
|
}
|
||||||
|
|
||||||
|
.art {
|
||||||
|
float: left;
|
||||||
|
width: 27%;
|
||||||
|
margin-left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
width: 71%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #666;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #b3b3b3;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 100px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bars {
|
||||||
|
height: 30px;
|
||||||
|
bottom: 23px;
|
||||||
|
margin: -20px 0 0 0px;
|
||||||
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
background: #1DB954cc;
|
||||||
|
bottom: 1px;
|
||||||
|
height: 3px;
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
animation: sound 0ms -800ms linear infinite alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sound {
|
||||||
|
0% {
|
||||||
|
opacity: .35;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{css_bar|safe}}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% if song_name %}
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<a class="art" href="{}" target="_BLANK">
|
||||||
|
<center>
|
||||||
|
<img src="data:image/png;base64, {{img}}" class="cover" />
|
||||||
|
</center>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="text">
|
||||||
|
<div class="song">{{song_name}}</div>
|
||||||
|
<div class="artist">{{artist_name}}</div>
|
||||||
|
<div id="bars">
|
||||||
|
{{content_bar|safe}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<div class="playing not-play">Nothing playing on Spotify</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
Reference in New Issue
Block a user