commit 8a63bee22b6488cfc969ee1d061d64180939f479 Author: bee Date: Mon Feb 28 20:59:49 2022 -0800 added connectivity between SP and PK for fronting. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6769e2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/SimplyAPI +/test.js +/node_modules +/.vscode +config.json +package-lock.json \ No newline at end of file diff --git a/WebsocketClient.js b/WebsocketClient.js new file mode 100644 index 0000000..3449566 --- /dev/null +++ b/WebsocketClient.js @@ -0,0 +1,77 @@ +const WebSocket = require('ws'); +const timestamp = () => new Date().toISOString().replace('T', ' ').substr(0, 19); + +function WebSocketClient(url) { + let client; + let timeout; + let connecting = false; + let backoff = 250; + const init = () => { + console.error(timestamp(), '::SimplyWS:: connecting'); + connecting = false; + if (client !== undefined) { + client.removeAllListeners(); + } + client = new WebSocket(url); + const heartbeat = () => { + if (timeout !== undefined) { + clearTimeout(timeout); + timeout = undefined; + } + timeout = setTimeout(() => client.terminate(), 35000); + }; + client.on('ping', () => { + console.log(timestamp(), '::SimplyWS:: pinged'); + heartbeat(); + }); + client.on('open', (e) => { + if (typeof this.onOpen === 'function') { + this.onOpen(); + } else { + console.log(timestamp(), '::SimplyWS:: opened'); + console.log(e); + } + heartbeat(); + }); + client.on('message', (e) => { + if (typeof this.onMessage === 'function') { + this.onMessage(e); + } else { + console.log(timestamp(), '::SimplyWS:: messaged'); + } + heartbeat(); + }); + client.on('close', (e) => { + if (e.code !== 1000) { + if (connecting === false) { // abnormal closure + backoff = backoff === 8000 ? 250 : backoff * 2; + setTimeout(() => init(), backoff); + connecting = true; + } + } else if (typeof this.onClose === 'function') { + this.onClose(); + } else { + console.error(timestamp(), '::SimplyWS:: closed'); + console.error(e); + } + }); + client.on('error', (e) => { + if (e.code === 'ECONREFUSED') { + if (connecting === false) { // abnormal closure + backoff = backoff === 8000 ? 250 : backoff * 2; + setTimeout(() => init(), backoff); + connecting = true; + } + } else if (typeof this.onError === 'function') { + this.onError(e); + } else { + console.error(timestamp(), '::SimplyWS : errored'); + console.error(e); + } + }); + this.send = client.send.bind(client); + }; + init(); +} + +module.exports = WebSocketClient; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..ff91326 --- /dev/null +++ b/index.js @@ -0,0 +1,134 @@ +const axios = require('axios') +const config = require('./config.json') +const SAPI = require('./SimplyAPI') +const SimplyAPI = new SAPI(config) + +const pkUrl = 'https://api.pluralkit.me/v2' +const pkHeader = { + 'Content-Type': 'application/json', + 'Authorization': config.pk_token +} + +let e; +main = async () => { + openWebSocket() +} + +openWebSocket = async () => { + const WebSocketClient = require('./WebSocketClient') + const wss = new WebSocketClient(config.socket); + let initialPacket = { "op": "authenticate", "token": config.token } + wss.onOpen = (_) => { wss.send(JSON.stringify(initialPacket)); } + wss.onClose = (e) => { console.log('SimplyWS/onClose :: %s', e); e = '' } + wss.onError = (e) => { console.log('SimplyWS/onError :: %s', e) } + + wss.onMessage = async (raw) => { + e = raw + let data = JSON.parse(e) + if (Object.keys(data).length === 0) return + + switch (data.msg) { + case "Successfully authenticated": + console.log('::SimplyWS:: authenticated') + break; + case "Authentication violation: Token is missing or invalid. Goodbye :)": + console.log('::SimplyWS:: invalid token, exiting..') + process.exit(1) + case "update": + let response = await generateResponse(data.target, data); + if (response) console.log('::SimplyWS:: ' + response) + break; + default: + unrecognizedMessage(data.msg) + break; + } + } +} + +generateResponse = async (target, data) => { + let response = '' + switch (target) { + case 'frontHistory': + response += 'Front has changed!' + await asyncForEach(data.results, async (o) => { + await SimplyAPI.findMemberById(o.content.member) + .then(async (member) => { + if (o.operationType == "insert") { + let fronters = await getPKFronters() + fronters.push(member.pkId) + + axios.post(`${pkUrl}/systems/${config.pk_system}/switches`, JSON.stringify({"members": fronters}), { + headers: pkHeader + }) + .catch(err => console.error(err.toJSON().message)); + + response += '\n' + member.name + ' was added to the front.' + return + } + else { + let fronters = await getPKFronters() + let index = fronters.indexOf(member.pkId) + fronters.splice(index, 1) + + axios.post(`${pkUrl}/systems/${config.pk_system}/switches`, JSON.stringify({ "members": fronters }), { + headers: pkHeader + }) + .catch(err => console.error(err.message)); + + response += '\n' + member.name + ' was removed from the front.' + return + } + }) + .catch(err => { + console.log('::SimplyWS:: Error finding member: ' + err) + }) + }) + break; + default: + unknownTarget(data.target) + break; + } + return response +} + +unknownTarget = (target) => { + console.log('::SimplyWS:: Unknown update target: ' + target + '\n::SimplyWS:: Full message: ' + e) +} + +unrecognizedMessage = (msg) => { + console.log('::SimplyWS:: Unrecognized message: ' + msg + '\n::SimplyWS:: Full message: ' + e) +} + +findMember = (who) => { + return new Promise(function (resolve, reject) { + SimplyAPI.findMember(who, (member) => { + if (member) { + resolve(member) + } else { + reject({"name": "Unknown member"}) + } + }) + }) +} + +getPKFronters = async () => { + let members = [] + let fronters = await axios.get(`${pkUrl}/systems/${config.pk_system}/fronters`, { + headers: pkHeader + }) + .catch(err => console.error("An error occured while getting current fronters: " + err.message)) + + fronters.data.members.forEach((key, value) => { + members.push(key.id) + }) + + return members +} + +asyncForEach = async (array, callback) => { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +main() \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b100935 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "sppk", + "version": "1.0.0", + "description": "A tool to automatically update PluralKit with SimplyPlural data.", + "main": "index.js", + "scripts": { + "start": "node .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "padlocks", + "license": "ISC", + "dependencies": { + "ajv": "^8.10.0", + "axios": "^0.26.0", + "pkapi.js": "^3.1.0", + "ws": "^8.5.0" + }, + "optionalDependencies": { + "bufferutil": "^4.0.6", + "utf-8-validate": "^5.0.8" + } +}