Browse Source

Switch to python flask

add-license-1
novatorem 5 years ago
parent
commit
fde431e7b3
14 changed files with 271 additions and 607 deletions
  1. +4
    -3
      .gitignore
  2. +1
    -5
      README.md
  3. +0
    -47
      api/now-playing.ts
  4. +3
    -0
      api/requirements.txt
  5. +153
    -0
      api/spotify-playing.py
  6. +110
    -0
      api/templates/spotify.html.j2
  7. +0
    -162
      components/NowPlaying.tsx
  8. +0
    -27
      components/ReadmeImg.tsx
  9. +0
    -51
      components/Text.tsx
  10. +0
    -216
      package-lock.json
  11. +0
    -31
      package.json
  12. +0
    -10
      tsconfig.json
  13. +0
    -49
      utils/spotify.ts
  14. +0
    -6
      vercel.json

+ 4
- 3
.gitignore View File

@ -1,4 +1,5 @@
.vercel
venv
__pycache__
.cache*
env
.env
node_modules
.DS_STORE

+ 1
- 5
README.md View File

@ -1,5 +1 @@
**Now Playing**
<a href="https://novatorem.vercel.app/now-playing?open">
<img src="https://novatorem.vercel.app/now-playing" width="256" height="64" alt="Now Playing">
</a>
![Spotify](https://novatorem.vercel.app/api/spotify-playing)

+ 0
- 47
api/now-playing.ts View File

@ -1,47 +0,0 @@
import { renderToString } from 'react-dom/server';
import { NowRequest, NowResponse } from "@vercel/node";
import { decode } from 'querystring';
import { nowPlaying } from "../utils/spotify";
import { Player } from '../components/NowPlaying';
export default async function (req: NowRequest, res: NowResponse) {
const { item = {}, is_playing: isPlaying = false, progress_ms: progress = 0 } = await nowPlaying();
const params = decode(req.url.split('?')[1]) as any;
if (params && typeof params.open !== 'undefined') {
if (item && item.external_urls) {
res.writeHead(302, {
Location: item.external_urls.spotify,
});
return res.end();
}
return res.status(200).end();
}
res.setHeader("Content-Type", "image/svg+xml");
// res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
res.setHeader("Cache-Control", "no-cache");
const {
duration_ms: duration,
name: track,
} = item;
const { images = [] } = item.album || {};
const cover = images[images.length - 1]?.url;
let coverImg = null;
if (cover) {
const buff = await(await fetch(cover)).arrayBuffer();
coverImg = `data:image/jpeg;base64,${Buffer.from(buff).toString(
"base64"
)}`;
}
const artist = (item.artists || []).map(({ name }) => name).join(', ');
const text = renderToString(
Player({ cover: coverImg, artist, track, isPlaying, progress, duration })
);
return res.status(200).send(text);
}

+ 3
- 0
api/requirements.txt View File

@ -0,0 +1,3 @@
flask==1.1.2
requests==2.24.0
python-dotenv==0.14.0

+ 153
- 0
api/spotify-playing.py View File

@ -0,0 +1,153 @@
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
print("Starting Server")
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 get_authorization():
return b64encode(f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_SECRET_ID}".encode()).decode("ascii")
def refresh_token():
data = {
"grant_type": "refresh_token",
"refresh_token": SPOTIFY_REFRESH_TOKEN,
}
headers = {"Authorization": "Basic {}".format(get_authorization())}
response = requests.post(SPOTIFY_URL_REFRESH_TOKEN, data=data, headers=headers)
repsonse_json = response.json()
return repsonse_json["access_token"]
def get_recently_play():
token = refresh_token()
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(SPOTIFY_URL_RECENTLY_PLAY, headers=headers)
if response.status_code == 204:
return {}
repsonse_json = response.json()
return repsonse_json
def get_now_playing():
token = refresh_token()
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(SPOTIFY_URL_NOW_PLAYING, headers=headers)
if response.status_code == 204:
return {}
repsonse_json = response.json()
return repsonse_json
def generate_css_bar(num_bar=75):
css_bar = ""
left = 1
for i in range(1, num_bar + 1):
anim = random.randint(350, 500)
css_bar += ".bar:nth-child({}) {{ left: {}px; animation-duration: {}ms; }}".format(
i, left, anim
)
left += 4
return css_bar
def load_image_b64(url):
resposne = requests.get(url)
return b64encode(resposne.content).decode("ascii")
def make_svg(data):
height = 445
num_bar = 75
title_text = "Now playing"
content_bar = "".join(["<div class='bar'></div>" for i in range(num_bar)])
css_bar = generate_css_bar(num_bar)
if data == {}:
# Get recently play
title_text = "Currently playing"
content_bar = ""
recent_plays = get_recently_play()
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 = load_image_b64(item["album"]["images"][1]["url"])
artist_name = item["artists"][0]["name"]
song_name = item["name"]
url = item["external_urls"]["spotify"]
rendered_data = {
"height": height,
"num_bar": num_bar,
"content_bar": content_bar,
"css_bar": css_bar,
"title_text": title_text,
"artist_name": artist_name,
"song_name": song_name,
"content_bar": content_bar,
"img": img,
}
return render_template("spotify.html.j2", **rendered_data)
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def catch_all(path):
data = get_now_playing()
svg = make_svg(data)
resp = Response(svg, mimetype="image/svg+xml")
resp.headers["Cache-Control"] = "s-maxage=1"
return resp
if __name__ == "__main__":
app.run(debug=True)

+ 110
- 0
api/templates/spotify.html.j2 View File

@ -0,0 +1,110 @@
<svg width="320" height="{{height}}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<foreignObject width="320" height="{{height}}">
<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;
}
.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: 20px;
color: #fff;
text-align: center;
margin-top: 5px;
}
.song {
font-size: 16px;
color: #b3b3b3;
text-align: center;
margin-top: 5px;
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;
}
}
{{css_bar|safe}}
</style>
{% if song_name %}
<div class="playing">
{{title_text}} on
<img class="logo"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAE5ElEQVRYR81WS08bVxQ+dzweY48fQCCBllJDmqpRSx6gKizSiAUbILZjA21KVEhF20hNlLb/IKu2m3QRKaqQogIRkAXhYQxFSJGCQqVKrVSBQtpCFqEQkrQhxnb8Yjxzp7oTmdjjsWd4LHIlLzz3PL57zvedexG8Qgu9Qlhg22Bqumr0+/L2VdAMOpBvtdgCoedBnhPvsw/YpcFLg9x2DrklMA19DVajkf4B0dQnAMDkSJgQedwfWot9fevcraBWYJrA1HXX5RUWWG8jhGq1Bk7aiVj8PfGfWOc754uq+aqCOXXT8bFOTw2oBVLb5zmhw9s6fj2XXU4wniFnF6LRF2qJtO6LPPQMN3s/zWafFcxuA0kCwILYO+IZO6sESBHMbrUmWwVwQjw70jLWK9/PAEPIuqfQFtNa+u3acU8wKyd1BhjPqPNXJdVgHkP8WRwiqxGI/RsDLsSBiEXQGXRgKDCAqcQk/RgbA4hS1QXx/WPYPVaTepg0LzJHTBZmcy5wQQ5mL89CeCUMIGqvAQFjLjeD/aQdiqqLsoKLh/nCiTMT68nIaWA8Q45riKY6k5szF2YgEU5oR5HFkn2dhUNfHQLjXmOaBRZw34jHRwaotNLANHtdG6mTdaF3AVZvrwJCCNgyFoqOFIHtgA2MxUbQGXXSdyxg4MM8RJ9EYf2vdfDf80PsaUyxkqSNtd+nzU1+yOXVZ4Ahd429pCzjTiG80MIBeVH4GA/Lk8uw/PMyEL4lV9XFKiiuLt78H/2Hy5u8OEmK8LIyjb2Nbxvz9Qu5eiIKokTcuD8OfISXTk+zNOQV5r0grk6ZuIGFAMxdnpMIf+LHE0Dpqc00XCT+nq9t6l4aGOeNpia9iR6XgyHKuXv1rjYSI5BaWN5QDqUflAJFv0ya7ZBCHLeMfuQbSgPTPnG6LcLH+uVOM+dnIBFRIHGyCDlURgh7+JvDYCo1ZS04qzd3Xm/s/0lTZQJ/B2BxYBHK6sug+Ggx6M16Ge1BahdRHSHvytQKhB6E0pITMLXfKV/4QgK3jLbIKqOFM1vROFHW/NX5zdFw7NtjwL7GZoSIB/mqifaJ+bTKtF5qZfBRTmK14hIBuOccRB5GIPIoAhv+DeDjPDBWBsxvmMFaaQVDviGjaut/rkNgMQAVpyoUwyqqiVg2e11E2pu6l7xFgDtf3gEiVS2LgNv/4X4oPV6qxVx5zhBPz5CjG9FU2vUuiiJMd05LcrRWWKHg3QKwvGkBJv/FHUQkHl4Ow9rsGhAJp86USncl2F32rKCwgG+MeHxtSYO0wVDfVW+zlbABLUfKLlUBFvsW4fEvjyWTg50HJZkrrfDT+J6pz6b8imCk6ow4f0MUen8ngKTuCiIsjS+B3WFXnOAiFueG3WNHUvNkjExHl8PElFCRnYJR83/2MGiZPj8dzgmGbLoGT7bTjC7jJaaWQOs+zwmfe1vHr8ntc7yBXd2IBsW3qtakSnYixgPDbt8Zpb2cTzL3sLOH0qGOnSRP9c0FhNipvg/dN5s6KD3ds1NA2Vqjyhl5YkJq/V40gyhUvVVQRDX+R6HjcrJuuU1yh6b+pgLGSF2hdNRpAKBzAOOxgAejfu5C6hxRO4hqm7IFaLjSYKCL8Fs6HfOO1WK1haKhYCKKF3AA30++3NSSa1bTVgPthv22K7MbyeUx/gfIiuIzZiZJFQAAAABJRU5ErkJggg==" />
</div>
<div class="artist">{{artist_name}}</div>
<div class="song">{{song_name}}</div>
<div id='bars'>
{{content_bar|safe}}
</div>
<a href="{}" target="_BLANK">
<center>
<img src="data:image/png;base64, {{img}}" width="300" height="300" class="cover" />
</center>
</a>
{% else %}
<div class="playing not-play">Nothing playing on Spotify</div>
{% endif %}
</div>
</foreignObject>
</svg>

+ 0
- 162
components/NowPlaying.tsx View File

@ -1,162 +0,0 @@
import React from "react";
import ReadmeImg from "./ReadmeImg";
import Text from "./Text";
export interface Props {
cover?: string;
track: string;
artist: string;
progress: number;
duration: number;
isPlaying: boolean;
}
export const Player: React.FC<Props> = ({
cover,
track,
artist,
progress,
duration,
isPlaying,
}) => {
return (
<ReadmeImg width="256" height="64">
<style>
{`
.paused {
animation-play-state: paused !important;
background: #e1e4e8 !important;
}
img:not([src]) {
content: url("data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==");
border-radius: 6px;
background: #FFF;
border: 1px solid #e1e4e8;
}
p {
display: block;
opacity: 0;
}
.progress-bar {
position: relative;
width: 100%;
height: 4px;
margin: -1px;
border: 1px solid #e1e4e8;
border-radius: 4px;
overflow: hidden;
padding: 2px;
z-index: 0;
}
#progress {
position: absolute;
top: -1px;
left: 0;
width: 100%;
height: 6px;
transform-origin: left center;
background-color: #24292e;
animation: progress ${duration}ms linear;
animation-delay: -${progress}ms;
}
.progress-bar,
#track,
#artist,
#cover {
opacity: 0;
animation: appear 300ms ease-out forwards;
}
#track {
animation-delay: 400ms;
}
#artist {
animation-delay: 500ms;
}
.progress-bar {
animation-delay: 550ms;
margin-top: 4px;
}
#cover {
animation-name: cover-appear;
animation-delay: 300ms;
box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 3px 10px rgba(0,0,0,0.05);
}
#cover:not([src]) {
box-shadow: none;
}
@keyframes cover-appear {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes appear {
from {
opacity: 0;
transform: translateX(-8px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes progress {
from {
transform: scaleX(0)
}
to {
transform: scaleX(1)
}
}
`}
</style>
<div
className={isPlaying ? "disabled" : ""}
style={{
display: "flex",
alignItems: "center",
paddingTop: 8,
paddingLeft: 4,
}}
>
<img id="cover" src={cover ?? null} width="48" height="48" />
<div
style={{
display: "flex",
flex: 1,
flexDirection: "column",
marginTop: -4,
marginLeft: 8,
}}
>
<Text id="track" weight="bold">
{`${track ?? ""} `.trim()}
</Text>
<Text id="artist" color={!track ? "gray" : undefined}>
{artist || "Nothing playing..."}
</Text>
{track && (
<div className="progress-bar">
<div id="progress" className={!isPlaying ? "paused" : ""} />
</div>
)}
</div>
</div>
</ReadmeImg>
);
};

