Skip to content

Commit

Permalink
Better README.md and modified self hosted action
Browse files Browse the repository at this point in the history
  • Loading branch information
LuisEnMarroquin committed Aug 24, 2020
1 parent 4ed650c commit 4ec20ca
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 80 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: sh deployment.sh
- uses: LuisEnMarroquin/[email protected]
with:
ORIGIN: spotify.marroquin.dev
SSHKEY: ${{ secrets.SSH_SERVER }}
HOST: production
USER: ${{ secrets.SSH_USER }}
- run: ssh production ls --help
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# history-sync-for-spotify
# History Sync for Spotify

[![Standard](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![GitLab](https://gitlab.com/chimenea/spotify/badges/master/pipeline.svg)](https://gitlab.com/chimenea/spotify/pipelines)
[![GitHub](https://github.com/LuisEnMarroquin/spotify-sync/workflows/Pages/badge.svg)](https://github.com/LuisEnMarroquin/spotify-sync/actions)
[![NotFound](https://github.com/LuisEnMarroquin/spotify-sync/workflows/GitHub%20Pages/badge.svg)](https://github.com/LuisEnMarroquin/spotify-sync/actions)
[![NotFound](https://github.com/LuisEnMarroquin/spotify-sync/workflows/Self%20Hosted/badge.svg)](https://github.com/LuisEnMarroquin/spotify-sync/actions)

Sync your Spotify's playback history every hour

* This is a mirror or [gitlab.com/chimenea/spotify](https://gitlab.com/chimenea/spotify)
* Initially folked from [github.com/spotify/web-api-auth-examples](https://github.com/spotify/web-api-auth-examples)
* This project uses Authorization Code Flow to [authenticate against the Spotify Web API](https://developer.spotify.com/web-api/authorization-guide)

## Installation

These examples run on Node.js. On this [website](http://www.nodejs.org) you can find instructions on how to install it.
These examples run on Node.js. On this [website](http://www.nodejs.org) you can find instructions on how to install it

Once installed, install yarn globally
Once node is installed, install yarn globally
```shell
npm install -g yarn
```
Expand All @@ -26,24 +24,24 @@ yarn install --pure-lockfile

## Use your credentials

You will need to register your app and get your own credentials from the Spotify for Developers Dashboard.
You will need to register your app and get your own credentials from the Spotify for Developers Dashboard

To do so, go to [your Spotify for Developers Dashboard](https://developer.spotify.com/dashboard) and create your application.
To do so, go to [your Spotify for Developers Dashboard](https://developer.spotify.com/dashboard) and create your application

For the examples, we registered these Redirect URIs:

* http://localhost:8888 (needed for the implicit grant flow)
* http://localhost:8888/callback

Once you have created your app, create an `.env` file and add the `CLIENT_ID` and `CLIENT_SECRET` that you got from your Spotify Dashboard.
Once you have created your app, create an `.env` file and add the `CLIENT_ID` and `CLIENT_SECRET` that you got from your Spotify Dashboard
```env
CLIENT_ID=12345678
CLIENT_SECRET=12345678
```

## Run database

[MongoDB 4.2](https://docs.mongodb.com/v4.2/tutorial) is required to run this project.
[MongoDB 4.2](https://docs.mongodb.com/v4.2/tutorial) is required to run this project

If you have Docker you can run
```shell
Expand All @@ -57,4 +55,4 @@ In order to run the project, run `server.js`
node server.js
```

Then, open `http://localhost:8888` in your browser.
Then, open `http://localhost:8888` in your browser
2 changes: 1 addition & 1 deletion deployment.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ cp -R node_modules/popper.js/dist/umd/* public/popper/
mkdir public/handlebars
cp -R node_modules/handlebars/dist/* public/handlebars/

cd public && ls -R
cd public/ && ls -R
6 changes: 4 additions & 2 deletions docker-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ services:
- mongo:/data/db
- mongoc:/data/configdb
node:
build: .
build:
context: .
dockerfile: Dockerfile
container_name: ${NAME_APP:-spotify_app}
depends_on:
- mongo
Expand All @@ -34,7 +36,7 @@ services:
DATABASE_URL: mongodb://mongo/spotify
LETSENCRYPT_HOST: ${FULL_URL}
VIRTUAL_HOST: ${FULL_URL}
image: luisenmarroquin/spotify
image: luisenmarroquin/spotify-sync
networks:
- external
- internal
Expand Down
89 changes: 24 additions & 65 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const cookieparser = require('cookie-parser')
const querystring = require('querystring')
const compression = require('compression')
const querystring = require('querystring')
const CronJob = require('cron').CronJob
const mongoose = require('mongoose')
const express = require('express')
Expand All @@ -22,7 +22,7 @@ let Tracks = mongoose.model('Tracks', new mongoose.Schema({}, { strict: false, v
// Spotify Dashboard variables
let stateKey = 'spotify_auth_state' // Which type of key
let redirectUri = process.env.API_HOST || 'http://localhost:8888/callback' // Your redirect uri (should be registered on my dashboard)
let bufferAuth = (Buffer.from(process.env.CLIENT_ID + ':' + process.env.CLIENT_SECRET).toString('base64'))
let bufferAuth = (Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString('base64'))

let generateRandomString = (length) => {
let text = ''
Expand Down Expand Up @@ -83,20 +83,20 @@ app.get('/callback', (req, res) => { // your application requests refresh and ac
let expireToken = Date.now() + body.expires_in - 300 // When token expires (-300 === expire 5 minutes before)
let options = {
url: 'https://api.spotify.com/v1/me',
headers: { Authorization: 'Bearer ' + accessToken },
headers: { Authorization: `Bearer ${accessToken}` },
json: true
}
request.get(options, (error, response, body) => { // use the access token to access the Spotify Web API
if (error) { // Handling error
return console.log(error)
}
if (error) return console.log(error) // Handling error
let newDoc = { ...body, accessToken, refreshToken, expireToken }
Users.findOneAndUpdate({ id: body.id }, newDoc, { upsert: true }).lean().exec()
.then(data => {
if (!data) console.log('New user created!', newDoc.display_name)
else console.log('Existing user updated!', data.display_name)
})
.catch(err => { console.log(err) })
.catch(err => {
console.log(err)
})
})
res.redirect('/#' + querystring.stringify({ access_token: accessToken })) // we can also pass the token to the browser to make requests from there
} else {
Expand All @@ -106,25 +106,6 @@ app.get('/callback', (req, res) => { // your application requests refresh and ac
}
})

app.get('/refresh_token', (req, res) => {
let authOptions = { // requesting access token using refresh token
url: 'https://accounts.spotify.com/api/token',
headers: { Authorization: `Basic ${bufferAuth}` },
form: {
grant_type: 'refresh_token',
refresh_token: req.query.refresh_token
},
json: true
}
request.post(authOptions, (error, response, body) => {
if (!error && response.statusCode === 200) {
res.status(200).send({ access_token: body.access_token })
} else {
res.status(500).send({ message: 'Can\'t refresh your token' })
}
})
})

let lastPlayedTracks = (options, user, res = false) => { // Responding request
console.log(new Date(Date.now()).toLocaleString())
options.url = 'https://api.spotify.com/v1/me/player/recently-played?limit=25'
Expand Down Expand Up @@ -201,40 +182,24 @@ app.get('/last_played', (req, res) => { // Getting tracks from frontend
})

app.get('/my_history', async (req, res) => {
if (!req.query.page) { // Handling query.page // Aditional param is required
return res.status(404).send('Send me a valid page')
}
if (!req.query.page) return res.status(404).send('Send me a valid page') // Handling query.page // Aditional param is required
let pagination = Number(req.query.page)
if (isNaN(pagination)) { // Aditional param is required
return res.status(404).send('Your page is not a number')
}
if (isNaN(pagination)) return res.status(404).send('Your page is not a number') // Aditional param is required
pagination = Math.round(pagination)
if (pagination < 1) pagination = 1
let skip = 0
let limit = 20
if (pagination > 1) skip = ((pagination * limit) - limit)
if (!req.query.access_token) { // Handling query.page.access_token // Aditional param is required
return res.status(404).send('Send me a valid access_token')
}
if (!req.query.access_token) return res.status(404).send('Send me a valid access_token') // Handling query.page.access_token // Aditional param is required
let user = await Users.findOne({ accessToken: req.query.access_token }, 'id -_id').lean().exec() // Getting user id from DB
if (!user) { // Your access token has probably expired
return res.status(418).send('Please login again')
}
if (!user.id) { // You have no id on DB
return res.status(500).send('You should contact the app admin')
}
if (!user) return res.status(418).send('Please login again') // Your access token has probably expired
if (!user.id) return res.status(500).send('You should contact the app admin') // You have no id on DB
let filter = { user: user.id } // Getting tracks && documents length
let music = await Tracks.find(filter, '-_id -createdAt -updatedAt -context', { skip, limit, sort: { played_at: -1 } }).lean().exec()
if (!music) { // No music with your id
return res.status(404).send('You have no music')
}
if (!music) return res.status(404).send('You have no music') // No music with your id
let count = await Tracks.countDocuments(filter).lean().exec() // Counting songs number
if (count === 0) {
count = 1
}
if (!count) {
return res.status(500).send('Error counting your music')
}
if (count === 0) count = 1
if (!count) return res.status(500).send('Error counting your music')
let body = [] // Declare empty array
music.forEach(el => { // Iterate for each music
let obj = {} // Create clean object
Expand Down Expand Up @@ -279,7 +244,7 @@ app.get('/my_history', async (req, res) => {
try { // image
obj.img = el.track.album.images[el.track.album.images.length - 1].url
} catch (error) {
obj.img = 'favicon-32x32.png'
obj.img = 'favicon.png'
}
body.push(obj) // Push to array
})
Expand All @@ -289,30 +254,24 @@ app.get('/my_history', async (req, res) => {
for (let i = 0; i < navigation; i++) {
nav.push(i + 1)
}
} else if (pagination === 1 || pagination === 2) {
nav = [1, 2, 3, 4, 5]
} else if (pagination === navigation - 1) {
nav = [pagination - 3, pagination - 2, pagination - 1, pagination, pagination + 1]
} else if (pagination === navigation) {
nav = [pagination - 4, pagination - 3, pagination - 2, pagination - 1, pagination]
} else { // Has the at least 2 options on both sides
nav = [pagination - 2, pagination - 1, pagination, pagination + 1, pagination + 2]
}
else if (pagination === 1 || pagination === 2) nav = [1, 2, 3, 4, 5]
else if (pagination === navigation - 1) nav = [pagination - 3, pagination - 2, pagination - 1, pagination, pagination + 1]
else if (pagination === navigation) nav = [pagination - 4, pagination - 3, pagination - 2, pagination - 1, pagination]
else nav = [pagination - 2, pagination - 1, pagination, pagination + 1, pagination + 2] // Has at least 2 options on both sides
res.status(200).send({ count, body, nav, navigation }) // Send response
})

let cronjob = () => { // CronJob
console.log('You will see this message every hour')
Users.find({}).lean().exec()
.then(data => {
if (!data) { // If you have no users
return console.log('You have no users')
}
if (!data) return console.log('You have no users') // If you have no users
data.forEach(elm => {
console.log(elm.display_name, elm.id) // Showing username
let authOptions = { // Refreshing token
url: 'https://accounts.spotify.com/api/token',
headers: { Authorization: 'Basic ' + bufferAuth },
headers: { Authorization: `Basic ${bufferAuth}` },
form: {
grant_type: 'refresh_token',
refresh_token: elm.refreshToken
Expand All @@ -323,11 +282,11 @@ let cronjob = () => { // CronJob
console.log(body)
if (!error && body.access_token && response.statusCode === 200) {
lastPlayedTracks({
headers: { Authorization: 'Bearer ' + body.access_token },
headers: { Authorization: `Bearer ${body.access_token}` },
json: true
}, elm.id || 'Undefined')
} else {
console.log('Can not refresh token', error)
console.log(`Can't refresh token`, error)
}
})
})
Expand Down

0 comments on commit 4ec20ca

Please sign in to comment.