Compare commits
No commits in common. "develop" and "master" have entirely different histories.
10 changed files with 0 additions and 2721 deletions
19
.eslintrc.js
19
.eslintrc.js
|
@ -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
2
.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
node_modules
|
|
||||||
local.sh
|
|
21
LICENCE.txt
21
LICENCE.txt
|
@ -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.
|
|
45
README.md
45
README.md
|
@ -1,47 +1,2 @@
|
||||||
# rx3-to-mastodon
|
# 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)
|
|
37
config.js
37
config.js
|
@ -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'
|
|
||||||
}
|
|
||||||
}
|
|
42
index.js
42
index.js
|
@ -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
285
libs.js
|
@ -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
|
|
||||||
}
|
|
50
mongo.js
50
mongo.js
|
@ -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
|
|
||||||
}
|
|
24
package.json
24
package.json
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue