diff --git a/README.md b/README.md index ee400b023..13209b13e 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ Why choose Botmation? Baby Bot -It empowers Puppeteer code with a simple pattern that maximizes code readability, reusability and testability. +It empowers Puppeteer code with a simple pattern to maximize your code readability, reusability and testability. It has a compositional design with safe defaults for building bots with less code. -It encourages learning at your own pace, to inspire an appreciation for the possibilities of Functional programming. +It encourages a learn at your own pace approach to exploring the possibilities of Functional programming. -It has 100% library code test coverage. +It has 100% source code test coverage. # Introduction @@ -31,8 +31,6 @@ It has 100% library code test coverage. `BotAction`'s handle almost everything from simple tasks in crawling and scraping the web to logging in & automating your social media. They are composable. They make assembling Bots easy, declarative, and simple. -You can compose new `BotAction`'s from ones provided or build your own from scratch, then mix them up. - The possibilities are endless! # Getting Started @@ -69,10 +67,12 @@ After intalling through `npm`, import any `BotAction` from the main module: import { chain, goTo, screenshot } from 'botmation' ``` -As of v2.0.x, there are 12 groups of `BotAction` to compose with: +As of v2.1.x, there are 14 groups of `BotAction` to compose from: Leader Bot + - [abort](https://www.botmation.dev/api/abort) + - abort an assembly of `BotAction`'s - [assembly-line](https://www.botmation.dev/api/assembly-lines) - compose and run `BotAction`'s in lines - [console](https://www.botmation.dev/api/console) @@ -93,6 +93,10 @@ As of v2.0.x, there are 12 groups of `BotAction` to compose with: - read/write/delete from a page's Local Storage - [navigation](https://www.botmation.dev/api/navigation) - change the page's URL, wait for form submissions to change page URL, back, forward, refresh + - [pipe](https://www.botmation.dev/api/pipe) + - functions specific to Piping + - [scrapers](https://www.botmation.dev/api/scrapers) + - scrape HTML documents with an HTML parser and evaluate JavaScript inside a Page - [utilities](https://www.botmation.dev/api/utilties) - handle more complex logic patterns ie if statements and for loops @@ -105,6 +109,7 @@ In the `./src/examples` [directory](/src/examples) of this repo (excluded from t - [Generate Screenshots](/src/examples/screenshots.ts) - [Save a PDF](/src/examples/pdf.ts) - [Instagram Login](/src/examples/instagram.ts) + - [LinkedIn Like Feed Posts](/src/examples/linkedin.ts) # Dev Notes diff --git a/package.json b/package.json index 93fff00be..7d0044bae 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,9 @@ "examples/puppeteer-cluster": "node build/examples/puppeteer-cluster.js", "examples/screenshots": "node build/examples/screenshots.js", "examples/pdf": "node build/examples/screenshots.js", - "linkedin": "node build/examples/linkedin.js", + "examples/linkedin": "node build/examples/linkedin.js", "test": "jest --runInBand", - "localtestsite": "http-server src/tests/server", - "insta": "npm run examples/instagram" + "localtestsite": "http-server src/tests/server" }, "keywords": [ "social", diff --git a/src/botmation/actions/assembly-lines.ts b/src/botmation/actions/assembly-lines.ts index 8ff0cdb24..6416963f7 100644 --- a/src/botmation/actions/assembly-lines.ts +++ b/src/botmation/actions/assembly-lines.ts @@ -113,7 +113,7 @@ export const pipe = * Before each assembled BotAction is ran, the pipe is switched back to whatever is set `toPipe` * `toPipe` is optional and can be provided by an injected pipe object value (if nothing provided, default is undefined) * - * AbortLineSignal default abort(1) is ignored until a MatchesSignal is returned by an assembled BotAction, marking that at least one Case has ran + * AbortLineSignal default abort(1) is ignored until a CasesSignal is returned by an assembled BotAction, marking that at least one Case has ran * to break that, you can abort(2+) * This is to support the classic switch/case/break flow where its switchPipe/pipeCase/abort * Therefore, if a pipeCase() does run, its returning MatcheSignal will be recognized by switchPipe and then lower the required abort count by 1 @@ -144,7 +144,7 @@ export const switchPipe = let resolvedActionResult = await action(page, ...injects) // resolvedActionResult can be of 3 things - // 1. MatchesSignal 2. AbortLineSignal 3. PipeValue + // 1. CasesSignal 2. AbortLineSignal 3. PipeValue // switchPipe will return (if not aborted) an array of all the resolved results of each BotAction assembled in the switchPipe()() 2nd call if (isCasesSignal(resolvedActionResult) && resolvedActionResult.conditionPass) { hasAtLeastOneCaseMatch = true diff --git a/src/botmation/actions/cookies.ts b/src/botmation/actions/cookies.ts index 2f6f08eeb..874c66e1f 100644 --- a/src/botmation/actions/cookies.ts +++ b/src/botmation/actions/cookies.ts @@ -4,7 +4,7 @@ import { BotFilesAction } from '../interfaces/bot-actions' import { enrichBotFileOptionsWithDefaults } from '../helpers/files' import { BotFileOptions } from '../interfaces' import { getFileUrl } from '../helpers/files' -import { unpipeInjects } from 'botmation/helpers/pipe' +import { unpipeInjects } from '../helpers/pipe' /** * @description Parse page's cookies and save them as JSON in a local file diff --git a/src/botmation/actions/navigation.ts b/src/botmation/actions/navigation.ts index f7aef5748..748151f90 100644 --- a/src/botmation/actions/navigation.ts +++ b/src/botmation/actions/navigation.ts @@ -69,9 +69,10 @@ export const wait = (milliseconds: number): BotAction => async() => { /** * * @param htmlSelector + * @param waitTimeForScroll milliseconds to wait for scrolling */ -export const scrollTo = (htmlSelector: string): BotAction => +export const scrollTo = (htmlSelector: string, waitTimeForScroll: number = 2500): BotAction => chain( - evaluate(scrollToElement, htmlSelector), - wait(2500) // wait for scroll to complete + evaluate(scrollToElement, htmlSelector), // init's scroll code, but does not wait for it to complete + wait(waitTimeForScroll) // wait for scroll to complete ) \ No newline at end of file diff --git a/src/botmation/actions/pipe.ts b/src/botmation/actions/pipe.ts index a5cc2ce47..18139a82b 100644 --- a/src/botmation/actions/pipe.ts +++ b/src/botmation/actions/pipe.ts @@ -37,10 +37,10 @@ export const emptyPipe: BotAction = async () => undefined * Similar to givenThat except instead of evaluating a BotAction for TRUE, its testing an array of values against the pipe object value for truthy. * A value can be a function. In this case, the function is treated as a callback, expected to return a truthy expression, is passed in the pipe object's value * @param valuesToTest - * @return AbortLineSignal|MatchesSignal - * If no matches are found or matches are found, a MatchesSignal is returned + * @return AbortLineSignal|CasesSignal + * If no matches are found or matches are found, a CasesSignal is returned * It is determined if signal has matches by using hasAtLeastOneMatch() helper - * If assembled BotAction aborts(1), it breaks line & returns a MatchesSignal with the matches + * If assembled BotAction aborts(1), it breaks line & returns a CasesSignal with the matches * If assembled BotAction aborts(2), it breaks line & returns AbortLineSignal.pipeValue * If assembled BotAction aborts(3+), it returns AbortLineSignal(2-) */ @@ -86,7 +86,7 @@ export const pipeCase = } /** - * runs assembled actions ONLY if ALL cases pass otherwise it breaks the case checking and immediately returns an empty MatchesSignal + * runs assembled actions ONLY if ALL cases pass otherwise it breaks the case checking and immediately returns an empty CasesSignal * it's like if (case && case && case ...) * Same AbortLineSignal behavior as pipeCase()() * @param valuesToTest diff --git a/src/botmation/actions/scrapers.ts b/src/botmation/actions/scrapers.ts index 14608d051..1298c9002 100644 --- a/src/botmation/actions/scrapers.ts +++ b/src/botmation/actions/scrapers.ts @@ -81,7 +81,7 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar /** * Evaluate functions inside the `page` context - * @param functionToEvaluate + * @param functionToEvaluate * @param functionParams */ export const evaluate = (functionToEvaluate: EvaluateFn, ...functionParams: any[]): BotAction => diff --git a/src/botmation/helpers/README.md b/src/botmation/helpers/README.md deleted file mode 100644 index 2e7df9af6..000000000 --- a/src/botmation/helpers/README.md +++ /dev/null @@ -1,73 +0,0 @@ -

