update to current state of SimplyAPI

This commit is contained in:
bee 2022-03-06 03:02:52 -08:00
parent 888ec5a072
commit ebaaa23553
No known key found for this signature in database
GPG key ID: 70EECBF29DA75D8B
12 changed files with 40 additions and 498 deletions

View file

@ -1,5 +1,6 @@
# Compatiplural # Compatiplural
url="https://devapi.apparyllis.com" url_override="https://devapi.apparyllis.com"
api_version="v1"
socket="wss://devapi.apparyllis.com/v1/socket" socket="wss://devapi.apparyllis.com/v1/socket"
pk_url="https://api.pluralkit.me/v2" pk_url="https://api.pluralkit.me/v2"
token="AAAAAAAAAAAAAAAAAAAA" token="AAAAAAAAAAAAAAAAAAAA"

View file

@ -9,7 +9,8 @@ This project already has a Procfile set up, so it's super easy to get started. O
These can be set either in the .env file, in terminal, or in the config vars section of Heroku. These can be set either in the .env file, in terminal, or in the config vars section of Heroku.
| Setting | Default | Description | | Setting | Default | Description |
| ---------| ------- | ------------------ | | ---------| ------- | ------------------ |
| url | https://devapi.apparyllis.com | The base URL for all SimplyPlural API requests. Unless you are running your own fork of Simply Plural, you shouldn't change this. | | url_override | https://devapi.apparyllis.com | The base URL for all SimplyPlural API requests. Unless you are running your own fork of Simply Plural, you shouldn't change this. |
| api_version | v1 | The target SimplyPlural API version. Unless you are running your own fork of Simply Plural, you shouldn't change this. |
| socket | wss://devapi.apparyllis.com/v1/socket | The socket URL for SimplyPlural. Unless you are running your own fork of Simply Plural, you shouldn't change this. | | socket | wss://devapi.apparyllis.com/v1/socket | The socket URL for SimplyPlural. Unless you are running your own fork of Simply Plural, you shouldn't change this. |
| pk_url | https://api.pluralkit.me/v2 | The base URL for all PluralKit API requests. Unless you are running your own fork of PluralKit, you shouldn't change this. | | pk_url | https://api.pluralkit.me/v2 | The base URL for all PluralKit API requests. Unless you are running your own fork of PluralKit, you shouldn't change this. |
| token | token_here | Your SimplyPlural account token. As of now, the only permission necessary is the Read permission. | | token | token_here | Your SimplyPlural account token. As of now, the only permission necessary is the Read permission. |

View file

@ -1,61 +0,0 @@
const config = require('./config.json')
const SAPI = require('./SimplyAPI.js')
const SimplyAPI = new SAPI(config)
let group = {
parent: "root",
color: "",
private: true,
preventTrusted: false,
name: "123",
desc: "test group",
emoji: "",
members: []
}
main = async () => {
getGroups()
findGroup("123")
createTestGroup(group)
deleteTestGroup("123")
}
getGroups = async () => {
SimplyAPI.getGroups()
.then((response) => {
console.log(response.data)
})
.catch(err => console.error(err))
}
findGroup = async (what) => {
SimplyAPI.findGroup(what, (group) => {
if (group) {
console.log(group)
}
})
}
createTestGroup = async (data) => {
SimplyAPI.createGroup(data)
.then((response) => {
console.log(response.data)
})
.catch(err => console.error(err))
}
deleteTestGroup = async (what) => {
await SimplyAPI.findGroup(what, async (group) => {
if (group) {
await SimplyAPI.deleteGroup(group.id)
.then(async (res) => {
if (res.status == 200) {
console.log(`group deleted: ${group.content.name}.`,)
}
})
.catch(err => console.error(err))
}
})
}
main()

View file

@ -1,58 +0,0 @@
const config = require('./config.json')
const SAPI = require('./lib/SimplyAPI.js')
const SimplyAPI = new SAPI(config)
let member = {
name: "Test",
desc: "a test member",
pronouns: "It/Its",
pkId: "",
color: "",
avatarUuid: "",
avatarUrl: "",
private: false,
preventTrusted: false,
preventFrontNotifs: false,
info: {
"Age": "19",
"Likes": "bread"
}
}
main = async () => {
findMember("Test")
createTestMember(member)
deleteTestMember("Test")
}
findMember = async (who) => {
SimplyAPI.findMemberCallback(who, (member) => {
if (member) {
console.log(member)
}
})
}
createTestMember = async (data) => {
SimplyAPI.createMember(data)
.then((response) => {
console.log(response.data)
})
.catch(err => console.error(err))
}
deleteTestMember = async (who) => {
await SimplyAPI.findMember(who, async (member) => {
if (member) {
await SimplyAPI.deleteMember(member.id)
.then((res) => {
if (res.status == 200) {
console.log(`member deleted: ${res.data.content.name}.`)
}
})
.catch(err => console.error(err))
}
})
}
main()

