Initial release
This commit is contained in:
parent
5616106330
commit
edde981b1f
10 changed files with 4129 additions and 0 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/node_modules/
|
26
.eslintrc.js
Normal file
26
.eslintrc.js
Normal 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
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
local.sh
|
||||||
|
currentSongId
|
71
app.js
Normal file
71
app.js
Normal 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
6
config/index.js
Normal 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
147
libs/Format.js
Normal 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
42
package.json
Normal 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
7
server.js
Normal 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
19
start-server.js
Normal 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");
|
Loading…
Reference in a new issue