Compare commits

..

No commits in common. "develop" and "master" have entirely different histories.

10 changed files with 0 additions and 2721 deletions

View file

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

2
.gitignore vendored
View file

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

View file

@ -1,21 +0,0 @@
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,47 +1,2 @@
# 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)

View file

@ -1,37 +0,0 @@
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,
rx3List: ['Rx3', 'REAL REBEL RADIO', 'REAL REBEL RADIO homemade'],
rx3CoverBaseUrl: process.env.rx3CoverBaseUrl,
colors: {
Reset: '\x1b[0m',
Bright: '\x1b[1m',
Dim: '\x1b[2m',
Underscore: '\x1b[4m',
Blink: '\x1b[5m',
Reverse: '\x1b[7m',
Hidden: '\x1b[8m',
FgBlack: '\x1b[30m',
FgRed: '\x1b[31m',
FgGreen: '\x1b[32m',
FgYellow: '\x1b[33m',
FgBlue: '\x1b[34m',
FgMagenta: '\x1b[35m',
FgCyan: '\x1b[36m',
FgWhite: '\x1b[37m',
BgBlack: '\x1b[40m',
BgRed: '\x1b[41m',
BgGreen: '\x1b[42m',
BgYellow: '\x1b[43m',
BgBlue: '\x1b[44m',
BgMagenta: '\x1b[45m',
BgCyan: '\x1b[46m',
BgWhite: '\x1b[47m'
}
}

View file

@ -1,42 +0,0 @@
const moment = require('moment')
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(config.colors.FgRed, '[ERR] GET LAST SONG:', moment().format(), err, config.colors.Reset)
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(config.colors.FgRed, '[ERR] SAVE SONG:', moment().format(), err, config.colors.Reset)
return false
}
// On récupère la cover du morceau actuel
libs.findCover(currentSong, (err, coverUrl) => {
if (err) {
console.log(config.colors.FgRed, '[ERR] FIND COVER:', moment().format(), err, config.colors.Reset)
return false
}
// On publie sur Mastodon
libs.publishMessage(currentSong, coverUrl)
})
})
}
})
}
})
}, config.delay)

285
libs.js
View file

@ -1,285 +0,0 @@
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].stringId !== values.stringId)
) {
console.log(config.colors.FgBlue, '[INFO][saveSong] song not found:', values.title, values.artist, config.colors.Reset)
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 retrouver la cover d'un titre Rx3
* @param {Object} song
* @param {Function} callback
*/
const getRx3Cover = (song, callback) => {
let cover = null
// Cas des GSU
if (song.title.indexOf('GSU') === 0) {
const year = song.title.split(' ')[1]
if (!isNaN(parseInt(year))) {
cover = `${config.rx3CoverBaseUrl}gsu${year}.jpg`
}
}
callback(null, cover)
}
/**
* Fonction permettant de chercher sur Discogs la pochette d'un album
* @param {Object} song
* @param {Function} callback
*/
const getRemoteCover = (song, callback) => {
if (config.rx3List.indexOf(song.artist) !== -1) {
getRx3Cover(song, callback)
return true
}
// Si c'est KOЯN on remplace par KORN (merci discogs)
if (song.artist === 'KOЯN') {
song.artist = 'KORN'
}
dis.search({ q: song.album, artist: song.artist, page: 1, per_page: 1 }, (err, res) => {
if (err) {
console.log(config.colors.FgRed, 'ERR:', err, config.colors.Reset)
callback(err)
return false
}
// Une pochette est trouvée
if (res.results && res.results.length === 1) {
callback(null, res.results[0].cover_image)
} else {
console.log(config.colors.FgBlue, '[INFO] No cover found for:', song.album, song.artist, config.colors.Reset)
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({
stringId: song.stringId
})
.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) {
console.log(config.colors.FgBlue, '[INFO][findCover] cover exists:', metadata._id, metadata.cover, config.colors.Reset)
callback(null, metadata.cover)
return true
}
// Aucune pochette trouvée, on interroge Discogs (peut être que cette fois ils auront une cover...)
getRemoteCover(song, (err, coverUrl) => {
if (err) {
callback(err)
return false
}
console.log(config.colors.FgBlue, '[INFO][findCover] cover does not exists but found on discogs:', coverUrl, config.colors.Reset)
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
}
console.log(config.colors.FgBlue, '[INFO][findCover] cover does not exists but found on discogs (2):', coverUrl, config.colors.Reset)
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
try {
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,
stringId: _body.data[0].track.id || `FAKEID_${_body.data[0].track.artist}_${_body.data[0].track.title}_${_body.data[0].track.album}`,
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
}
} catch (e) {
error = e
console.error('getStream error:', error)
}
}
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)
try {
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'
}
})
.on('error', (error) => {
console.log(config.colors.FgRed, 'ERR:', error, config.colors.Reset)
callback(error)
})
.pipe(file)
.on('finish', () => {
callback(null, dest)
})
} catch (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) => {
const status = formatMessage(song)
const callback = (err, res) => {
if ( err ) {
console.log(config.colors.FgRed, 'ERR on publishMessage:', err, config.colors.Reset)
}
}
if (cover) {
getMedia(cover, (err, dest) => {
if (err) {
M.post('statuses', { status }, callback)
} else {
M.post('media', { file: fs.createReadStream(dest) }).then(resp => {
const id = resp.data.id
M.post('statuses', { status, media_ids: [id] }, callback)
})
.catch( () => {
M.post('statuses', { status }, callback)
})
}
})
} else {
M.post('statuses', { status }, callback)
}
}
module.exports = {
saveSong: saveSong,
getLastSong: getLastSong,
findCover: findCover,
getStream: getStream,
getMedia: getMedia,
formatMessage: formatMessage,
publishMessage: publishMessage
}

View file

@ -1,50 +0,0 @@
const mongoose = require('mongoose')
const config = require('./config')
const schemas = {
histories: mongoose.Schema({
artist: String,
title: String,
album: String,
royaltytrackid: Number,
id: Number,
stringId: String,
playlistId: Number,
thumbCover: String,
createdAt: {
type: Date,
default: Date.now
}
}),
metadata: mongoose.Schema({
artist: String,
title: String,
album: String,
royaltytrackid: Number,
id: Number,
stringId: String,
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.set('debug', true)
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
}

View file

@ -1,24 +0,0 @@
{
"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",
"moment": "^2.24.0",
"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"
}
}

2196
yarn.lock

File diff suppressed because it is too large Load diff