First release

This commit is contained in:
dbroqua 2019-12-01 17:56:13 +01:00
parent 77e07bdf1c
commit b8d9c1f28d
10 changed files with 2627 additions and 0 deletions

19
.eslintrc.js Normal file
View file

@ -0,0 +1,19 @@
module.exports = {
env: {
commonjs: true,
es6: true,
node: true
},
extends: [
'standard'
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018
},
rules: {
}
}

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
local.sh

21
LICENCE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Damien Broqua and contributors (https://framagit.org/dbroqua/rx3-to-mastodon/-/graphs/master)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +1,47 @@
# rx3-to-mastodon
Bot permettant de publier sur Mastodon le titre en cours de lecture sur [Real Rebel Radio](https://www.real-rebel-radio.net/).
## Pré requis
* Un compte mastodon
* Un compte discogs
* Un serveur avec NodeJS
* Une base de données MongDB
## Configuration
Le bot a besoin de quelques variables d'environnement pour tourner en totale autonomie :
```json
"streamUrl": "Url du flux rx3 (facultatif)",
"mongoUrl": "Url de connexion à mongoDb, par défaut : mongodb://localhost/rx3-to-mastodon",
"mastodonToken": "Token Mastodon (https://mamot.fr/settings/applications, autorisations requises : write:media, write:statuses)",
"mastondonApi": "Url de l'instance Mastodon utilisées, par défaut : https://mamot.fr/api/v1/",
"discogsToken": "Token d'accès l'API Discogs (https://www.discogs.com/settings/developers)",
"delay": "Délai en millisecondes entre 2 scans au flux rx3, par défaut 4000ms"
```
## Lancement du BOT
Une fois les variables d'environnement appliquées il faut installer les dépendances nécessaires au programe via npm ou yarn :
```
yarn install
```
Une fois cela fait on peut lancer le bot via la commande start :
```
yarn start
```
## Crédits
Ce bot est une idée originale de Rx3 et [Brunus](https://framapiaf.org/@Brunus).
Développé par [DarKou](https://mamot.fr/@DarKou).
## Licence
Rx3 to Mastodon est distribué sous [licence MIT](LICENCE.txt)

8
config.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = {
streamUrl: process.env.streamUrl || 'http://37.59.28.208/rpc/realrebe/streaminfo.get',
mongoUrl: process.env.mongoUrl || 'mongodb://localhost/rx3-to-mastodon',
mastodonToken: process.env.mastodonToken,
mastondonApi: process.env.mastondonApi || 'https://mamot.fr/api/v1/',
discogsToken: process.env.discogsToken,
delay: process.env.delay || 4000
}

42
index.js Normal file
View file

@ -0,0 +1,42 @@
const libs = require('./libs')
const config = require('./config')
setInterval(() => {
// Récupération du morceau en cours de diffusion
libs.getStream((error, currentSong) => {
if (!error) {
// On récupére en base le précédent morceau joué
libs.getLastSong((err, last) => {
if (err) {
console.log(err)
return false
}
// Le morceau actuel est différent du précedent morceau
if (last.length === 0 ||
(last[0] !== undefined && last[0].id !== currentSong.id)
) {
// On sauvegarde en base le morceau en cours de diffusion
libs.saveSong(currentSong, (err, savedSond) => {
if (err) {
console.log('ERR:', err)
return false
}
// On récupère la cover du morceau actuel
libs.findCover(currentSong, (err, coverUrl) => {
if (err) {
console.log('ERR:', err)
return false
}
// On publie sur Mastodon
libs.publishMessage(currentSong, coverUrl)
})
})
}
})
}
})
}, config.delay)

230
libs.js Normal file
View file

@ -0,0 +1,230 @@
const fs = require('fs')
const request = require('request')
const Discogs = require('disconnect').Client
const Masto = require('mastodon')
const mongo = require('./mongo')
const config = require('./config')
// Instanciation de Mastodon
const M = new Masto({
access_token: config.mastodonToken,
api_url: config.mastondonApi
})
// Instanciation de Disgocs
const dis = new Discogs({ userToken: config.discogsToken }).database()
/**
* Fonction permettant de sauvegarder en historique le morceau en cours de diffusion
* @param {Object} values
* @param {Function} callback
*/
const saveSong = (values, callback) => {
mongo.Histories
.find({})
.sort({
createdAt: 'desc'
})
.limit(1)
.exec(function (err, last) {
if (err ||
last.length === 0 ||
(last[0] !== undefined && last[0].id !== values.id)
) {
const history = new mongo.Histories(values)
history.save(callback)
}
})
}
/**
* Fonction permettant de retrouver le dernier morceau sauvegardé en base
* @param {Function} callback
*/
const getLastSong = (callback) => {
mongo.Histories
.find({})
.sort({
createdAt: 'desc'
})
.limit(1)
.exec(function (err, last) {
if (err) {
callback(err)
return false
}
callback(null, last)
})
}
/**
* Fonction permettant de chercher sur Discogs la pochette d'un album
* @param {Object} song
* @param {Function} callback
*/
const getRemoteCover = (song, callback) => {
dis.search({ q: song.album, artist: song.artist, page: 1, per_page: 1 }, (err, res) => {
if (err) {
console.log('ERR:', err)
callback(err)
return false
}
// Une pochette est trouvée
if (res.results && res.results.length === 1) {
callback(null, res.results[0].cover_image)
} else {
callback(null, null)
}
})
}
/**
* Fonction permettant de retourner l'url de la pochette d'un album
* @param {Object} song
* @param {Function} callback
*/
const findCover = (song, callback) => {
mongo.Metadata.findOne({
id: song.id
})
.exec((err, metadata) => {
if (err) {
callback(err)
return false
}
// Ce morceau est déjà en base
if (metadata) {
// On a déjà une pochette pour ce morceau
if (metadata.cover) {
callback(null, metadata.cover)
return true
}
// Aucune pochette trouvée, on interroge Discogs
getRemoteCover(song, (err, coverUrl) => {
if (err) {
callback(err)
return false
}
metadata.updateOne({ cover: coverUrl })
callback(null, coverUrl)
})
} else { // Première fois que ce morceau est jouée, on rempli sa fiche
getRemoteCover(song, (err, coverUrl) => {
if (err) {
callback(err)
return false
}
song.cover = coverUrl
const metadata = new mongo.Metadata(song)
metadata.save()
callback(null, coverUrl)
})
}
})
}
/**
* Fonction permettant de récupérer le titre diffusé
* @param {Funtion} callback
*/
const getStream = (callback) => {
request.get(config.streamUrl,
(error, response, body) => {
if (!error && response.statusCode === 200) {
let res = null
const _body = JSON.parse(body)
res = {
artist: _body.data[0].track.artist,
title: _body.data[0].track.title,
album: _body.data[0].track.album,
royaltytrackid: _body.data[0].track.royaltytrackid,
id: _body.data[0].track.id,
playlistId: _body.data[0].track.playlist ? _body.data[0].track.playlist.id : null,
thumbCover: _body.data[0].track.imageurl
}
if (res !== null && res.artist !== undefined && res.title !== undefined) {
callback(null, res)
} else {
error = true
}
}
if (error) {
callback(error)
}
})
}
/**
* Fonction permettant de télécharger la pochette d'un album selon une URL donnée
* @param {String} coverUrl
* @param {Function} callback
*/
const getMedia = (coverUrl, callback) => {
const dest = '/tmp/attachment.jpg'
const file = fs.createWriteStream(dest)
request({
uri: coverUrl,
headers: {
'Cache-Control': 'max-age=0',
Connection: 'keep-alive',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
}
})
.pipe(file)
.on('finish', () => {
callback(null, dest)
})
.on('error', (error) => {
console.log('ERR:', error)
callback(error)
})
}
/**
* Fonction formattant le texte à publier
* @param {Object} values
*/
const formatMessage = (values) => {
return `#rx3 #nowplaying ${values.artist} - ${values.title}`
}
/**
* Fonction publiant un message (et média si attaché) sur Mastdon
* @param {Object} song
* @param {String} cover
*/
const publishMessage = (song, cover) => {
if (cover) {
getMedia(cover, (err, dest) => {
if (err) {
M.post('statuses', { status: formatMessage(song) })
} else {
M.post('media', { file: fs.createReadStream(dest) }).then(resp => {
const id = resp.data.id
M.post('statuses', { status: formatMessage(song), media_ids: [id] })
})
}
})
} else {
M.post('statuses', { status: formatMessage(song) })
}
}
module.exports = {
saveSong: saveSong,
getLastSong: getLastSong,
findCover: findCover,
getStream: getStream,
getMedia: getMedia,
formatMessage: formatMessage,
publishMessage: publishMessage
}

46
mongo.js Normal file
View file

@ -0,0 +1,46 @@
const mongoose = require('mongoose')
const config = require('./config')
const schemas = {
histories: mongoose.Schema({
artist: String,
title: String,
album: String,
royaltytrackid: Number,
id: Number,
playlistId: Number,
thumbCover: String,
createdAt: {
type: Date,
default: Date.now
}
}),
metadata: mongoose.Schema({
artist: String,
title: String,
album: String,
royaltytrackid: Number,
id: Number,
playlistId: Number,
thumbCover: String,
cover: String,
createdAt: {
type: Date,
default: Date.now
}
})
}
const Histories = mongoose.model('histories', schemas.histories)
const Metadata = mongoose.model('metadata', schemas.metadata)
mongoose.connect(config.mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true })
const db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error:'))
module.exports = {
Histories: Histories,
Metadata: Metadata
}

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "rx3-to-mastodon",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "./node_modules/.bin/nodemon ./index.js"
},
"dependencies": {
"disconnect": "^1.2.1",
"mastodon": "^1.2.2",
"mongoose": "^5.7.13",
"request": "^2.88.0"
},
"devDependencies": {
"eslint": "^6.7.1",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"nodemon": "^2.0.1"
}
}

2191
yarn.lock Normal file

File diff suppressed because it is too large Load diff