Initial release

This commit is contained in:
dbroqua 2020-05-01 14:20:02 +02:00
parent 5616106330
commit edde981b1f
10 changed files with 4129 additions and 0 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
/node_modules/

26
.eslintrc.js Normal file
View file

@ -0,0 +1,26 @@
module.exports = {
parser: "babel-eslint",
extends: ["airbnb-base", "prettier", "plugin:jest/recommended"],
plugins: ["prettier", "jest"],
env: {
es6: true,
browser: false,
node: true,
"jest/globals": true
},
globals: {
__DEV__: true
},
rules: {
"no-plusplus": [2, { allowForLoopAfterthoughts: true }],
"no-underscore-dangle": "off",
indent: ["error", 2, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
"import/no-extraneous-dependencies": ["error", { packageDir: "." }],
"prettier/prettier": "error",
"func-names": ["error", "never"],
"no-restricted-globals": ["error", "event", "fdescribe"]
}
};

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules
local.sh
currentSongId

71
app.js Normal file
View file

@ -0,0 +1,71 @@
import express from "express";
import fs from "fs";
import cookieParser from "cookie-parser";
import bodyParser from "body-parser";
import Pino from "express-pino-logger";
import cors from "cors";
import config from "./config";
import {
formatResponse,
formatResponseError,
publishMessage,
} from "./libs/Format";
let currentSongId = "";
const pino = new Pino({
prettyPrint: true,
});
pino.logger.info(`Server started on port ${config.port}`);
const app = express();
app.use(cors());
app.use(pino);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(cookieParser());
app.set("trust proxy", config.trustProxy);
// Get last saved song id on load
fs.readFile("./currentSongId", "utf8", (err, data) => {
if (!err) {
currentSongId = data;
}
});
app.post("/post", function (req, res, next) {
if (req.body && req.body.now_playing && req.body.now_playing.song) {
const newSongId = req.body.now_playing.song.id;
if (newSongId !== currentSongId) {
currentSongId = newSongId;
fs.writeFile("./currentSongId", currentSongId, (err) => {
if (err) {
req.log.error(err);
}
});
publishMessage(req.body.now_playing, (err, response) => {
if (err) {
req.log.error(err);
}
formatResponse(req, res, next, err, response);
});
}
} else {
formatResponseError(res, new Error("Missing now_playing object"));
}
});
// Gestion des erreurs
app.use((err, req, res, next) => {
res.error = err;
if (res.headersSent) {
next(err);
} else {
formatResponseError(res, err);
}
});
module.exports = app;

6
config/index.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
port: process.env.PORT || 3000,
trustProxy: process.env.TRUST_PROXY || "loopback",
mastodonToken: process.env.MASTODON_TOKEN,
mastondonApi: process.env.MASTODON_API_URL || "https://mamot.fr/api/v1/",
};

147
libs/Format.js Normal file
View file

@ -0,0 +1,147 @@
import fs from "fs";
import request from "request";
import Masto from "mastodon";
import config from "../config";
/**
* Fonction permettant de formater une réponse API
* @param {Object} req
* @param {Object} res
* @param {Functiont} next
* @param {Object} err
* @param {Object} response {code: Integer, res: Array/Object}
*/
const _formatResponse = (req, res, next, err, response) => {
if (err) {
req.response = response;
next(err);
} else {
switch (req.method) {
case "GET":
res
.status(response ? 200 : 204)
.json(response)
.end();
break;
case "PATCH":
res.status(200).json(response).end();
break;
case "DELETE":
res.status(200).json(response).end();
break;
case "POST":
res.status(201).json(response).end();
break;
default:
next(new Error("Not implemented"));
break;
}
}
};
/**
* Fonction permettant de formater une erreur
* @param {Object} res
* @param {Object} err
*/
const _formatResponseError = (res, err) => {
const code = err.errorCode || 500;
const response = {
code,
message: err.message,
};
res.status(Math.trunc(code)).json(response);
};
/**
* 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) => {
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.text}`;
};
/**
* Fonction publiant un message (et média si attaché) sur Mastdon
* @param {Object} playing
* @param {Function} cb
*/
const _publishMessage = (playing, cb) => {
const callback = (err, res) => {
if (err) {
cb(err);
} else {
cb(null, { id: res.id });
}
};
if (playing.song) {
const status = _formatMessage(playing.song);
const cover = playing.song.art;
// Instanciation de Mastodon
const M = new Masto({
access_token: config.mastodonToken,
api_url: config.mastondonApi,
});
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;
M.post("statuses", { status, media_ids: [id] }, callback);
})
.catch(() => {
M.post("statuses", { status }, callback);
});
}
});
} else {
M.post("statuses", { status }, callback);
}
} else {
callback(new Error("Missing song object"));
}
};
export const formatResponse = _formatResponse;
export const formatResponseError = _formatResponseError;
export const getMedia = _getMedia;
export const formatMessage = _formatMessage;
export const publishMessage = _publishMessage;

42
package.json Normal file
View file

@ -0,0 +1,42 @@
{
"name": "azurcast-to-mastodon",
"version": "1.0.0",
"description": "Azurcast webhook to mastodon",
"main": "start-server.js",
"repository": "git@framagit.org:dbroqua/azurcast-to-mastodon.git",
"author": "dbroqua <dbroqua@mousur.org>",
"license": "MIT",
"private": false,
"dependencies": {
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-pino-logger": "^5.0.0",
"mastodon": "^1.2.2",
"pino-pretty": "^4.0.0",
"request": "^2.88.2"
},
"scripts": {
"start": "node start-server.js",
"start:local": "nodemon start-server.js",
"lint": "./node_modules/.bin/eslint . --fix"
},
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.9.6",
"@babel/plugin-transform-runtime": "^7.9.6",
"@babel/preset-env": "^7.9.6",
"@babel/register": "^7.9.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.8.2",
"eslint-plugin-prettier": "^3.1.3",
"nodemon": "^2.0.3",
"prettier": "^2.0.5"
}
}

7
server.js Normal file
View file

@ -0,0 +1,7 @@
import app from "./app";
const config = require("./config");
const server = app.listen(config.port, () => {});
module.exports = server;

19
start-server.js Normal file
View file

@ -0,0 +1,19 @@
require("@babel/register")({
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-runtime",
],
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
],
});
module.exports = require("./server.js");

3807
yarn.lock Normal file

File diff suppressed because it is too large Load diff