+ 0
- 27
components/ReadmeImg.tsx View File

@ -1,27 +0,0 @@
import React from "react";
const ReadmeImg = ({ width, height, children }) => {
return (
<svg
fill="none"
width={width}
height={height}
viewBox={`0 0 ${width} ${height}`}
xmlns="http://www.w3.org/2000/svg"
>
<foreignObject width={width} height={height}>
<div {...{ xmlns: "http://www.w3.org/1999/xhtml" }}>
<style>{`
* {
margin: 0;
box-sizing: border-box;
}
`}</style>
{children}
</div>
</foreignObject>
</svg>
);
};
export default ReadmeImg;

+ 0
- 51
components/Text.tsx View File

@ -1,51 +0,0 @@
import React from "react";
const sizes = {
default: 14,
small: 12,
};
const colors = {
default: "#24292e",
"gray-light": "#e1e4e8",
gray: "#586069",
"gray-dark": "#24292e",
};
const families = {
default:
"-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji",
mono: "SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace",
};
const weights = {
default: 400,
bold: 600,
};
const Text: React.FC<any> = ({
children = "",
weight = "default",
family = "default",
color = "default",
size = "default",
...props
}) => {
return (
<p
style={{
whiteSpace: "pre",
fontSize: `${sizes[size]}px`,
lineHeight: 1.5,
fontFamily: families[family],
color: colors[color],
fontWeight: weights[weight],
}}
{...props}
>
{children}
</p>
);
};
export default Text;

