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