2023-01-27 03:22:44 -08:00
import type {
2023-03-09 09:13:15 -08:00
IExecuteFunctions ,
2019-06-23 03:35:23 -07:00
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
2023-06-16 07:26:35 -07:00
import set from 'lodash/set' ;
2024-01-17 01:15:20 -08:00
import {
setupRedisClient ,
redisConnectionTest ,
convertInfoToObject ,
getValue ,
setValue ,
} from './utils' ;
2019-06-23 03:35:23 -07:00
export class Redis implements INodeType {
description : INodeTypeDescription = {
displayName : 'Redis' ,
name : 'redis' ,
2021-03-25 09:10:02 -07:00
icon : 'file:redis.svg' ,
2019-06-23 03:35:23 -07:00
group : [ 'input' ] ,
version : 1 ,
2021-07-03 05:40:16 -07:00
description : 'Get, send and update data in Redis' ,
2019-06-23 03:35:23 -07:00
defaults : {
name : 'Redis' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
credentials : [
{
name : 'redis' ,
required : true ,
2022-09-01 05:29:15 -07:00
testedBy : 'redisConnectionTest' ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
] ,
properties : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
2022-05-20 14:47:24 -07:00
noDataExpression : true ,
2019-06-23 03:35:23 -07:00
options : [
{
name : 'Delete' ,
value : 'delete' ,
2022-05-06 14:01:25 -07:00
description : 'Delete a key from Redis' ,
2022-07-10 13:50:51 -07:00
action : 'Delete a key from Redis' ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Get' ,
value : 'get' ,
2022-05-06 14:01:25 -07:00
description : 'Get the value of a key from Redis' ,
2022-07-10 13:50:51 -07:00
action : 'Get the value of a key from Redis' ,
2019-06-23 03:35:23 -07:00
} ,
2021-05-07 16:33:14 -07:00
{
name : 'Increment' ,
value : 'incr' ,
description : 'Atomically increments a key by 1. Creates the key if it does not exist.' ,
2022-07-10 13:50:51 -07:00
action : 'Atomically increment a key by 1. Creates the key if it does not exist.' ,
2021-05-07 16:33:14 -07:00
} ,
2022-06-03 10:23:49 -07:00
{
name : 'Info' ,
value : 'info' ,
description : 'Returns generic information about the Redis instance' ,
2022-07-10 13:50:51 -07:00
action : 'Return generic information about the Redis instance' ,
2022-06-03 10:23:49 -07:00
} ,
2019-06-23 03:35:23 -07:00
{
name : 'Keys' ,
value : 'keys' ,
2022-05-06 14:01:25 -07:00
description : 'Returns all the keys matching a pattern' ,
2022-07-10 13:50:51 -07:00
action : 'Return all keys matching a pattern' ,
2019-06-23 03:35:23 -07:00
} ,
2022-07-09 23:12:18 -07:00
{
name : 'Pop' ,
value : 'pop' ,
description : 'Pop data from a redis list' ,
2022-07-13 01:38:12 -07:00
action : 'Pop data from a redis list' ,
2022-07-09 23:12:18 -07:00
} ,
2022-03-12 03:14:39 -08:00
{
name : 'Publish' ,
value : 'publish' ,
2022-05-06 14:01:25 -07:00
description : 'Publish message to redis channel' ,
2022-07-10 13:50:51 -07:00
action : 'Publish message to redis channel' ,
2022-03-12 03:14:39 -08:00
} ,
2022-07-09 23:12:18 -07:00
{
name : 'Push' ,
value : 'push' ,
description : 'Push data to a redis list' ,
2022-07-13 01:38:12 -07:00
action : 'Push data to a redis list' ,
2022-07-09 23:12:18 -07:00
} ,
2022-06-03 10:23:49 -07:00
{
name : 'Set' ,
value : 'set' ,
description : 'Set the value of a key in redis' ,
2022-07-10 13:50:51 -07:00
action : 'Set the value of a key in redis' ,
2022-06-03 10:23:49 -07:00
} ,
2019-06-23 03:35:23 -07:00
] ,
default : 'info' ,
} ,
// ----------------------------------
2024-01-17 01:15:20 -08:00
// delete
2019-06-23 03:35:23 -07:00
// ----------------------------------
{
2024-01-17 01:15:20 -08:00
displayName : 'Key' ,
name : 'key' ,
2019-06-23 03:35:23 -07:00
type : 'string' ,
displayOptions : {
show : {
2024-01-17 01:15:20 -08:00
operation : [ 'delete' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
2024-01-17 01:15:20 -08:00
default : '' ,
2019-06-23 03:35:23 -07:00
required : true ,
2024-01-17 01:15:20 -08:00
description : 'Name of the key to delete from Redis' ,
2019-06-23 03:35:23 -07:00
} ,
2024-01-17 01:15:20 -08:00
// ----------------------------------
// get
// ----------------------------------
2019-06-23 03:35:23 -07:00
{
2024-01-17 01:15:20 -08:00
displayName : 'Name' ,
name : 'propertyName' ,
2019-06-23 03:35:23 -07:00
type : 'string' ,
displayOptions : {
show : {
2024-01-17 01:15:20 -08:00
operation : [ 'get' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
2024-01-17 01:15:20 -08:00
default : 'propertyName' ,
2019-06-23 03:35:23 -07:00
required : true ,
2024-01-17 01:15:20 -08:00
description :
'Name of the property to write received data to. Supports dot-notation. Example: "data.person[0].name".' ,
2019-06-23 03:35:23 -07:00
} ,
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'get' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Name of the key to get from Redis' ,
2019-06-23 03:35:23 -07:00
} ,
{
displayName : 'Key Type' ,
name : 'keyType' ,
type : 'options' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'get' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
options : [
{
name : 'Automatic' ,
value : 'automatic' ,
2022-05-06 14:01:25 -07:00
description : 'Requests the type before requesting the data (slower)' ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Hash' ,
value : 'hash' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'hash'" ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'List' ,
value : 'list' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'lists'" ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Sets' ,
value : 'sets' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'sets'" ,
2022-06-03 10:23:49 -07:00
} ,
{
name : 'String' ,
value : 'string' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'string'" ,
2019-06-23 03:35:23 -07:00
} ,
] ,
default : 'automatic' ,
2022-05-06 14:01:25 -07:00
description : 'The type of the key to get' ,
2019-06-23 03:35:23 -07:00
} ,
2020-05-05 06:52:47 -07:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'get' ] ,
2020-05-05 06:52:47 -07:00
} ,
} ,
placeholder : 'Add Option' ,
default : { } ,
options : [
{
displayName : 'Dot Notation' ,
name : 'dotNotation' ,
type : 'boolean' ,
default : true ,
2022-06-20 07:54:01 -07:00
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2022-08-17 08:50:24 -07:00
description :
'<p>By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.<p></p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>.' ,
2020-05-05 06:52:47 -07:00
} ,
] ,
} ,
2021-05-07 16:33:14 -07:00
// ----------------------------------
// incr
// ----------------------------------
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'incr' ] ,
2021-05-07 16:33:14 -07:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Name of the key to increment' ,
2021-05-07 16:33:14 -07:00
} ,
{
displayName : 'Expire' ,
name : 'expire' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'incr' ] ,
2021-05-07 16:33:14 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to set a timeout on key' ,
2021-05-07 16:33:14 -07:00
} ,
{
displayName : 'TTL' ,
name : 'ttl' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'incr' ] ,
expire : [ true ] ,
2021-05-07 16:33:14 -07:00
} ,
} ,
default : 60 ,
2022-05-06 14:01:25 -07:00
description : 'Number of seconds before key expiration' ,
2021-05-07 16:33:14 -07:00
} ,
2019-06-23 03:35:23 -07:00
// ----------------------------------
// keys
// ----------------------------------
{
displayName : 'Key Pattern' ,
name : 'keyPattern' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'keys' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'The key pattern for the keys to return' ,
2019-06-23 03:35:23 -07:00
} ,
2022-07-09 23:12:18 -07:00
{
displayName : 'Get Values' ,
name : 'getValues' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'keys' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
default : true ,
description : 'Whether to get the value of matching keys' ,
} ,
2019-06-23 03:35:23 -07:00
// ----------------------------------
// set
// ----------------------------------
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'set' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Name of the key to set in Redis' ,
2019-06-23 03:35:23 -07:00
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'set' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
default : '' ,
2022-05-06 14:01:25 -07:00
description : 'The value to write in Redis' ,
2019-06-23 03:35:23 -07:00
} ,
{
displayName : 'Key Type' ,
name : 'keyType' ,
type : 'options' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'set' ] ,
2019-06-23 03:35:23 -07:00
} ,
} ,
options : [
{
name : 'Automatic' ,
value : 'automatic' ,
2022-05-06 14:01:25 -07:00
description : 'Tries to figure out the type automatically depending on the data' ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Hash' ,
value : 'hash' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'hash'" ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'List' ,
value : 'list' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'lists'" ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Sets' ,
value : 'sets' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'sets'" ,
2022-06-03 10:23:49 -07:00
} ,
{
name : 'String' ,
value : 'string' ,
2022-08-17 08:50:24 -07:00
description : "Data in key is of type 'string'" ,
2019-06-23 03:35:23 -07:00
} ,
] ,
default : 'automatic' ,
2022-05-06 14:01:25 -07:00
description : 'The type of the key to set' ,
2019-06-23 03:35:23 -07:00
} ,
2023-03-30 08:04:34 -07:00
{
displayName : 'Value Is JSON' ,
name : 'valueIsJSON' ,
type : 'boolean' ,
displayOptions : {
show : {
keyType : [ 'hash' ] ,
} ,
} ,
default : true ,
description : 'Whether the value is JSON or key value pairs' ,
} ,
2020-01-08 02:27:39 -08:00
{
displayName : 'Expire' ,
name : 'expire' ,
type : 'boolean' ,
2020-01-08 03:22:18 -08:00
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'set' ] ,
2020-01-08 03:22:18 -08:00
} ,
} ,
2020-01-08 02:27:39 -08:00
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to set a timeout on key' ,
2020-01-08 02:27:39 -08:00
} ,
{
displayName : 'TTL' ,
name : 'ttl' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'set' ] ,
expire : [ true ] ,
2020-01-08 02:27:39 -08:00
} ,
} ,
default : 60 ,
2022-05-06 14:01:25 -07:00
description : 'Number of seconds before key expiration' ,
2020-10-22 06:46:03 -07:00
} ,
2022-03-12 03:14:39 -08:00
// ----------------------------------
// publish
// ----------------------------------
{
displayName : 'Channel' ,
name : 'channel' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'publish' ] ,
2022-03-12 03:14:39 -08:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Channel name' ,
2022-03-12 03:14:39 -08:00
} ,
{
displayName : 'Data' ,
name : 'messageData' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'publish' ] ,
2022-03-12 03:14:39 -08:00
} ,
} ,
default : '' ,
required : true ,
2022-05-06 14:01:25 -07:00
description : 'Data to publish' ,
2022-03-12 03:14:39 -08:00
} ,
2022-07-09 23:12:18 -07:00
// ----------------------------------
// push/pop
// ----------------------------------
{
displayName : 'List' ,
name : 'list' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'push' , 'pop' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the list in Redis' ,
} ,
{
displayName : 'Data' ,
name : 'messageData' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'push' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
default : '' ,
required : true ,
description : 'Data to push' ,
} ,
{
displayName : 'Tail' ,
name : 'tail' ,
type : 'boolean' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'push' , 'pop' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
default : false ,
description : 'Whether to push or pop data from the end of the list' ,
} ,
{
displayName : 'Name' ,
name : 'propertyName' ,
type : 'string' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'pop' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
default : 'propertyName' ,
2022-08-17 08:50:24 -07:00
description :
'Optional name of the property to write received data to. Supports dot-notation. Example: "data.person[0].name".' ,
2022-07-09 23:12:18 -07:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
displayOptions : {
show : {
2022-08-17 08:50:24 -07:00
operation : [ 'pop' ] ,
2022-07-09 23:12:18 -07:00
} ,
} ,
placeholder : 'Add Option' ,
default : { } ,
options : [
{
displayName : 'Dot Notation' ,
name : 'dotNotation' ,
type : 'boolean' ,
default : true ,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
2022-08-17 08:50:24 -07:00
description :
'<p>By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.<p></p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>.' ,
2022-07-09 23:12:18 -07:00
} ,
] ,
} ,
2020-10-22 06:46:03 -07:00
] ,
2019-06-23 03:35:23 -07:00
} ;
2022-09-01 05:29:15 -07:00
methods = {
2024-01-17 01:15:20 -08:00
credentialTest : { redisConnectionTest } ,
2022-09-01 05:29:15 -07:00
} ;
2024-01-17 01:15:20 -08:00
async execute ( this : IExecuteFunctions ) {
// TODO: For array and object fields it should not have a "value" field it should
// have a parameter field for a path. Because it is not possible to set
// array, object via parameter directly (should maybe be possible?!?!)
// Should maybe have a parameter which is JSON.
const credentials = await this . getCredentials ( 'redis' ) ;
const client = setupRedisClient ( credentials ) ;
await client . connect ( ) ;
await client . ping ( ) ;
const operation = this . getNodeParameter ( 'operation' , 0 ) ;
const returnItems : INodeExecutionData [ ] = [ ] ;
try {
if ( operation === 'info' ) {
const result = await client . info ( ) ;
returnItems . push ( { json : convertInfoToObject ( result ) } ) ;
} else if (
[ 'delete' , 'get' , 'keys' , 'set' , 'incr' , 'publish' , 'push' , 'pop' ] . includes ( operation )
) {
const items = this . getInputData ( ) ;
let item : INodeExecutionData ;
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
2024-01-19 03:47:25 -08:00
item = { json : { } , pairedItem : { item : itemIndex } } ;
2024-01-17 01:15:20 -08:00
if ( operation === 'delete' ) {
const keyDelete = this . getNodeParameter ( 'key' , itemIndex ) as string ;
await client . del ( keyDelete ) ;
returnItems . push ( items [ itemIndex ] ) ;
} else if ( operation === 'get' ) {
const propertyName = this . getNodeParameter ( 'propertyName' , itemIndex ) as string ;
const keyGet = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const keyType = this . getNodeParameter ( 'keyType' , itemIndex ) as string ;
const value = ( await getValue ( client , keyGet , keyType ) ) ? ? null ;
const options = this . getNodeParameter ( 'options' , itemIndex , { } ) ;
if ( options . dotNotation === false ) {
item . json [ propertyName ] = value ;
} else {
set ( item . json , propertyName , value ) ;
}
2019-06-23 03:35:23 -07:00
2024-01-17 01:15:20 -08:00
returnItems . push ( item ) ;
} else if ( operation === 'keys' ) {
const keyPattern = this . getNodeParameter ( 'keyPattern' , itemIndex ) as string ;
const getValues = this . getNodeParameter ( 'getValues' , itemIndex , true ) as boolean ;
2019-06-23 03:35:23 -07:00
2024-01-17 01:15:20 -08:00
const keys = await client . keys ( keyPattern ) ;
2019-06-23 03:35:23 -07:00
2024-01-17 01:15:20 -08:00
if ( ! getValues ) {
returnItems . push ( { json : { keys } } ) ;
continue ;
}
2019-06-23 03:35:23 -07:00
2024-01-17 01:15:20 -08:00
for ( const keyName of keys ) {
item . json [ keyName ] = await getValue ( client , keyName ) ;
}
returnItems . push ( item ) ;
} else if ( operation === 'set' ) {
const keySet = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const value = this . getNodeParameter ( 'value' , itemIndex ) as string ;
const keyType = this . getNodeParameter ( 'keyType' , itemIndex ) as string ;
const valueIsJSON = this . getNodeParameter ( 'valueIsJSON' , itemIndex , true ) as boolean ;
const expire = this . getNodeParameter ( 'expire' , itemIndex , false ) as boolean ;
const ttl = this . getNodeParameter ( 'ttl' , itemIndex , - 1 ) as number ;
await setValue . call ( this , client , keySet , value , expire , ttl , keyType , valueIsJSON ) ;
returnItems . push ( items [ itemIndex ] ) ;
} else if ( operation === 'incr' ) {
const keyIncr = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const expire = this . getNodeParameter ( 'expire' , itemIndex , false ) as boolean ;
const ttl = this . getNodeParameter ( 'ttl' , itemIndex , - 1 ) as number ;
const incrementVal = await client . incr ( keyIncr ) ;
if ( expire && ttl > 0 ) {
await client . expire ( keyIncr , ttl ) ;
}
returnItems . push ( { json : { [ keyIncr ] : incrementVal } } ) ;
} else if ( operation === 'publish' ) {
const channel = this . getNodeParameter ( 'channel' , itemIndex ) as string ;
const messageData = this . getNodeParameter ( 'messageData' , itemIndex ) as string ;
await client . publish ( channel , messageData ) ;
returnItems . push ( items [ itemIndex ] ) ;
} else if ( operation === 'push' ) {
const redisList = this . getNodeParameter ( 'list' , itemIndex ) as string ;
const messageData = this . getNodeParameter ( 'messageData' , itemIndex ) as string ;
const tail = this . getNodeParameter ( 'tail' , itemIndex , false ) as boolean ;
await client [ tail ? 'rPush' : 'lPush' ] ( redisList , messageData ) ;
returnItems . push ( items [ itemIndex ] ) ;
} else if ( operation === 'pop' ) {
const redisList = this . getNodeParameter ( 'list' , itemIndex ) as string ;
const tail = this . getNodeParameter ( 'tail' , itemIndex , false ) as boolean ;
const propertyName = this . getNodeParameter (
'propertyName' ,
itemIndex ,
'propertyName' ,
) as string ;
const value = await client [ tail ? 'rPop' : 'lPop' ] ( redisList ) ;
let outputValue ;
2023-03-30 08:04:34 -07:00
try {
2024-01-17 01:15:20 -08:00
outputValue = value && JSON . parse ( value ) ;
2023-03-30 08:04:34 -07:00
} catch {
2024-01-17 01:15:20 -08:00
outputValue = value ;
2023-03-30 08:04:34 -07:00
}
2024-01-17 01:15:20 -08:00
const options = this . getNodeParameter ( 'options' , itemIndex , { } ) ;
if ( options . dotNotation === false ) {
item . json [ propertyName ] = outputValue ;
} else {
set ( item . json , propertyName , outputValue ) ;
}
returnItems . push ( item ) ;
2023-03-30 08:04:34 -07:00
}
2019-06-23 03:35:23 -07:00
}
2020-01-08 02:27:39 -08:00
}
2024-01-17 01:15:20 -08:00
} catch ( error ) {
throw error ;
} finally {
await client . quit ( ) ;
}
2019-08-01 13:55:33 -07:00
2024-01-17 01:15:20 -08:00
return [ returnItems ] ;
2019-06-23 03:35:23 -07:00
}
}