+ 0
- 216
package-lock.json View File

@ -1,216 +0,0 @@
{
"name": "novatorem",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "14.0.22",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.22.tgz",
"integrity": "sha512-emeGcJvdiZ4Z3ohbmw93E/64jRzUHAItSHt8nF7M4TGgQTiWqFVGB8KNpLGFmUHmHLvjvBgFwVlqNcq+VuGv9g==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
"dev": true
},
"@types/react": {
"version": "16.9.42",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.42.tgz",
"integrity": "sha512-iGy6HwfVfotqJ+PfRZ4eqPHPP5NdPZgQlr0lTs8EfkODRBV9cYy8QMKcC9qPCe1JrESC1Im6SrCFR6tQgg74ag==",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-dom": {
"version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
"integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@vercel/node": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@vercel/node/-/node-1.7.2.tgz",
"integrity": "sha512-XV5lrLC+K/cxsaFj8H2OoGu1zliOqnxcrOnPInI8HmQjR/Tztt+0nzgpt+7sx8wXcrib0Nu7lK303jP7VjSETw==",
"dev": true,
"requires": {
"@types/node": "*",
"ts-node": "8.9.1",
"typescript": "3.9.3"
},
"dependencies": {
"typescript": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz",
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==",
"dev": true
}
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
"dev": true
},
"csstype": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz",
"integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==",
"dev": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"isomorphic-unfetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.0.0.tgz",
"integrity": "sha512-V0tmJSYfkKokZ5mgl0cmfQMTb7MLHsBMngTkbLY0eXvKqiVRRoZP04Ly+KhKrJfKtzC9E6Pp15Jo+bwh7Vi2XQ==",
"requires": {
"node-fetch": "^2.2.0",
"unfetch": "^4.0.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
"requires": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.8.1"
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"react": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
},
"react-dom": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz",
"integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.1"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"scheduler": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"source-map-support": {
"version": "0.5.19",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"ts-node": {
"version": "8.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.9.1.tgz",
"integrity": "sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
}
},
"typescript": {
"version": "3.9.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
"integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==",
"dev": true
},
"unfetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}

