adaptorex
Version:
Connect all your live interactive storytelling devices and software
534 lines (490 loc) • 15 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
}