Botmation: Helpers

- -Helpers are single purpose functions to help with various tasks. - -# Helpers Reference - -As of v1.0.0, there are 8 types of helpers. - -1. Actions -2. Assets -3. BotOptions -4. Console -5. Files -6. Navigation -7. Utilities - -## Actions - -These helper methods help with dealing with bot actions. - -- async applyBotActionOrActions(page: Page, options: BotOptions, actions: BotAction[] | BotAction, ...injects: any[]) -It's an effective way to run bot actions when you're not sure if they are an array of bot actions or not an array, but just one single bot action. - -## Assets - -These helper methods deal with the output bot actions, and help out with managing assets. - -- createFolderURL(...folderNames: string[]) -It focuses on prepending URL's for a relative structure from the root directory. It will not leave a trailing backslash. So you can include filename at the end for a full relative URL. - -## BotOptions - -This is our loosely coupled bot options helper functions for dealing with the `options` of class `Botmation`, and if you're skipping that, the Bot Actions Chain Factory. The strategy behind the `options` is that they are optional, given that have safe defaults for all the bot actions that rely on data from `options`. So in creating an object instance, or using the Functional approach, you don't need to provide options, but if you do, you only need to provide what you want to change from the safe default values. - -- getDefaultBotOptions(options: Partial) -This is used when providing `options` for bot actions with safe defaults. - -## Console - -The higher order Console Bot Actions use these functions, so if you want to use the colorful logging messages outside a chain of actions, you can use these methods directly: - -- logMessage(message: string) -Logs a clear green message to the Console - -- logWarning(warning: string) -Logs a orange warning message to the Console - -- logError(error: string) -Logs a red error message to the Console - -## Files - -These helper functions facilitate in the management of files. Asset management may build on these functions. They are used in testing, to help clean up tests. - -- async fileExist(filePath: string) -Returns a promise that resolves TRUE or FALSE - -- async deleteFile(filePath: string) -Resolves or rejects on success or failure - -## Navigation - -These helper functions deal with the defaults in using Puppeteer's navigational methods. - -- getDefaultGoToPageOptions(overloadDefaultOptions: DirectNavigationOptions = {}) -The `goTo()` bot action uses this to provide safe defaults in navigating pages. Can be overloaded with changes only. - -## Utilities - -Helpful utility functions for dev's. - -- async sleep(milliseconds: number) -Pauses program execution until time provided is awaited. \ No newline at end of file diff --git a/src/botmation/index.ts b/src/botmation/index.ts index aa081e940..88b5a9c69 100644 --- a/src/botmation/index.ts +++ b/src/botmation/index.ts @@ -30,12 +30,14 @@ export * from './actions/utilities' // // Helpers export * from './helpers/abort' +export * from './helpers/cases' export * from './helpers/console' export * from './helpers/files' export * from './helpers/indexed-db' export * from './helpers/local-storage' export * from './helpers/navigation' export * from './helpers/pipe' +export * from './helpers/scrapers' // // Class diff --git a/src/botmation/sites/linkedin/actions/feed.ts b/src/botmation/sites/linkedin/actions/feed.ts index 62a555454..1f46931dc 100644 --- a/src/botmation/sites/linkedin/actions/feed.ts +++ b/src/botmation/sites/linkedin/actions/feed.ts @@ -21,7 +21,7 @@ import { postIsPromotion, postIsJobPostings, postIsUserInteraction, - postIsUserArticle, + postIsUserPost, postIsAuthoredByAPerson } from '../helpers/feed' @@ -39,13 +39,13 @@ export const scrapeFeedPost = (postDataId: string): BotAction => $('.application-outlet .feed-outlet [role="main"] [data-id="'+ postDataId + '"]') /** - * If the post hasn't been populated (waits loading), then scroll to it to trigger lazy loading then scrape it to return the hydrated version of it + * If the post hasn't been populated (waits loading), then scroll to it to cause lazy loading then scrape it to return the hydrated version of it * @param post */ -export const ifPostNotLoadedTriggerLoadingThenScrape = (post: CheerioStatic): BotAction => +export const ifPostNotLoadedCauseLoadingThenScrape = (post: CheerioStatic): BotAction => // linkedin lazily loads off screen posts, so check beforehand, and if not loaded, scroll to it, then scrape it again pipe(post)( - errors('LinkedIn triggerLazyLoadingThenScrapePost()')( + errors('LinkedIn causeLazyLoadingThenScrapePost()')( pipeCase(postHasntFullyLoadedYet)( scrollTo('.application-outlet .feed-outlet [role="main"] [data-id="'+ post('[data-id]').attr('data-id') + '"]'), scrapeFeedPost(post('[data-id]').attr('data-id') + '') @@ -62,7 +62,7 @@ export const ifPostNotLoadedTriggerLoadingThenScrape = (post: CheerioStatic): Bo * It would be nice to rely on ie Post.id as param to then find that "Like" button in page to click. In order to, de-couple this function * @param post */ -export const likeArticle = (post: CheerioStatic): BotAction => +export const likeUserPost = (post: CheerioStatic): BotAction => // Puppeteer.page.click() returned promise will reject if the selector isn't found // so if button is Pressed, it will reject since the aria-label value will not match errors('LinkedIn like() - Could not Like Post: Either already Liked or button not found')( @@ -76,12 +76,12 @@ export const likeArticle = (post: CheerioStatic): BotAction => * @description Demonstration of what's currently possible, this function goes beyond the scope of its name, but to give an idea on how something more complex could be handled * @param peopleNames */ -export const likeArticlesFrom = (...peopleNames: string[]): BotAction => +export const likeUserPostsFrom = (...peopleNames: string[]): BotAction => pipe()( scrapeFeedPosts, forAll()( post => pipe(post)( - ifPostNotLoadedTriggerLoadingThenScrape(post), + ifPostNotLoadedCauseLoadingThenScrape(post), switchPipe()( pipeCase(postIsPromotion)( map((promotionPost: CheerioStatic) => promotionPost('[data-id]').attr('data-id')), @@ -98,11 +98,11 @@ export const likeArticlesFrom = (...peopleNames: string[]): BotAction => log(`Followed User's Interaction (ie like, comment, etc)`) ), abort(), - pipeCase(postIsUserArticle)( + pipeCase(postIsUserPost)( pipeCase(postIsAuthoredByAPerson(...peopleNames))( // scroll to post necessary to click off page link? ie use scrollTo() "navigation" BotAction // the feature, auto-scroll, was added to `page.click()` in later Puppeteer version, irc - likeArticle(post), + likeUserPost(post), log('User Article "liked"') ), emptyPipe, diff --git a/src/botmation/sites/linkedin/helpers/feed.ts b/src/botmation/sites/linkedin/helpers/feed.ts index dc2762e2c..4007f8a08 100644 --- a/src/botmation/sites/linkedin/helpers/feed.ts +++ b/src/botmation/sites/linkedin/helpers/feed.ts @@ -5,7 +5,7 @@ import { feedPostAuthorSelector } from '../selectors' * User articles are posts created by users you're either connected too directly (1st) or are following * @param peopleNames */ -export const postIsUserArticle: ConditionalCallback = (post: CheerioStatic) => { +export const postIsUserPost: ConditionalCallback = (post: CheerioStatic) => { const sharedActorFeedSupplementaryInfo = post('.feed-shared-actor__supplementary-actor-info').text().trim().toLowerCase() return sharedActorFeedSupplementaryInfo.includes('1st') || sharedActorFeedSupplementaryInfo.includes('following') diff --git a/src/examples/linkedin.ts b/src/examples/linkedin.ts index c7c49ea6b..02fa8a2eb 100644 --- a/src/examples/linkedin.ts +++ b/src/examples/linkedin.ts @@ -2,15 +2,15 @@ import puppeteer from 'puppeteer' // General BotAction's import { log } from 'botmation/actions/console' -// import { screenshot } from 'botmation/actions/files' +import { screenshot } from 'botmation/actions/files' import { loadCookies } from 'botmation/actions/cookies' // More advanced BotAction's import { pipe, saveCookies, wait, errors, givenThat } from 'botmation' import { login, isGuest, isLoggedIn } from 'botmation/sites/linkedin/actions/auth' import { toggleMessagingOverlay } from 'botmation/sites/linkedin/actions/messaging' -import { likeArticlesFrom } from 'botmation/sites/linkedin/actions/feed' -import { goHome } from 'botmation/sites/linkedin/actions/navigation' +import { likeUserPostsFrom } from 'botmation/sites/linkedin/actions/feed' +import { goHome, goToFeed } from 'botmation/sites/linkedin/actions/navigation' // Helper for creating filenames that sort naturally const generateTimeStamp = (): string => { @@ -53,10 +53,12 @@ const generateTimeStamp = (): string => { wait(5000), // tons of stuff loads... no rush givenThat(isLoggedIn)( + goToFeed, + toggleMessagingOverlay, // by default, Messaging Overlay loads in open state - // screenshot(generateTimeStamp()), // filename ie "2020-8-21-13-20.png" + screenshot(generateTimeStamp()), // filename ie "2020-8-21-13-20.png" - likeArticlesFrom('Peter Parker', 'Harry Potter') + likeUserPostsFrom('Peter Parker', 'Harry Potter') ) ) diff --git a/src/tests/botmation/actions/navigation.spec.ts b/src/tests/botmation/actions/navigation.spec.ts index e907a5f12..b328e2611 100644 --- a/src/tests/botmation/actions/navigation.spec.ts +++ b/src/tests/botmation/actions/navigation.spec.ts @@ -121,6 +121,11 @@ describe('[Botmation] actions/navigation', () => { expect(mockScrollToElement).toHaveBeenNthCalledWith(1, 'some-element-far-away') expect(mockSleep).toHaveBeenNthCalledWith(2, 2500) + + await scrollTo('some-element-far-far-away', 5000)(mockPage) + + expect(mockScrollToElement).toHaveBeenNthCalledWith(2, 'some-element-far-far-away') + expect(mockSleep).toHaveBeenNthCalledWith(3, 5000) }) // clean up diff --git a/tsconfig.dist.json b/tsconfig.dist.json index 42a475266..a4269e8c9 100644 --- a/tsconfig.dist.json +++ b/tsconfig.dist.json @@ -11,7 +11,8 @@ "baseUrl": "./src", "sourceMap": true, "esModuleInterop": true, - "declaration": true + "declaration": true, + "downlevelIteration": true }, "exclude": [ "node_modules", diff --git a/tsconfig.json b/tsconfig.json index 69fa56677..fe08f6761 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "baseUrl": "./src", "sourceMap": true, "esModuleInterop": true, - "declaration": true + "declaration": true, + "downlevelIteration": true }, "exclude": [ "assets", diff --git a/webpack.config.js b/webpack.config.js index 82549d01f..93c699a4c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,46 +8,53 @@ const path = require('path'); const CopyPlugin = require('copy-webpack-plugin'); var nodeExternals = require('webpack-node-externals'); +const localSrcBotmationDir = 'src/botmation/'; +const sitesInstagramDir = 'sites/instagram/'; + module.exports = { entry: { // // Class & Assembly-Lines - 'index': path.resolve(__dirname, 'src/botmation/index.ts'), // barrel + 'index': path.resolve(__dirname, localSrcBotmationDir, 'index.ts'), // barrel // // Interfaces & Types - 'interfaces': path.resolve(__dirname, 'src/botmation/interfaces/index.ts'), // barrel - 'types': path.resolve(__dirname, 'src/botmation/types/index.ts'), // barrel + 'interfaces': path.resolve(__dirname, localSrcBotmationDir, 'interfaces/index.ts'), // barrel + 'types': path.resolve(__dirname, localSrcBotmationDir, 'types/index.ts'), // barrel // // Actions - 'actions/assembly-lines': path.resolve(__dirname, 'src/botmation/actions/assembly-lines.ts'), - 'actions/console': path.resolve(__dirname, 'src/botmation/actions/console.ts'), - 'actions/cookies': path.resolve(__dirname, 'src/botmation/actions/cookies.ts'), - 'actions/errors': path.resolve(__dirname, 'src/botmation/actions/errors.ts'), - 'actions/files': path.resolve(__dirname, 'src/botmation/actions/files.ts'), - 'actions/indexed-db': path.resolve(__dirname, 'src/botmation/actions/indexed-db.ts'), - 'actions/input': path.resolve(__dirname, 'src/botmation/actions/input.ts'), - 'actions/local-storage': path.resolve(__dirname, 'src/botmation/actions/local-storage.ts'), - 'actions/navigation': path.resolve(__dirname, 'src/botmation/actions/navigation.ts'), - 'actions/pipe': path.resolve(__dirname, 'src/botmation/actions/pipe.ts'), - 'actions/utilities': path.resolve(__dirname, 'src/botmation/actions/utilities.ts'), + 'actions/abort': path.resolve(__dirname, localSrcBotmationDir, 'actions/abort.ts'), + 'actions/assembly-lines': path.resolve(__dirname, localSrcBotmationDir, 'actions/assembly-lines.ts'), + 'actions/console': path.resolve(__dirname, localSrcBotmationDir, 'actions/console.ts'), + 'actions/cookies': path.resolve(__dirname, localSrcBotmationDir, 'actions/cookies.ts'), + 'actions/errors': path.resolve(__dirname, localSrcBotmationDir, 'actions/errors.ts'), + 'actions/files': path.resolve(__dirname, localSrcBotmationDir, 'actions/files.ts'), + 'actions/indexed-db': path.resolve(__dirname, localSrcBotmationDir, 'actions/indexed-db.ts'), + 'actions/input': path.resolve(__dirname, localSrcBotmationDir, 'actions/input.ts'), + 'actions/local-storage': path.resolve(__dirname, localSrcBotmationDir, 'actions/local-storage.ts'), + 'actions/navigation': path.resolve(__dirname, localSrcBotmationDir, 'actions/navigation.ts'), + 'actions/pipe': path.resolve(__dirname, localSrcBotmationDir, 'actions/pipe.ts'), + 'actions/scrapers': path.resolve(__dirname, localSrcBotmationDir, 'actions/scrapers.ts'), + 'actions/utilities': path.resolve(__dirname, localSrcBotmationDir, 'actions/utilities.ts'), // // Helpers - 'helpers/console': path.resolve(__dirname, 'src/botmation/helpers/console.ts'), - 'helpers/files': path.resolve(__dirname, 'src/botmation/helpers/files.ts'), - 'helpers/indexed-db': path.resolve(__dirname, 'src/botmation/helpers/indexed-db.ts'), - 'helpers/local-storage': path.resolve(__dirname, 'src/botmation/helpers/local-storage.ts'), - 'helpers/navigation': path.resolve(__dirname, 'src/botmation/helpers/navigation.ts'), - 'helpers/pipe': path.resolve(__dirname, 'src/botmation/helpers/pipe.ts'), - 'helpers/utilities': path.resolve(__dirname, 'src/botmation/helpers/utilities.ts'), + 'helpers/abort': path.resolve(__dirname, localSrcBotmationDir, 'helpers/abort.ts'), + 'helpers/cases': path.resolve(__dirname, localSrcBotmationDir, 'helpers/cases.ts'), + 'helpers/console': path.resolve(__dirname, localSrcBotmationDir, 'helpers/console.ts'), + 'helpers/files': path.resolve(__dirname, localSrcBotmationDir, 'helpers/files.ts'), + 'helpers/indexed-db': path.resolve(__dirname, localSrcBotmationDir, 'helpers/indexed-db.ts'), + 'helpers/local-storage': path.resolve(__dirname, localSrcBotmationDir, 'helpers/local-storage.ts'), + 'helpers/navigation': path.resolve(__dirname, localSrcBotmationDir, 'helpers/navigation.ts'), + 'helpers/pipe': path.resolve(__dirname, localSrcBotmationDir, 'helpers/pipe.ts'), + 'helpers/scrapers': path.resolve(__dirname, localSrcBotmationDir, 'helpers/scrapers.ts'), // // Site Specific // Instagram - 'sites/instagram/selectors': path.resolve(__dirname, 'src/botmation/sites/instagram/selectors.ts'), - 'sites/instagram/actions/auth': path.resolve(__dirname, 'src/botmation/sites/instagram/actions/auth.ts'), - 'sites/instagram/actions/modals': path.resolve(__dirname, 'src/botmation/sites/instagram/actions/modals.ts'), - 'sites/instagram/constants/modals': path.resolve(__dirname, 'src/botmation/sites/instagram/constants/modals.ts'), - 'sites/instagram/constants/urls': path.resolve(__dirname, 'src/botmation/sites/instagram/constants/urls.ts'), - 'sites/instagram/helpers/urls': path.resolve(__dirname, 'src/botmation/sites/instagram/helpers/urls.ts'), + 'sites/instagram/selectors': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'selectors.ts'), + 'sites/instagram/actions/auth': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'actions/auth.ts'), + 'sites/instagram/actions/modals': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'actions/modals.ts'), + 'sites/instagram/constants/modals': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'constants/modals.ts'), + 'sites/instagram/constants/urls': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'constants/urls.ts'), + 'sites/instagram/helpers/urls': path.resolve(__dirname, localSrcBotmationDir, sitesInstagramDir, 'helpers/urls.ts'), }, module: { rules: [