View file

@ -1,26 +0,0 @@
const config = require('./config.json')
const SAPI = require('./SimplyAPI.js')
const SimplyAPI = new SAPI(config)
main = async () => {
getSystem()
getCurrentFronters()
}
getSystem = async () => {
SimplyAPI.getSystem()
.then((response) => {
console.log(response.data)
})
.catch(err => console.error(err))
}
getCurrentFronters = async () => {
SimplyAPI.getFronters()
.then((response) => {
console.log(response)
})
.catch(err => console.error(err))
}
main()

View file

@ -1,6 +0,0 @@
const SimplyAPI = require('./lib/SimplyAPI')
SimplyAPI.Validate = require('./lib/Validate')
SimplyAPI.Schemas = require('./lib/Schemas')
module.exports = SimplyAPI;

View file

@ -1,100 +0,0 @@
const memberSchema = {
type: "object",
properties: {
name: { type: "string" },
desc: { type: "string" },
pronouns: { type: "string" },
pkId: { type: "string" },
color: { type: "string" },
avatarUuid: { type: "string" },
avatarUrl: { type: "string" },
private: { type: "boolean" },
preventTrusted: { type: "boolean" },
preventFrontNotifs: { type: "boolean" },
info: {
type: "object",
properties: {
"*": { type: "string" }
}
}
},
nullable: false,
additionalProperties: false,
};
const groupSchema = {
type: "object",
properties: {
parent: { type: "string" },
color: { type: "string" },
private: { type: "boolean" },
preventTrusted: { type: "boolean" },
name: { type: "string" },
desc: { type: "string" },
emoji: { type: "string" },
members: { type: "array", items: { type: "string" } },
},
nullable: false,
additionalProperties: false,
dependencies: {
private: { required: ["preventTrusted"] },
preventTrusted: { required: ["private"] },
}
};
const customFrontSchema = {
type: "object",
properties: {
name: { type: "string" },
desc: { type: "string" },
avatarUrl: { type: "string" },
avatarUuid: { type: "string" },
color: { type: "string" },
preventTrusted: { type: "boolean" },
private: { type: "boolean" },
},
nullable: false,
additionalProperties: false,
}
const commentSchema = {
type: "object",
properties: {
time: { type: "number" },
text: { type: "string" },
documentId: { type: "string" },
collection: { type: "string" }
},
nullable: false,
additionalProperties: false,
required: ["time", "text", "documentId", "collection"]
}
const commentPatchSchema = {
type: "object",
properties: {
text: { type: "string" },
},
nullable: false,
additionalProperties: false,
required: ["text"]
}
const automatedTimerSchema = {
type: "object",
properties: {
name: { type: "string" },
message: { type: "string" },
action: { type: "number" },
delayInHours: { type: "number" },
type: { type: "number" },
},
nullable: false,
additionalProperties: false,
};
module.exports = {
memberSchema,
groupSchema
}

View file

