adaptorex
Version:
Connect all your live interactive storytelling devices and software
503 lines (458 loc) • 14.6 kB
JavaScript
/** @typedef {import('./types').AdaptorFunction} AdaptorFunction */
/**
* Log arguments and return them the way the came in
*
* @type {AdaptorFunction}
*
* @returns {array} the arguments that came in
*/
function test(args, {session, game}) {
return new Promise((resolve, reject) => {
session.log.info("called test function with args " + args)
return resolve(args)
})
}
/**
* Get a random Integer. Provide maximum only to get a number between 0 (inclusive) and max. Provide min and max argument to get a number between min (inclusive) and max (inclusive)
*
* If you provide no arguments but one or more "next states", random will randomly output to one of the next with an even chance for each next.
*
* @type {AdaptorFunction}
*
* @param {[string, string]} args - 0: With two arguments this is the minimum value. With one argument only, this is the maximum value, 1: Maximum value
* @returns {integer|Object<string, integer>} A random value or, if applicable, triggers one of the specified "next states" chosen randomly.
*/
function random(args, {session, game}) {
let min, max
if(Array.isArray(args) && args.length > 0) {
if(args.length == 1) {
min = 0
max = Math.floor(args[0])
} else if(args.length == 2) {
min = Math.ceil(args[0])
max = Math.floor(args[1])
}
} else {
if(session.action.payload.next && session.action.payload.next.length > 0) {
min = 0
max = session.action.payload.next.length -1
return {next: Math.floor(Math.random() * (max - min + 1) + min)}
}
}
return Math.floor(Math.random() * (max - min + 1) + min)
}
/**
* Get the current Clock Time (Berlin, UTC+2) in 24 hour Format. E.g.: "14:23"
*
* @returns {string} - current time
*/
function getTime() {
return new Promise((resolve, reject) => {
let time = new Date().toLocaleTimeString('de-DE', { hour: 'numeric', hour12: false, minute: 'numeric' })
return resolve(time)
})
}
/**
* Get the date that lies a certain amount of time past or future of some other date.
*
* Add/Subtract argument is formatted `HH:MM:SS` for example: `1:00` add 1 minute or `-23:30:00` subtract 23 and half an hour.
*
* @example
* dateTimeOffset("2023-06-22T12:00","15:00") // results in "2023-06-22T12:15"
*
* dateTimeOffset("2023-12-24T12:00","-48:30:00") // means 2 days and 30 minutes before and results in "2023-12-22T11:30"
*
* @param {[string, string]} args - 0: The date to add or subtract time from in [ISO Format](https://en.wikipedia.org/wiki/ISO_8601). Use "now" to set the current date/time, 1: The amount of time that is supposed to be added or subtracted from the original date (prepend `-` to subtract).
*/
function dateTimeOffset(args) {
/** @type Date */
let origin
if(args[0] == "now") {
origin = adaptor.now()
} else {
origin = new Date(args[0])
}
let offset
if(args[1][0] == "-") {
offset = -hmsToSeconds(args[1].slice(1))
} else {
offset = hmsToSeconds(args[1])
}
if(isNaN(offset)) {
throw new adaptor.InvalidError(`Could not set date-time offset. "${args[1]}" is not a valid time value.`)
}
let offset_date = new Date(origin.setSeconds(origin.getSeconds() + offset))
return offset_date.toISOString()
}
/**
* Convert an [ISO Formatted Time string](https://en.wikipedia.org/wiki/ISO_8601) into another kind of date and or time format.
*
* Uses the common javascript [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat) to convert date and time.
*
* You can pass any of the formatting options Intl.DateTimeFormat allows by providing a two part string argument for each option, e.g. `"short weekday"`.
*
* Pass a locale indicator to set the date and or time locale. E.g. "de-DE" for german or "en-GB" for British english locale.
*
* @example
* dateTimeFormat("2023-06-22","short date","en-US") // results in "06/22/23"
*
* dateTimeFormat("2023-06-22T17:35:12.345","weekday","hour","minute","1 secondDigits","de") // results in "Donnerstag, 17:35:12,3"
*
* @type {AdaptorFunction}
*
* @param {Array<string>} args - First argument is the date that will be formatted in ISO Format. Use "now" to set the current date/time. More arguments can be a locale and other formatting options (see above)
* @returns {string} - A date and or time format
*/
function dateTimeFormat(args, {session}) {
let date_arg = args.shift()
/** @type Date */
let date
if(date_arg == "now") {
date = adaptor.now()
} else {
date = new Date(date_arg)
}
let options = {}
if(args.length) {
for(let i=args.length-1; i>=0;i--) {
let opt = args[i].split(/_| /, 2)
let indicator = opt[0]
let style = ""
if(opt.length > 1) {
style = opt[0]
indicator = opt[1]
}
switch(indicator) {
case "dateStyle":
case "date":
options.dateStyle = style || "full"
args.splice(i,1)
break
case "timeStyle":
case "time":
options.timeStyle = style || "short"
args.splice(i,1)
break
case "calendar":
options.calendar = style || "gregory"
args.splice(i,1)
break
case "numbering":
case "numberingSystem":
options.numberingSystem = style || "arab"
args.splice(i,1)
break
case "formatMatcher":
case "localeMatcher":
options[indicator] = style || "best fit"
args.splice(i,1)
break
case "zone":
case "timeZone":
options.timeZone = style || "UTC"
args.splice(i,1)
break
case "hour12":
options.hour12 = true
args.splice(i,1)
break
case "hour24":
options.hour12 = false
args.splice(i,1)
break
case "cycle":
case "hourCycle":
options.hourCycle = style || "h24"
args.splice(i,1)
break
case "period":
case "dayPeriod":
case "weekday":
case "era":
case "zoneName":
case "timeZoneName":
options[indicator] = style || "long"
args.splice(i,1)
break
case "year":
case "month":
case "day":
case "hour":
case "minute":
case "second":
options[indicator] = style || "numeric"
args.splice(i,1)
break
case "digits":
case "secondDigits":
case "fractionalSecondDigits":
options.fractionalSecondDigits = style || 1
args.splice(i,1)
break
}
}
}
if(adaptor.isEmpty(options)) {
options = {dateStyle: "full", timeStyle: "short"}
}
let locale = args[0] || adaptor.locale
let result = new Intl.DateTimeFormat(locale, options).format(date)
session.log.debug(`Format date and/or time from "${date_arg}" (${locale}) to "${result}"`)
return result
}
/**
*
*/
function hmsToSeconds(str) {
var p = str.split(':'),
s = 0, m = 1;
s += m * parseFloat(p.pop());
m *= 60;
while (p.length > 0) {
s += m * parseInt(p.pop(), 10);
m *= 60;
}
return s;
}
/**
* Get Element in array at a specific index position.
*
* @param {Array<[Array,number]>} param0 - #1 The Array where to find element at index #2 the index number
* @returns {*} Some value
*/
async function atIndex([array, index]) {
return array[index]
}
/**
* Get the number of elements inside an array
*
* @param {Array<[Array]>} param0 - #1 The Array to get the length of
* @returns {number} Array length
*/
async function length([array]) {
return array.length
}
/**
* Get the number of hours that are left until a specific date/time
*
* Number of hours is being rounded down to the next full hour
*
* date/time in the past returns negative number of hours (-n)
*
* @param {string} schedule - UTC formatted date/time string (e.g. 2022-12-24T16:00)
* @param {*} param1
* @returns {integer} number of hours
*/
async function hoursUntil(schedule, {session, game}) {
let today=new Date()
let schedule_date = new Date(schedule)
let one_hour=1000*60*60
let hours_until = Math.floor((schedule_date.getTime()-today.getTime())/(one_hour))
session.log(hours_until + " hours left until " + schedule_date.toLocaleString('de-DE'))
return hours_until
}
/**
* Get the current Date in DD.MM.YYYY format. E.g.: "23.02.2022"
*
* @returns {string} - current date
*/
function getDate() {
return new Promise((resolve, reject) => {
let today = new Date()
let dd = String(today.getDate()).padStart(2, '0');
let mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
let yyyy = today.getFullYear();
return resolve(dd + "." + mm + "." + yyyy)
})
}
/**
* Get the number of milliseconds between midnight of January 1, 1970 and now
*
* @returns {integer} - Number of milliseconds since January 1, 1970
*/
function timestamp() {
return new Promise((resolve, reject) => {
const now = new Date()
const millis = now.getTime()
resolve(millis)
})
}
/**
* Takes a state name as argument and returns the time that passed since it was dispatched in the format hh:mm:ss
*
* @param {string} arg - Name property of a state in the current level
* @returns time that has passed since the state was dispatched last
*/
function since(arg, {session, game}) {
return new Promise((resolve, reject) => {
since_state = arg
game.db.sessions.find({ _id: session._id })
.then(result => {
if (result.length) {
if (result[0].state_data.hasOwnProperty(since_state)) {
let t = new Date(Date.now() - new Date(result[0].state_data[since_state].date))
let date_since = t.toTimeString()
date_since = date_since.split(' ')[0]
return resolve(date_since)
} else {
session.log.error("no such state " + since_state)
return resolve(undefined)
}
} else {
return resolve(undefined)
}
})
.catch(error => {
//log.error(this.name, error)
return reject(error)
})
})
}
/**
* Delete active sessions based on a find query or session name string
*
* @deprecated please use Control cancel action instead
*
* @param {Object|string} args - find query that addresses currently active sessions.
*/
async function cancelSessions(args, {session, game}) {
try {
await game.deleteSession(args[0])
} catch (error) {
if(error instanceof adaptor.NotFoundError) {
session.log.error(error)
} else {
throw error
}
}
}
/**
* cancel sessions based on player reference
*
* @param {string} args - player reference name
*/
function cancelPlayerSessions(args, refs) {
return new Promise((resolve, reject) => {
let name = "cancelPlayerSessions"
let reference = refs.variables.getReference(args)
if (!reference) {
return reject(name + " " + args + " is not a valid reference")
}
let query = reference.query
let collection = "players"
let key = 'sessions._id'
let value = query.sessions._id
//log.info("REFERENCE", reference)
let match = {}
//match['player.sessions._id'] = query.sessions._id
match[collection + '.' + key] = value
// log.info("MATCH", match)
if (!refs.db.hasOwnProperty(collection)) {
//log.error(name, collection + " is not an existing collection")
return reject(name + ": " + collection + " is not an existing collection")
}
refs.db.sessions.c.aggregate([{
$lookup:
{
from: collection,
localField: "_id",
foreignField: "sessions._id",
as: collection
}
},
{
$match: match
},
{ $project: { _id: 1 } }
]).toArray()
.then(result => {
if (!result.length) {
log.warn(name, key + ":" + value + " did not return any results in " + collection)
return resolve(undefined)
}
let object_ids = []
for (let res of result) {
if (!refs._id.equals(res._id)) {
object_ids.push(res._id)
}
}
if (object_ids.length) {
log.info(name, "cancel sessions with references to " + JSON.stringify(match))
log.info(name, object_ids)
refs.game_event.emit('cancel', { _id: { $in: object_ids } })
} else {
log.info(name, "no session to delete.")
}
return resolve(undefined)
})
.catch(error => {
return reject(error)
})
})
}
/**
* cancel sessions based on references
*
* @param {string} args.0 - name of collection to find references in
* @param {string} args.1 - key part of query that finds documents in that collection
* @param {string} args.2 - value part of query that finds documents in that collection
*/
function cancelSessionsFor(args, {session, game}) {
return new Promise((resolve, reject) => {
let name = "cancelSessions"
let collection = args[0]
let key = args[1]
let value = args[2]
let match = {}
match[collection + '.' + key] = value
if (!game.db.hasOwnProperty(collection)) {
//log.error(name, collection + " is not an existing collection")
return reject(name + ": " + collection + " is not an existing collection")
}
game.db.sessions.c.aggregate([{
$lookup:
{
from: collection,
localField: "_id",
foreignField: "sessions._id",
as: collection
}
},
{
$match: match
},
{ $project: { _id: 1 } }
]).toArray()
.then(result => {
if (!result.length) {
log.warn(name, key + ":" + value + " did not return any results in " + collection)
return resolve(undefined)
}
let object_ids = []
for (let res of result) {
object_ids.push(res._id)
}
log.info(name, "cancel sessions with references to " + JSON.stringify(match))
log.info(name, object_ids)
game.deleteSession({ _id: { $in: object_ids } })
return resolve(undefined)
})
.catch(error => {
return reject(error)
})
})
}
module.exports = {
test: test,
random:random,
hoursUntil:hoursUntil,
getTime: getTime,
getDate: getDate,
timestamp: timestamp,
since:since,
dateTimeOffset:dateTimeOffset,
dateTimeFormat:dateTimeFormat,
atIndex: atIndex,
length: length,
cancelSessions: cancelSessions
}