+ 0
- 31
package.json View File

@ -1,31 +0,0 @@
{
"name": "novatorem",
"private": true,
"version": "1.0.0",
"description": "Github profile",
"main": "index.js",
"dependencies": {
"isomorphic-unfetch": "^3.0.0",
"querystring": "^0.2.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"typescript": "^3.9.6"
},
"devDependencies": {
"@types/node": "^14.0.22",
"@types/react": "^16.9.42",
"@types/react-dom": "^16.9.8",
"@vercel/node": "^1.7.2"
},
"repository": {
"type": "git",
"url": "git+https://github.com/novatorem/novatorem.git"
},
"keywords": [],
"author": "Andrew Novac",
"license": "MIT",
"bugs": {
"url": "https://github.com/novatorem/novatorem/issues"
},
"homepage": "https://github.com/novatorem/novatorem#readme"
}

+ 0
- 10
tsconfig.json View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2017",
"jsx": "react",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}

+ 0
- 49
utils/spotify.ts View File

@ -1,49 +0,0 @@
import fetch from "isomorphic-unfetch";
import { stringify } from "querystring";
const {
SPOTIFY_CLIENT_ID: client_id,
SPOTIFY_CLIENT_SECRET: client_secret,
SPOTIFY_REFRESH_TOKEN: refresh_token,
} = process.env;
const basic = Buffer.from(`${client_id}:${client_secret}`).toString(
"base64"
);
const Authorization = `Basic ${basic}`;
async function getAuthorizationToken() {
const url = new URL("https://accounts.spotify.com/api/token");
const body = stringify({
grant_type: "refresh_token",
refresh_token,
});
const response = await fetch(`${url}`, {
method: "POST",
headers: {
Authorization,
"Content-Type": "application/x-www-form-urlencoded",
},
body,
}).then((r) => r.json());
return `Bearer ${response.access_token}`;
}
const NOW_PLAYING_ENDPOINT = `https://api.spotify.com/v1/me/player/currently-playing`;
export async function nowPlaying () {
const Authorization = await getAuthorizationToken();
const response = await fetch(NOW_PLAYING_ENDPOINT, {
headers: {
Authorization,
},
});
const { status } = response;
if (status === 204) {
return {};
} else if (status === 200) {
const data = await response.json();
return data;
}
}

+ 0
- 6
vercel.json View File

@ -1,6 +0,0 @@
{
"version": 2,
"rewrites": [
{ "source": "/now-playing", "destination": "/api/now-playing" }
]
}

Loading…
Cancel
Save