@ -1,170 +0,0 @@
const { resolveRef } = require('ajv/dist/compile')
const axios = require('axios')
const schemas = require('./Schemas')
const validate = require('./Validate')
/**
* @param {Object} config
*/
class SimplyAPI {
constructor(config) {
this.url = config.url_override || 'https://devapi.apparyllis.com'
this.userId = config.userId
this.system = config.userId
this.token = config.token
this.header = {
'Content-Type': 'application/json',
'Authorization': this.token
}
}
getSystem = async () => {
let system = await axios.get(`${this.url}/v1/members/${this.system}`, {
headers: this.header
})
return system.data
//.then((response) => response)
//.catch(err => console.error(err.toJSON().message));
}
getGroups = async () => {
return axios.get(`${this.url}/v1/groups/${this.system}`, {
headers: this.header
})
.then((response) => response)
.catch(err => console.error(err.toJSON().message));
}
/**
* @param {string} group
* @param {function} callback
*/
findGroup = async (group, callback) => {
await this.getGroups()
.then((groups) => {
for (let i in groups.data) {
if (groups.data[i].content.name.includes(group)) {
callback(groups.data[i])
return
}
}
})
}
createGroup = async (group) => {
let valid = await validate.validateSchema(schemas.groupSchema, group)
if (valid) {
return axios.post(`${this.url}/v1/group/`, JSON.stringify(group), {
headers: this.header,
})
.then((response) => response)
.catch(err => console.error(err.toJSON().message));
} else {
let response = {}
response.data = {status: 'error', message: 'Invalid group schema'}
return response
}
}
deleteGroup = async (group) => {
return await axios.delete(`${this.url}/v1/group/${group}`, {
headers: this.header,
})
.then((response) => response)
.catch(err => console.error(err.toJSON().message));
}
/**
* @param {string} id
*/
findMemberById = async (id) => {
let found = false
let system = await this.getSystem()
return new Promise(async (resolve) => {
await asyncForEach(system, async (m) => {
if (m.id == id) {
found = true
resolve(m.content)
}
})
if (!found) resolve({ "name": "Unknown member" })
})
}
/**
* @param {string} member
*/
findMember = async (member) => {
let found = false
let system = await this.getSystem()
return new Promise(async (resolve) => {
await asyncForEach(system, async (m) => {
if (m.content.name.includes(member)) {
found = true
resolve(m)
}
})
if (!found) resolve({"name": "Unknown member"})
})
}
/**
* @param {string} member
* @param {function} callback
*/
findMemberCallback = async (member, callback) => {
await this.getSystem()
.then(async (system) => {
for (let i in system) {
if (system[i].content.name.includes(member)) {
await callback(system[i])
return
}
}
})
}
createMember = async (member) => {
let valid = await validate.validateSchema(schemas.memberSchema, member)
if (valid) {
return axios.post(`${this.url}/v1/member/`, JSON.stringify(member), {
headers: this.header,
})
.then((response) => response)
.catch(err => console.error(err.toJSON().message));
} else {
let response = {}
response.data = { status: 'error', message: 'Invalid group schema' }
return response
}
}
deleteMember = async (member) => {
return await axios.delete(`${this.url}/v1/member/${member}`, {
headers: this.header,
})
.then((response) => response)
.catch(err => console.error(err.toJSON().message));
}
getFronters = async () => {
return await axios.get(`${this.url}/v1/fronters/`, {
headers: this.header,
})
.then((response) => response.data)
.catch(err => console.error(err.toJSON().message));
}
}
asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
module.exports = SimplyAPI

View file

@ -1,11 +0,0 @@
const Ajv = require('ajv')
const ajv = new Ajv()
class Validate {
static validateSchema = async (schema, body) => {
const validate = ajv.compile(schema)
return validate(body)
}
}
module.exports = Validate

View file

@ -1,20 +0,0 @@
{
"name": "simplyapi",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "padlocks",
"license": "ISC",
"optionalDependencies": {
"bufferutil": "^4.0.6",
"utf-8-validate": "^5.0.8"
},
"dependencies": {
"ajv": "^8.10.0",
"axios": "^0.26.0",
"ws": "^8.5.0"
}
}

View file

