Botmation Documentation
Pipe
Pipe
These BotAction's manipulate the value of a Pipe object. They should not be used in a Chain.
Map
Map transforms the Pipe value with the provided a pure function.
const map = <R extends PipeValue = PipeValue>(mapFunction: (pipedValue: any) => R): BotAction<R> => async (page, ...injects) => mapFunction(getInjectsPipeValue(injects))
For an usage example see Instagram's isGuest BotAction.
Pipe Value
This can be used to set the Pipe value for the next BotAction in an Assembly Line.
const pipeValue = <R extends PipeValue = PipeValue>(valueToPipe: R|undefined): BotAction<R|undefined> => async () => valueToPipe
Example:
await pipe()( pipeValue('Hello World'), log() // logs 'Pipe: Hello World')(page)
Empty Pipe
This BotAction simply returns undefined
which empties the Pipe. An empty Pipe is one that value
is undefined
.
const emptyPipe: BotAction = async () => undefined
Example:
await pipe('Hello World')( log(), // logs 'Pipe: Hello World' emptyPipe, log(), // logs 'Pipe: Empty')(page)
Pipe Case
This BotAction is similar to givenThat()() in that it evaluates a condition, or in this case conditions, and if at least one condition passes (is true), then the assembled BotAction's are ran. Instead of providing ConditionalBotAction's, the first call pipeCase()
accepts a spread array of PipeValue's, including Functions. The values provided are tested against the pipe object's value. If the value is a function, it's called as a callback with the pipe object's value (as the first param) to test the return value as true.
Pipe Case is similar to an if statement where the conditions of the expression are combined with
||
or. At least one case must evaluate astrue
in order for the if statement to be considered true.
const pipeCase = (...valuesToTest: CaseValue[]) => (...actions: BotAction<PipeValue|AbortLineSignal|void>[]): BotAction<PipeValue|AbortLineSignal|CasesSignal> => async(page, ...injects) => { if (injectsHavePipe(injects)) { const pipeObjectValue = getInjectsPipeValue(injects)
const matches: Dictionary = valuesToTest.reduce((foundMatches, value, index) => { if (typeof value === 'function') { if (value(pipeObjectValue)) { (foundMatches as Dictionary)[index] = value } } else { if (value === pipeObjectValue) { (foundMatches as Dictionary)[index] = value } }
return foundMatches }, {}) as Dictionary
if (Object.keys(matches).length > 0) { const returnValue:PipeValue|AbortLineSignal = await pipe()(...actions)(page, ...injects)
if (isAbortLineSignal(returnValue)) { return processAbortLineSignal(returnValue) } else { return createCasesSignal(matches, true, returnValue) } } else { return createCasesSignal(matches, false, pipeObjectValue) } }
return createCasesSignal() }
pipeCase()()
similar to pipeCases()()
returns an unique JSON object with the CasesSignal type. This standardizes the response to interpret what case(s) matched the pipe object value, whether or not the if statement, as a whole condition, passed. Furthermore, there's an optional pipeValue
to carry back from the result of the assembled BotAction's, which are ran in a Pipe.
It takes assembledLines
of at least 2 to abort a pipeCase()()
from an assembled BotAction. Here's a table that explains it's aborting behavior given the number of assembledLines
of an AbortLineSignal returned by an assembled BotAction.
assembledLines | effect |
---|---|
0 | breaks pipeCase()() line, returns the same AbortLineSignal |
1 | breaks pipeCase()() line, returns a CasesSignal with the AbortLineSignal.pipeValue |
2 | breaks pipeCase()() line, returns AbortLineSignal's pipeValue |
3+ | breaks pipeCase()() line, returns same AbortLineSignal with its assembledLines count reduced by 2 |
For an example, see LinkedIn's likeUserPostsFrom() BotAction.
Pipe Cases
This BotAction is similar to givenThat()() in that it evaluates a condition, or in this case conditions, and if all conditions pass (are true), then the assembled BotAction's are ran. Instead of providing ConditionalBotAction's, the first call pipeCases()
accepts a spread array of PipeValue's, including functions. The values provided are tested against the pipe object's value. If the value is a function, it's called as a callback with the pipe object's value (as the first param) to test the return value as true.
Pipe Cases is similar to an if statement where the conditions of the expression are combined with
&&
and. All of the cases must evaluate totrue
, in order for the if statement to be considered true.
const pipeCases = (...valuesToTest: CaseValue[]) => (...actions: BotAction<PipeValue|AbortLineSignal|void>[]): BotAction<PipeValue|AbortLineSignal|CasesSignal> => async(page, ...injects) => { const matches: Dictionary = {}
if (injectsHavePipe(injects)) { const pipeObjectValue = getInjectsPipeValue(injects)
for (const [i, value] of valuesToTest.entries()) { if (typeof value === 'function') { if (value(pipeObjectValue)) { matches[i] = value } else { break } } else { if (value === pipeObjectValue) { matches[i] = value } else { break } } }
if (Object.keys(matches).length === valuesToTest.length) { const returnValue:PipeValue|AbortLineSignal = await pipe()(...actions)(page, ...injects)
if (isAbortLineSignal(returnValue)) { return processAbortLineSignal(returnValue) } else { return createCasesSignal(matches, true, returnValue) } } else { return createCasesSignal(matches, false, pipeObjectValue) } }
return createCasesSignal(matches) }
pipeCases()()
similar to pipeCase()()
returns an unique JSON object with the CasesSignal type. This standardizes the response to interpret what case(s) matched the pipe object value, whether or not the if statement, as a whole condition, passed. Furthermore, there's an optional pipeValue
to carry back from the result of the assembled BotAction's, which are ran in a Pipe.
It takes assembledLines
of at least 2 to abort a pipeCase()()
from an assembled BotAction. Here's a table that explains it's aborting behavior given the number of assembledLines
of an AbortLineSignal returned by an assembled BotAction.
assembledLines | effect |
---|---|
0 | breaks pipeCase()() line, returns the same AbortLineSignal |
1 | breaks pipeCase()() line, returns a CasesSignal with the AbortLineSignal.pipeValue |
2 | breaks pipeCase()() line, returns AbortLineSignal's pipeValue |
3+ | breaks pipeCase()() line, returns same AbortLineSignal with its assembledLines count reduced by 2 |
For an example, see LinkedIn's likeUserPostsFrom() BotAction.
Helpers
These Helpers are for all BotAction's, including the ones focused just on Chain. They are functions designed to be Assembly Line safe, for creating BotAction's that can safely use a Pipe, but safely run in Chain with safe fallbacks.
unpipeInjects()
This function allows you to unwrap the Pipe value from the Pipe object and specify the number of injects that you are expecting. Therefore, if the BotAction runs with less injects
, it can fill them with undefined
or what you specify.
const unpipeInjects = <P extends PipeValue = PipeValue>(injectsMaybePiped: any[], minimumInjectsCount = 0, minimumInjectsFill = undefined): [P, ...any[]] => { if (minimumInjectsCount > 0 && injectsMaybePiped.length < minimumInjectsCount) { const minimumInjects = [] for(let i = 0; i < minimumInjectsCount; i++) { // check for inject AND that the inject isn't a Pipe if (injectsMaybePiped[i] && !isPipe(injectsMaybePiped[i])) { minimumInjects.push(injectsMaybePiped[i]) } else { minimumInjects.push(minimumInjectsFill) } }
return [getInjectsPipeValue(injectsMaybePiped), ...minimumInjects] }
return [getInjectsPipeValue(injectsMaybePiped), ...removePipe(injectsMaybePiped)]}
This way you can safely use a Pipe with injects when you may not get the injects
you want or even a Pipe object.
removePipe()
This function removes the Pipe object from injects if injects
have it.
const removePipe = (injects: any[]): any[] => { if (injectsHavePipe(injects)) { return injects.slice(0, injects.length - 1) }
return injects}
It is helpful in making BotAction's that are Chain focused.
getInjectsPipeOrEmptyPipe()
This function gets the Pipe object from injects. If injects
is missing the Pipe object, it returns an empty Pipe with the value undefined
.
const getInjectsPipeOrEmptyPipe = <P = any>(injects: any[]): Pipe<P> => injects.length > 0 && isPipe(injects[injects.length - 1]) ? injects[injects.length - 1] : createEmptyPipe()
This is helpful for creating BotAction's where you want to use the Pipe object safely, even if there is none ie when assembled in a Chain.
createEmptyPipe()
This function creates an empty Pipe object.
const createEmptyPipe = (): EmptyPipe => wrapValueInPipe()
The default Pipe object in a Pipe is this. When a BotAction, in a Pipe, doesn't return a value, the next BotAction is injected an empty Pipe object.
wrapValueInPipe()
This function creates a Pipe object with the value provided.
const wrapValueInPipe = <P = any>(value?: P): Pipe<P> => ({ brand: 'Pipe', value})
This function is helpful for writing Assembly Line BotAction's that deal with piping.
injectsHavePipe()
This function returns a boolean for whether or not the injects have a Pipe object.
const injectsHavePipe = (injects: any[]): boolean => { if (injects.length === 0) { return false }
return isPipe(injects[injects.length - 1])}
This is a way to determine if the BotAction is assembled in a Pipe or a Chain. If the injects
has a Pipe object, then it's assembled in a Chain.
getInjectsPipeValue()
This function returns the Pipe object's value from injects. If the injects
don't have a Pipe object, then the value undefined
is returned.
const getInjectsPipeValue = (injects: any[]): any => { if (injectsHavePipe(injects)) { return injects[injects.length - 1].value }
return undefined}
This function is helpful for writing BotAction's that want to use the Pipe value safely, even in a Chain, when you're not interested in the injects
.
pipeInjects()
This function adds an empty Pipe object to injects if the injects
don't have a Pipe object.
const pipeInjects = (injects: any[]): any[] => { if (injectsHavePipe(injects)) { return injects }
return [...injects, createEmptyPipe()]}
This can be used to simulate a BotAction being assembled in a Pipe.
createCasesSignal()
This function is used by pipeCase()()
and pipeCases()()
to create a CasesSignal
object to return. If no params are provided, the safe default is a CasesSignal
that has no matches, the overall condition did not pass, and the pipe value is undefined.
This function is found in a separate helpers/cases.ts
file
const createCasesSignal = <V = any>(matches: Dictionary<V> = {}, conditionPass: boolean = false, pipeValue?: PipeValue): CasesSignal<V> => ({ brand: 'Cases_Signal', conditionPass, matches, pipeValue})
casesSignalToPipeValue()
This function is helpful with the map() BotAction. It can be provided directly as a means to map the CasesSignal object to its pipe value. Therefore, when combined after a pipeCase()()
call, it will change the pipe object value to the CasesSignal.pipeValue
.
This function is found in a separate helpers/cases.ts
file
const casesSignalToPipeValue = (casesSignal: CasesSignal|any): PipeValue => isCasesSignal(casesSignal) ? casesSignal.pipeValue : undefined
Example:
await pipe(5)( pipeCase(5)( // some actions that will run // last BotAction returns a value ), // CasesSignal has pipeValue with last returned value map(casesSignalToPipeValue), log('Cases Signal pipe value'))(page)
isCasesSignal()
This function is a type guard for the CasesSignal
type. It's found in the types/cases.ts
file.
const isCasesSignal = <V = any>(value: any): value is CasesSignal<V> => typeof value === 'object' && value !== null && value.brand === 'Cases_Signal' && typeof value.conditionPass === 'boolean' && isDictionary<V>(value.matches)
isString()
This function was built for use with pipeCase or pipeCases, but can be used however necessary. It's found in the types/general.ts
file.
const isString = (data: unknown): boolean => typeof data === 'string'
Edit this page on GitHubisString() was released in
@botmation/core
v1.4.0