mirror of
https://github.com/Alvin-Zilverstand/novatorem.git
synced 2026-03-06 02:57:12 +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