@ -1,15 +1,14 @@
const dotenv = require('dotenv') const dotenv = require('dotenv')
dotenv.config() dotenv.config()
const config = process.env //const config = process.env
const axios = require('axios') const axios = require('axios')
const SAPI = require('./SimplyAPI') const { Config, System, FrontHistory } = require('SimplyAPI')
const SimplyAPI = new SAPI(config)
const pkUrl = config.pk_url const pkUrl = Config.pk_url
const pkHeader = { const pkHeader = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': config.pk_token 'Authorization': Config.pk_token
} }
let e let e
@ -20,8 +19,8 @@ main = async () => {
openWebSocket = async () => { openWebSocket = async () => {
const WebSocketClient = require('./WebsocketClient') const WebSocketClient = require('./WebsocketClient')
const wss = new WebSocketClient(config.socket); const wss = new WebSocketClient(Config.socket);
let initialPacket = { "op": "authenticate", "token": config.token } let initialPacket = { "op": "authenticate", "token": Config.token }
wss.onOpen = (_) => { wss.send(JSON.stringify(initialPacket)); } wss.onOpen = (_) => { wss.send(JSON.stringify(initialPacket)); }
wss.onClose = (e) => { console.log('SimplyWS/onClose :: %s', e); e = '' } wss.onClose = (e) => { console.log('SimplyWS/onClose :: %s', e); e = '' }
wss.onError = (e) => { console.log('SimplyWS/onError :: %s', e) } wss.onError = (e) => { console.log('SimplyWS/onError :: %s', e) }
@ -35,7 +34,8 @@ openWebSocket = async () => {
case "Successfully authenticated": case "Successfully authenticated":
console.log('::SimplyWS:: authenticated') console.log('::SimplyWS:: authenticated')
// cache current front // cache current front
cache.frontHistory = await SimplyAPI.getFronters() let system = new System(Config)
cache.frontHistory = await system.getFronters()
break; break;
case "Authentication violation: Token is missing or invalid. Goodbye :)": case "Authentication violation: Token is missing or invalid. Goodbye :)":
console.log('::SimplyWS:: invalid token, exiting..') console.log('::SimplyWS:: invalid token, exiting..')
@ -45,7 +45,7 @@ openWebSocket = async () => {
if (response) console.log('::SimplyWS:: ' + response) if (response) console.log('::SimplyWS:: ' + response)
break; break;
default: default:
unrecognizedMessage(data.msg) //unrecognizedMessage(data.msg)
break; break;
} }
} }
@ -57,12 +57,13 @@ generateResponse = async (target, data) => {
case 'frontHistory': case 'frontHistory':
//response += 'Front has changed!' //response += 'Front has changed!'
await asyncForEach(data.results, async (o) => { await asyncForEach(data.results, async (o) => {
await SimplyAPI.findMemberById(o.content.member) let system = new System(Config)
await system.getMemberById(o.content.member)
.then(async (member) => { .then(async (member) => {
if (o.operationType == "insert") { if (o.operationType == "insert") {
// get current fronters and add new fronter // get current fronters and add new fronter
let fronters = await getPKFronters() let fronters = await getPKFronters()
fronters.push(member.pkId) fronters.push(member.content.pkId)
// find the "primary" fronter to move to the first element in the list // find the "primary" fronter to move to the first element in the list
let primary = findPrimary() let primary = findPrimary()
@ -74,10 +75,10 @@ generateResponse = async (target, data) => {
} }
// cache front // cache front
cache.frontHistory = await SimplyAPI.getFronters() cache.frontHistory = await system.getFronters()
// post the new switch // post the new switch
axios.post(`${pkUrl}/systems/${config.pk_system}/switches`, JSON.stringify({"members": fronters}), { axios.post(`${pkUrl}/systems/${Config.pk_system}/switches`, JSON.stringify({"members": fronters}), {
headers: pkHeader headers: pkHeader
}) })
.catch(err => { .catch(err => {
@ -85,18 +86,18 @@ generateResponse = async (target, data) => {
else console.error(err.message) else console.error(err.message)
}) })
response += '' + member.name + ' was added to the front.' response += '' + member.content.name + ' was added to the front.'
return return
} }
else { else {
// get current fronters and patch the list // get current fronters and patch the list
let fronters = await getPKFronters() let fronters = await getPKFronters()
let frontData = await SimplyAPI.getFronters() let frontData = await system.getFronters()
let action = await determineAction(o, frontData) let action = await determineAction(o, frontData)
// if delete operation, remove the member from the list // if delete operation, remove the member from the list
switch (action) { switch (action) {
case "remove": case "remove":
let index = fronters.indexOf(member.pkId) let index = fronters.indexOf(member.content.pkId)
fronters.splice(index, 1) fronters.splice(index, 1)
// find the "primary" fronter to move to the first element in the list // find the "primary" fronter to move to the first element in the list
@ -109,10 +110,10 @@ generateResponse = async (target, data) => {
} }
// cache front // cache front
cache.frontHistory = await SimplyAPI.getFronters() cache.frontHistory = await system.getFronters()
// post the new switch // post the new switch
axios.post(`${pkUrl}/systems/${config.pk_system}/switches`, JSON.stringify({ "members": fronters }), { axios.post(`${pkUrl}/systems/${Config.pk_system}/switches`, JSON.stringify({ "members": fronters }), {
headers: pkHeader headers: pkHeader
}) })
.catch(err => { .catch(err => {
@ -120,7 +121,7 @@ generateResponse = async (target, data) => {
else console.error(err.message) else console.error(err.message)
}) })
response += '' + member.name + ' was removed from the front.' response += '' + member.content.name + ' was removed from the front.'
break; break;
case "customStatus": case "customStatus":
@ -132,21 +133,21 @@ generateResponse = async (target, data) => {
fronters.unshift(primary) fronters.unshift(primary)
// cache front // cache front
cache.frontHistory = await SimplyAPI.getFronters() cache.frontHistory = await system.getFronters()
// post the new switch // post the new switch
axios.post(`${pkUrl}/systems/${config.pk_system}/switches`, JSON.stringify({ "members": fronters }), { axios.post(`${pkUrl}/systems/${Config.pk_system}/switches`, JSON.stringify({ "members": fronters }), {
headers: pkHeader headers: pkHeader
}) })
.catch(err => { .catch(err => {
if (err.toJSON().status == 400) unknownError400() if (err.toJSON().status == 400) unknownError400()
else console.error(err.message) else console.error(err.message)
}) })
response += '' + member.name + ' is now the primary fronter.' response += '' + member.content.name + ' is now the primary fronter.'
} }
} }
else { else {
response += '' + member.name + ' changed custom status.' response += '' + member.content.name + ' changed custom status.'
} }
break; break;
} }
@ -159,7 +160,7 @@ generateResponse = async (target, data) => {
}) })
break; break;
default: default:
unknownTarget(data.target) //unknownTarget(data.target)
break; break;
} }
return response return response
@ -177,21 +178,9 @@ unrecognizedMessage = (msg) => {
console.log('::SimplyWS:: Unrecognized message: ' + msg + '\n::SimplyWS:: Full message: ' + e) 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 () => { getPKFronters = async () => {
let members = [] let members = []
let fronters = await axios.get(`${pkUrl}/systems/${config.pk_system}/fronters`, { let fronters = await axios.get(`${pkUrl}/systems/${Config.pk_system}/fronters`, {
headers: pkHeader headers: pkHeader
}) })
.catch(err => console.error("An error occured while getting current fronters: " + err.message)) .catch(err => console.error("An error occured while getting current fronters: " + err.message))
@ -203,15 +192,16 @@ getPKFronters = async () => {
return members return members
} }
findPrimary = async () => { findPrimary = async () => {
let found = false let found = false
let fronters = await SimplyAPI.getFronters() let system = new System(Config)
let fronters = await system.getFronters()
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
await asyncForEach(fronters, async (fronter) => { await asyncForEach(fronters, async (fronter) => {
if (fronter.content.customStatus) { if (fronter.content.customStatus) {
if (fronter.content.customStatus.toLowerCase().includes("primary")) { if (fronter.content.customStatus.toLowerCase().includes("primary")) {
let member = await SimplyAPI.findMemberById(fronter.content.member) let member = await system.getMemberById(fronter.content.member)
resolve(member.pkId) resolve(member.content.pkId)
found = true found = true
} }
} }
@ -227,7 +217,8 @@ determineAction = async (eventData, frontData = []) => {
// check for cache // check for cache
if (!cache.frontHistory) { if (!cache.frontHistory) {
let frontHistory = await SimplyAPI.getFronters() let system = new System(Config)
let frontHistory = await system.getFronters()
cache.frontHistory = frontHistory cache.frontHistory = frontHistory
} }
@ -235,7 +226,7 @@ determineAction = async (eventData, frontData = []) => {
let diff = await calculateDiff(cache.frontHistory, frontData) let diff = await calculateDiff(cache.frontHistory, frontData)
// we handle one thing at a time, although this should be expanded since you can modify multiple custom statuses at once // we handle one thing at a time, although this should be expanded since you can modify multiple custom statuses at once
if (diff.length >= 1) { if (diff.length >= 1) {
if (diff[0].content.customStatus || eventData.content.customStatus) { if (diff[0].content.customStatus || eventData.content.customStatus || diff[0].content.customStatus == "" || eventData.content.customStatus == "") {
// check if customStatus value is in cache // check if customStatus value is in cache
let foundInCache = Object.keys(cache.frontHistory).filter((key) => { let foundInCache = Object.keys(cache.frontHistory).filter((key) => {
return cache.frontHistory[key] === diff[0].content.customStatus return cache.frontHistory[key] === diff[0].content.customStatus

View file

@ -8,7 +8,7 @@
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "padlocks", "author": "padlocks",
"license": "ISC", "license": "MIT",
"dependencies": { "dependencies": {
"ajv": "^8.10.0", "ajv": "^8.10.0",
"axios": "^0.26.0", "axios": "^0.26.0",
@ -17,6 +17,7 @@
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
"lodash.isobject": "^3.0.2", "lodash.isobject": "^3.0.2",
"lodash.transform": "^4.6.0", "lodash.transform": "^4.6.0",
"simplyapi": "^1.6.0",
"ws": "^8.5.0" "ws": "^8.5.0"
}, },
"optionalDependencies": { "optionalDependencies": {