├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── lerna.json ├── package.json ├── packages ├── puppeteer-email-cli │ ├── convert.js │ ├── index.js │ ├── index.test.js │ ├── lib │ │ └── cli.js │ ├── package.json │ ├── readme.md │ └── yarn.lock ├── puppeteer-email-provider-outlook │ ├── index.js │ ├── lib │ │ ├── get-email.js │ │ ├── get-emails.js │ │ ├── scratch │ │ ├── send-email.js │ │ ├── signin.js │ │ ├── signout.js │ │ └── signup.js │ ├── package.json │ ├── readme.md │ └── yarn.lock ├── puppeteer-email-provider-yahoo │ ├── index.js │ ├── lib │ │ ├── get-email.js │ │ ├── get-emails.js │ │ ├── send-email.js │ │ ├── signin.js │ │ ├── signout.js │ │ └── signup.js │ ├── package.json │ ├── readme.md │ └── yarn.lock ├── puppeteer-email-provider │ ├── index.js │ ├── package.json │ ├── readme.md │ └── yarn.lock ├── puppeteer-email-session │ ├── index.js │ ├── package.json │ ├── readme.md │ └── yarn.lock └── puppeteer-email │ ├── index.js │ ├── lib │ ├── providers.js │ └── providers.test.js │ ├── package.json │ ├── readme.md │ └── yarn.lock ├── readme.md └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # builds 7 | build 8 | dist 9 | 10 | # misc 11 | .DS_Store 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | .cache 18 | 19 | lerna-debug.log* 20 | lerna-error.log* 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | node_js: 4 | - 9 5 | - 8 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.11.0", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "independent", 7 | "useWorkspaces": true, 8 | "npmClient": "yarn" 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "description": "Email automation driven by headless chrome.", 4 | "repository": "transitive-bullshit/puppeteer-email", 5 | "author": "Travis Fischer ", 6 | "license": "MIT", 7 | "engines": { 8 | "node": ">=8" 9 | }, 10 | "scripts": { 11 | "bootstrap": "lerna bootstrap", 12 | "test": "lerna run test", 13 | "prepare": "lerna run prepare" 14 | }, 15 | "workspaces": [ 16 | "packages/*" 17 | ], 18 | "devDependencies": { 19 | "lerna": "^2.11.0" 20 | }, 21 | "dependencies": {} 22 | } 23 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/convert.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | const input = fs.readFileSync('./test.json', 'utf8') 6 | const json = JSON.parse(input) 7 | 8 | const output = json 9 | .map((u) => `${u.firstName}\t${u.lastName}\t${u.birthday.month}/${u.birthday.day}/${u.birthday.year}\t${u.email}\t${u.password}`) 10 | .map((u) => u.toString()).join('\n') 11 | 12 | console.log(output) 13 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const compatRequire = require('node-compat-require') 5 | compatRequire('./lib/cli', { node: '>= 8' }) 6 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('ava') 4 | const execa = require('execa') 5 | 6 | test('--version', async (t) => { 7 | const { stdout } = await execa('./index.js', [ '--version' ]) 8 | t.true(stdout.length > 0) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/lib/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | require('dotenv').config() 5 | 6 | const program = require('commander') 7 | 8 | const PuppeteerEmail = require('puppeteer-email') 9 | const CaptchaSolver = require('captcha-solver') 10 | const SMSNumberVerifier = require('sms-number-verifier') 11 | 12 | const { version } = require('../package') 13 | 14 | module.exports = (argv) => { 15 | program 16 | .name('puppeteer-email') 17 | .version(version) 18 | .option('-u, --username ', 'email account username') 19 | .option('-p, --password ', 'email account password') 20 | .option('-e, --email ', 'email account address (overrides username and provider)') 21 | .option('-P, --provider ', 'email provider', /^(outlook|yahoo)$/i, 'outlook') 22 | .option('-H, --no-headless', '(puppeteer) disable headless mode') 23 | .option('-s, --slow-mo ', '(puppeteer) slows down operations by the given ms', parseInt, 0) 24 | .option('-c, --captchaProvider ', 'API key for captcha provider', /^(anti-captcha)$/i, 'anti-captcha') 25 | .option('-k, --captchaKey ', 'Captcha solver provider') 26 | .option('-S, --smsProvider ', 'SMS number verifier provider', 'getsmscode') 27 | .option('-C, --smsCountryCode ', 'SMS number verifier country code', 'hk') 28 | 29 | program 30 | .command('signup') 31 | .option('-n, --first-name ', 'user first name') 32 | .option('-l, --last-name ', 'user last name') 33 | .option('-b, --birthday ', 'user birthday (month/day/year); eg 9/20/1986') 34 | .option('-r, --repeat ', 'repeat signup multiple times in bulk', (s) => parseInt(s), 1) 35 | .action(async (opts) => { 36 | try { 37 | const client = new PuppeteerEmail(program.email || program.provider) 38 | const captchaSolver = program.captchaKey 39 | ? new CaptchaSolver(program.captchaProvider, { key: program.captchaKey }) 40 | : null 41 | const smsNumberVerifier = program.smsProvider 42 | ? new SMSNumberVerifier(program.smsProvider, { cocode: program.smsCountryCode }) 43 | : null 44 | 45 | const users = [] 46 | const errors = [] 47 | let session 48 | let user 49 | 50 | const cleanup = async () => { 51 | if (session) { 52 | await session.close() 53 | session = null 54 | } 55 | } 56 | 57 | for (let i = 0; i < opts.repeat; ++i) { 58 | try { 59 | user = { 60 | username: program.username, 61 | password: program.password, 62 | firstName: opts.firstName, 63 | lastName: opts.lastName 64 | } 65 | 66 | if (opts.birthday) { 67 | const [ month, day, year ] = opts.birthday 68 | user.birthday = { month, day, year } 69 | } 70 | 71 | session = await client.signup(user, { 72 | captchaSolver, 73 | smsNumberVerifier, 74 | puppeteer: { 75 | headless: !!program.headless, 76 | slowMo: program.slowMo 77 | } 78 | }) 79 | 80 | user.email = session.email 81 | console.log('SUCCESS', JSON.stringify(user, null, 2)) 82 | users.push(user) 83 | 84 | await cleanup() 85 | } catch (err) { 86 | try { await cleanup() } catch (err) { } 87 | console.warn(`signup error attempt ${i}`, err) 88 | errors.push({ ...user, err }) 89 | } 90 | } 91 | 92 | if (smsNumberVerifier && smsNumberVerifier.provider.close) { 93 | await smsNumberVerifier.provider.close() 94 | } 95 | 96 | if (opts.repeat > 1) { 97 | console.log(JSON.stringify(users, null, 2)) 98 | console.log(JSON.stringify(errors, null, 2)) 99 | console.log(`${users.length} users created; ${errors.length} errors`) 100 | } 101 | } catch (err) { 102 | console.error(err) 103 | process.exit(1) 104 | } 105 | }) 106 | 107 | program 108 | .command('signin') 109 | .action(async () => { 110 | try { 111 | const client = new PuppeteerEmail(program.email || program.provider) 112 | 113 | const user = { 114 | username: program.username, 115 | password: program.password 116 | } 117 | 118 | if (!user.username || !user.username.length) { 119 | throw new Error('missing required "username"') 120 | } 121 | 122 | if (!user.password || !user.password.length) { 123 | throw new Error('missing required "password"') 124 | } 125 | 126 | const session = await client.signin(user, { 127 | puppeteer: { 128 | headless: !!program.headless, 129 | slowMo: program.slowMo 130 | } 131 | }) 132 | 133 | await session.close() 134 | 135 | console.log(session.email) 136 | } catch (err) { 137 | console.error(err) 138 | process.exit(1) 139 | } 140 | }) 141 | 142 | program 143 | .command('get-emails') 144 | .option('-q, --query ', 'query string to filter emails') 145 | .action(async (opts) => { 146 | try { 147 | const client = new PuppeteerEmail(program.email || program.provider) 148 | 149 | const user = { 150 | username: program.username, 151 | password: program.password 152 | } 153 | 154 | if (!user.username || !user.username.length) { 155 | throw new Error('missing required "username"') 156 | } 157 | 158 | if (!user.password || !user.password.length) { 159 | throw new Error('missing required "password"') 160 | } 161 | 162 | if (!opts.query || !opts.query.length) { 163 | throw new Error('missing required "query"') 164 | } 165 | 166 | const session = await client.signin(user, { 167 | puppeteer: { 168 | headless: !!program.headless, 169 | slowMo: program.slowMo 170 | } 171 | }) 172 | 173 | const emails = await session.getEmails({ 174 | query: opts.query 175 | }) 176 | 177 | await session.close() 178 | 179 | console.log(JSON.stringify(emails, null, 2)) 180 | } catch (err) { 181 | console.error(err) 182 | process.exit(1) 183 | } 184 | }) 185 | 186 | program.parse(argv) 187 | } 188 | 189 | module.exports(process.argv) 190 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email-cli", 3 | "version": "0.1.0", 4 | "description": "CLI for email automation driven by headless chrome.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email-cli", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "reveal": true, 10 | "bin": { 11 | "puppeteer-email": "index.js" 12 | }, 13 | "scripts": { 14 | "docs": "update-markdown-usage", 15 | "test": "ava -v && standard" 16 | }, 17 | "engines": { 18 | "node": ">=4" 19 | }, 20 | "keywords": [ 21 | "puppeteer", 22 | "email", 23 | "cli" 24 | ], 25 | "devDependencies": { 26 | "ava": "^0.25.0", 27 | "standard": "^11.0.0", 28 | "update-markdown-usage": "^1.0.1" 29 | }, 30 | "dependencies": { 31 | "captcha-solver": "^0.0.6", 32 | "commander": "^2.16.0", 33 | "dotenv": "^6.0.0", 34 | "node-compat-require": "^1.0.5", 35 | "puppeteer-email": "^0.1.0", 36 | "sms-number-verifier": "^0.1.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/puppeteer-email-cli/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email-cli 2 | 3 | > CLI for email automation driven by headless chrome. 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email-cli.svg)](https://www.npmjs.com/package/puppeteer-email-cli) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email-cli 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```bash 16 | Usage: puppeteer-email [options] [command] 17 | 18 | Options: 19 | 20 | -V, --version output the version number 21 | -u, --username email account username 22 | -p, --password email account password 23 | -e, --email email account address (overrides username and provider) 24 | -P, --provider email provider (default: outlook) 25 | -H, --no-headless (puppeteer) disable headless mode 26 | -s, --slow-mo (puppeteer) slows down operations by the given ms (default: 0) 27 | -h, --help output usage information 28 | 29 | Commands: 30 | 31 | signup [options] 32 | signin 33 | get-emails [options] 34 | ``` 35 | 36 | ## Related 37 | 38 | - [puppeteer-email](https://github.com/transitive-bullshit/puppeteer-email) - Email automation driven by headless chrome. 39 | 40 | ## License 41 | 42 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 43 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ow = require('ow') 4 | 5 | const PuppeteerEmailProvider = require('puppeteer-email-provider') 6 | const PuppeteerEmailSession = require('puppeteer-email-session') 7 | 8 | const signup = require('./lib/signup') 9 | const signin = require('./lib/signin') 10 | const signout = require('./lib/signout') 11 | const sendEmail = require('./lib/send-email') 12 | const getEmails = require('./lib/get-emails') 13 | 14 | /** 15 | * Puppeteer email provider for [Outlook](https://outlook.live.com). 16 | * 17 | * @extends PuppeteerEmailProvider 18 | */ 19 | class PuppeteerEmailProviderOutlook extends PuppeteerEmailProvider { 20 | /** 21 | * Email provider to automate. 22 | * 23 | * @member {PuppeteerEmailProvider} 24 | */ 25 | get name () { 26 | return 'outlook' 27 | } 28 | 29 | /** 30 | * Creates a new email account. 31 | * 32 | * Returns an email session with the authenticated puppeteer browser. 33 | * 34 | * @param {object} user - User info for the account to create 35 | * @param {string} user.username - Username 36 | * @param {string} user.password - Password 37 | * @param {string} user.firstName - User's given name 38 | * @param {string} user.lastName - User's family name 39 | * @param {object} user.birthday - User's birthday 40 | * @param {string} user.birthday.month - User's birthday month 41 | * @param {string} user.birthday.day - User's birthday day 42 | * @param {string} user.birthday.year - User's birthday year 43 | * 44 | * @param {object} opts - Options 45 | * @param {Object} opts.browser - Puppeteer browser instance to use 46 | * 47 | * @return {Promise} 48 | */ 49 | async signup (user, opts) { 50 | ow(user, ow.object.plain.nonEmpty.label('user')) 51 | ow(user.username, ow.string.nonEmpty.label('user.username')) 52 | ow(user.password, ow.string.nonEmpty.label('user.password')) 53 | ow(opts, ow.object.nonEmpty.label('opts')) 54 | ow(opts.browser, ow.object.nonEmpty.label('browser')) 55 | 56 | await signup(user, opts) 57 | 58 | return new PuppeteerEmailSession({ 59 | user: { 60 | username: user.username, 61 | email: `${user.username}@outlook.com` 62 | }, 63 | browser: opts.browser, 64 | provider: this 65 | }) 66 | } 67 | 68 | /** 69 | * Signs into an existing email account. 70 | * 71 | * You must specify either `user.username` or `user.email`. 72 | * 73 | * Returns an email session with the authenticated puppeteer browser. 74 | * 75 | * @param {object} user - User info for the account to sign into 76 | * @param {string} [user.username] - Username (implies email) 77 | * @param {string} [user.email] - Email (implies username) 78 | * @param {string} user.password - Password 79 | * 80 | * @param {object} opts - Options 81 | * @param {Object} opts.browser - Puppeteer browser instance to use 82 | * 83 | * @return {Promise} 84 | */ 85 | async signin (user, opts) { 86 | ow(user, ow.object.plain.nonEmpty.label('user')) 87 | ow(user.password, ow.string.nonEmpty.label('user.password')) 88 | 89 | if (user.username) { 90 | ow(user.username, ow.string.nonEmpty) 91 | } else if (user.email) { 92 | ow(user.email, ow.string.nonEmpty) 93 | } else { 94 | throw new Error('missing required parameter "username" or "email"') 95 | } 96 | 97 | ow(opts, ow.object.nonEmpty) 98 | ow(opts.browser, ow.object.nonEmpty) 99 | 100 | user.email = user.email || `${user.username}@outlook.com` 101 | 102 | await signin(user, opts) 103 | 104 | return new PuppeteerEmailSession({ 105 | user: { 106 | username: user.username, 107 | email: user.email 108 | }, 109 | browser: opts.browser, 110 | provider: this 111 | }) 112 | } 113 | 114 | /** 115 | * Signs out of an authenticated session. 116 | * 117 | * @param {PuppeteerEmailSession} session 118 | * 119 | * @return {Promise} 120 | */ 121 | async signout (session) { 122 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 123 | 124 | return signout({ 125 | browser: session.browser 126 | }) 127 | } 128 | 129 | /** 130 | * Sends an email from an authenticated session. 131 | * 132 | * @param {PuppeteerEmailSession} session 133 | * @param {object} email - TODO 134 | * @param {object} [opts] - Options 135 | * 136 | * @return {Promise} 137 | */ 138 | async sendEmail (session, email, opts = { }) { 139 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 140 | ow(email, ow.object) 141 | ow(opts, ow.object.plain) 142 | 143 | return sendEmail(email, { 144 | browser: session.browser, 145 | ...opts 146 | }) 147 | } 148 | 149 | /** 150 | * Fetches emails from the inbox of an authenticated session. 151 | * 152 | * @param {PuppeteerEmailSession} session 153 | * @param {object} [opts] - Options 154 | * @param {object} [opts.query] - Search query to narrow down results 155 | * 156 | * @return {Promise>} 157 | */ 158 | async getEmails (session, opts = { }) { 159 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 160 | ow(opts, ow.object.plain) 161 | 162 | return getEmails({ 163 | browser: session.browser, 164 | ...opts 165 | }) 166 | } 167 | } 168 | 169 | module.exports = PuppeteerEmailProviderOutlook 170 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/get-email.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const delay = require('delay') 4 | const parseEmail = require('parse-email') 5 | 6 | module.exports = async (page) => { 7 | // await page.waitForNavigation({ timeout: 0 }) 8 | 9 | // view original message source from the email overflow options menu 10 | await page.waitFor('.allowTextSelection button[aria-label="More mail actions"]', { visible: true }) 11 | await page.click('.allowTextSelection button[aria-label="More mail actions"]') 12 | await page.waitFor('button[name~=View]', { visible: true }) 13 | await page.click('button[name~=View]') 14 | 15 | await page.waitFor('[role=dialog] .allowTextSelection', { visible: true }) 16 | await delay(500) 17 | const content = await page.$eval('[role=dialog] .allowTextSelection', $el => $el.textContent) 18 | await page.click('[role=dialog] button') 19 | await delay(500) 20 | 21 | try { 22 | const email = await parseEmail(content) 23 | return email 24 | } catch (err) { 25 | return null 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/get-emails.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const delay = require('delay') 4 | const ow = require('ow') 5 | const pMap = require('p-map') 6 | 7 | const getEmail = require('./get-email') 8 | 9 | module.exports = async (opts) => { 10 | const { 11 | browser, 12 | query 13 | } = opts 14 | 15 | ow(query, ow.string.nonEmpty.label('query')) 16 | 17 | let page 18 | 19 | try { 20 | const page = await browser.newPage() 21 | await page.goto('https://outlook.live.com/mail/inbox') 22 | await delay(1000) 23 | 24 | // search for an input query to narrow down email results 25 | // TODO: don't require a search query 26 | await page.waitFor('input[aria-label=Search]', { visible: true }) 27 | await delay(2000) 28 | 29 | let attempts = 0 30 | do { 31 | await page.focus('input[aria-label=Search]') 32 | await page.click('input[aria-label=Search]') 33 | await page.type('input[aria-label=Search]', query, { delay: 7 }) 34 | 35 | const value = await page.$eval('input[aria-label=Search]', (el) => el.value) 36 | if (value.trim() === query.trim()) { 37 | break 38 | } 39 | 40 | if (++attempts > 3) { 41 | throw new Error(`unable to search for query "${query}"`) 42 | } 43 | 44 | // erase current input 45 | await page.focus('input[aria-label=Search]') 46 | for (let i = 0; i < value.length + 8; ++i) { 47 | await page.keyboard.press('Backspace') 48 | } 49 | await delay(200) 50 | } while (true) 51 | 52 | // await page.waitForNavigation({ timeout: 0 }) 53 | await page.click('button[aria-label=Search]') 54 | await delay(2000) 55 | 56 | const $emails = await page.$$('[data-convid] > div > div') 57 | 58 | // fetch and parse individual emails 59 | const emails = await pMap($emails, async ($email) => { 60 | await Promise.all([ 61 | page.waitForNavigation(), 62 | $email.click() 63 | ]) 64 | 65 | return getEmail(page) 66 | }, { 67 | concurrency: 1 68 | }) 69 | 70 | await page.close() 71 | return emails 72 | } catch (err) { 73 | await page.close() 74 | throw err 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/scratch: -------------------------------------------------------------------------------- 1 | 2 | // fetch list of email ids 3 | const ids = await page.$$eval('[data-convid]', $els => { 4 | return $els.map(($el) => $el.getAttribute('data-convid')) 5 | }) 6 | 7 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/send-email.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (email, opts) => { 4 | // TODO 5 | throw new Error('TODO: "PuppeteerEmailProviderOutlook.sendEmail"') 6 | } 7 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (user, opts) => { 4 | const { 5 | browser 6 | } = opts 7 | 8 | const page = await browser.newPage() 9 | await page.goto('https://login.live.com/') 10 | 11 | await page.waitFor('input[type=email]', { visible: true }) 12 | await page.type('input[type=email]', user.email) 13 | await page.click('input[type=submit]') 14 | 15 | await page.waitFor('input[type=password]', { visible: true }) 16 | await page.type('input[type=password]', user.password) 17 | 18 | await page.waitFor('input[type=checkbox]', { visible: true }) 19 | await page.click('input[type=checkbox]') 20 | 21 | await Promise.all([ 22 | page.waitForNavigation(), 23 | page.click('input[type=submit]') 24 | ]) 25 | 26 | // should now be at 27 | // https://account.microsoft.com/?lang=en-US&refd=account.live.com&refp=landing 28 | 29 | await page.close() 30 | } 31 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/signout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (user, opts) => { 4 | // TODO 5 | throw new Error('TODO: "PuppeteerEmailProviderOutlook.signout"') 6 | } 7 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/lib/signup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // TODO: move addNumberToBlacklist and close to top-level SMSNumberVerifier 4 | // TODO: remove manual inputs for edge cases and bail instead -- breaks batch jobs 5 | 6 | const delay = require('delay') 7 | const faker = require('faker') 8 | const tempy = require('tempy') 9 | 10 | module.exports = async (user, opts) => { 11 | const { 12 | captchaSolver, 13 | smsNumberVerifier, 14 | browser 15 | } = opts 16 | 17 | const page = await browser.newPage() 18 | await page.goto('https://signup.live.com/signup') 19 | 20 | // email / username 21 | // ---------------- 22 | 23 | await page.waitFor('#liveSwitch', { visible: true }) 24 | await page.click('#liveSwitch', { delay: 10 }) 25 | await delay(100) 26 | 27 | let error = null 28 | do { 29 | await page.type('#MemberName', user.username, { delay: 40 }) 30 | await delay(250) 31 | await page.click('#iSignupAction', { delay: 20 }) 32 | 33 | await delay(1000) 34 | error = await page.$('#MemberNameError') 35 | 36 | // TODO: there is an issue here where if one username fails, the next will 37 | // always also fail 38 | if (error) { 39 | await delay(1000) 40 | await page.focus('#MemberName') 41 | for (let i = 0; i < user.username.length + 8; ++i) { 42 | await page.keyboard.press('Backspace') 43 | } 44 | 45 | await delay(1000) 46 | user.username = faker.internet.userName() + (Math.random() * 10000 | 0) 47 | } 48 | } while (error) 49 | 50 | // password 51 | // ------------------- 52 | 53 | await page.waitFor('#PasswordInput', { visible: true }) 54 | await delay(100) 55 | await page.type('#PasswordInput', user.password, { delay: 10 }) 56 | await delay(100) 57 | await page.click('#iOptinEmail', { delay: 10 }) 58 | await delay(100) 59 | await page.click('#iSignupAction', { delay: 30 }) 60 | 61 | // first and last name 62 | // ------------------- 63 | 64 | await page.waitFor('#FirstName', { visible: true }) 65 | await delay(100) 66 | await page.type('#FirstName', user.firstName, { delay: 30 }) 67 | await delay(120) 68 | await page.type('#LastName', user.lastName, { delay: 35 }) 69 | await delay(260) 70 | await page.click('#iSignupAction', { delay: 25 }) 71 | 72 | // birth date 73 | // ---------- 74 | 75 | await page.waitFor('#BirthMonth', { visible: true }) 76 | await delay(100) 77 | await page.select('#BirthMonth', user.birthday.month) 78 | await delay(120) 79 | await page.select('#BirthDay', user.birthday.day) 80 | await delay(260) 81 | await page.select('#BirthYear', user.birthday.year) 82 | await delay(220) 83 | await page.click('#iSignupAction', { delay: 8 }) 84 | 85 | // captcha and/or sms validation 86 | // ----------------------------- 87 | 88 | await delay(2000) 89 | let waitForNavigation = true 90 | let attempt = 0 91 | 92 | const waitForManualInput = async (msg) => { 93 | console.warn(msg) 94 | console.warn('waiting for manual input...') 95 | await page.waitForNavigation({ timeout: 200000 }) 96 | waitForNavigation = false 97 | } 98 | 99 | // TODO: is it possible to go from sms verification to captcha or will it 100 | // always be captcha first? 101 | do { 102 | if (await page.$('#wlspispHipChallengeContainer')) { 103 | console.log('SMS NUMBER VALIDATION') 104 | 105 | // sms validation 106 | if (smsNumberVerifier) { 107 | const blacklist = new Set() 108 | const service = 'microsoft' 109 | let number 110 | 111 | // TODO: TEMP 112 | // await waitForManualInput('sms number verification required') 113 | // break 114 | 115 | do { 116 | number = await smsNumberVerifier.getNumber({ service, blacklist }) 117 | if (!number) break // TODO 118 | 119 | const info = smsNumberVerifier.getNumberInfo(number) 120 | if (!info || !info.isValid()) throw new Error(`received invalid sms number "${number}"`) 121 | 122 | // select country code prefix 123 | const regionCode = info.getRegionCode().toUpperCase() 124 | 125 | // ignore country code prefix 126 | const shortNumber = info.getNumber('significant') 127 | 128 | console.log('sms verification', { 129 | number, 130 | regionCode, 131 | shortNumber 132 | }) 133 | 134 | await page.select('#wlspispHipChallengeContainer select', regionCode) 135 | 136 | await page.type('#wlspispHipChallengeContainer input[type=text]', shortNumber, { delay: 15 }) 137 | await delay(200) 138 | await page.click('#wlspispHipControlButtonsContainer a[title="Send SMS code"]', { delay: 28 }) 139 | 140 | let error = null 141 | await Promise.race([ 142 | page.waitFor('#wlspispHipChallengeContainer input[type=text]', { visible: true }), 143 | page.waitFor('.alert-error[aria-hidden=false]', { visible: true }) 144 | .then(() => page.$eval('.alert-error[aria-hidden=false]', (e) => e.innerText)) 145 | .then((e) => { error = e.trim() }) 146 | ]) 147 | 148 | if (error) { 149 | console.warn(`sms number error "${number}":`, error) 150 | blacklist.add(number) 151 | 152 | if (/unavailable|cannot|too many|banned/i.test(error)) { 153 | throw new Error(`sms verification failed "${number}" "${error}"`) 154 | } 155 | 156 | if (smsNumberVerifier.provider.addNumberToBlacklist) { 157 | const result = await smsNumberVerifier.provider.addNumberToBlacklist({ service, number }) 158 | console.warn('sms adding to blacklist', { service, number }, result) 159 | } 160 | 161 | await delay(1000) 162 | await page.focus('#wlspispHipChallengeContainer input[type=text]') 163 | for (let i = 0; i < shortNumber.length + 8; ++i) { 164 | await page.keyboard.press('Backspace') 165 | } 166 | await delay(1000) 167 | } else { 168 | break 169 | } 170 | } while (true) 171 | 172 | if (!waitForNavigation) { 173 | break 174 | } 175 | 176 | let authCodes 177 | try { 178 | authCodes = await smsNumberVerifier.getAuthCodes({ number, service }) 179 | if (!authCodes || !authCodes.length) throw new Error(`unable to retrieve auth code ${number} ${service}`) 180 | } catch (err) { 181 | if (smsNumberVerifier.provider.addNumberToBlacklist) { 182 | const result = await smsNumberVerifier.provider.addNumberToBlacklist({ service, number }) 183 | console.warn('sms adding to blacklist', { service, number }, result) 184 | } 185 | 186 | throw err 187 | } 188 | console.log('sms request', service, number, authCodes) 189 | 190 | // TODO: likely won't work for multiple auth codes found because error will still be 191 | // present after first failure 192 | for (let i = 0; i < authCodes.length; ++i) { 193 | const code = authCodes[i] 194 | await page.type('#wlspispHipSolutionContainer input[type=text]', code) 195 | 196 | let error = false 197 | await Promise.all([ 198 | page.click('#iSignupAction', { delay: 9 }), 199 | Promise.race([ 200 | page.waitForNavigation({ timeout: 60000 }) 201 | .then(() => { waitForNavigation = false }), 202 | // page.waitFor('.alert.alert-error', { visible: true }) 203 | page.waitFor('.alert-error[aria-hidden=false]', { visible: true, timeout: 60000 }) 204 | .then(() => page.$eval('.alert-error[aria-hidden=false]', (e) => e.innerText)) 205 | .then((e) => { error = e.trim() }) 206 | ]) 207 | ]) 208 | 209 | if (error) { 210 | console.warn('sms code error', { number, code }, error) 211 | 212 | if (smsNumberVerifier.provider.addNumberToBlacklist) { 213 | const result = await smsNumberVerifier.provider.addNumberToBlacklist({ service, number }) 214 | console.warn('sms adding to blacklist', { service, number }, result) 215 | } 216 | 217 | await delay(1000) 218 | await page.focus('#wlspispHipSolutionContainer input[type=text]') 219 | for (let i = 0; i < code.length + 8; ++i) { 220 | await page.keyboard.press('Backspace') 221 | } 222 | await delay(1000) 223 | } else { 224 | break 225 | } 226 | } 227 | 228 | if (waitForNavigation) { 229 | await waitForManualInput('sms number verification failed') 230 | } 231 | } else { 232 | await waitForManualInput('sms number verification required') 233 | } 234 | } else if (attempt <= 0) { 235 | await delay(3000) 236 | } else if (await page.$('#hipTemplateContainer')) { 237 | console.log('CAPTCHA CHALLENGE') 238 | 239 | if (captchaSolver) { 240 | do { 241 | await page.waitFor('#hipTemplateContainer img[aria-label="Visual Challenge"]', { visible: true }) 242 | 243 | const $img = await page.$('#hipTemplateContainer img[aria-label="Visual Challenge"]') 244 | const captchaPath = tempy.file({ extension: 'png' }) 245 | await $img.screenshot({ path: captchaPath }) 246 | 247 | console.log({ captchaPath }) 248 | 249 | const taskId = await captchaSolver.createTask({ 250 | type: 'image-to-text', 251 | image: captchaPath 252 | }) 253 | console.log(`captcha task id: ${taskId}`) 254 | await delay(10000) 255 | 256 | const result = await captchaSolver.getTaskResult(taskId, { 257 | timeout: 60000, 258 | minTimeout: 8000, 259 | onFailedAttempt: (err) => { 260 | console.log(`Error getting captcha task result #${err.attemptNumber} failed. Retrying ${err.attemptsLeft} times left...`) 261 | } 262 | }) 263 | console.log(`captcha task result: ${JSON.stringify(result, null, 2)}`) 264 | 265 | const solution = result && result.solution && result.solution.text 266 | await page.type('#hipTemplateContainer input', solution, { delay: 40 }) 267 | 268 | // TODO: handle incorrect captcha result 269 | 270 | let error = false 271 | await Promise.all([ 272 | page.click('#iSignupAction', { delay: 9 }), 273 | Promise.race([ 274 | page.waitForNavigation({ timeout: 60000 }) 275 | .then(() => { waitForNavigation = false }), 276 | page.waitFor('.alert-error[aria-hidden=false]', { visible: true }) 277 | .then(() => page.$eval('.alert-error[aria-hidden=false]', (e) => e.innerText)) 278 | .then((e) => { error = e.trim() }), 279 | page.waitFor('#wlspispHipChallengeContainer', { visible: true }) 280 | ]) 281 | ]) 282 | 283 | if (error) { 284 | console.warn('captcha solver error:', { solution }, error) 285 | } else { 286 | break 287 | } 288 | } while (true) 289 | } else { 290 | await waitForManualInput('captcha solver required') 291 | } 292 | } else { 293 | await waitForManualInput('waiting for navigation') 294 | } 295 | 296 | ++attempt 297 | } while (waitForNavigation) 298 | 299 | // main account page 300 | // ----------------- 301 | 302 | await delay(1000) 303 | await page.goto('https://www.outlook.com/?refd=account.microsoft.com&fref=home.banner.profile') 304 | 305 | // email inbox first-run 306 | // --------------------- 307 | 308 | await delay(800) 309 | 310 | await Promise.race([ 311 | delay(2000), 312 | page.waitFor('.dialog button.nextButton', { visible: true }), 313 | page.waitFor('.dialog button.primaryButton', { visible: true }) 314 | ]) 315 | 316 | // keep pressing next... 317 | while (true) { 318 | if (!await page.$('.dialog button.nextButton')) break 319 | await page.click('.dialog button.nextButton', { delay: 5 }) 320 | await delay(220) 321 | } 322 | 323 | // wait until "let's go" button appears... 324 | while (true) { 325 | await delay(1000) 326 | if (await page.$('.dialog button.primaryButton')) break 327 | } 328 | 329 | await delay(120) 330 | await Promise.all([ 331 | page.waitForNavigation({ timeout: 60000 }), 332 | page.click('.dialog button.primaryButton', { delay: 7 }) 333 | ]) 334 | 335 | // should now be at https://outlook.live.com/mail/inbox 336 | await page.close() 337 | } 338 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email-provider-outlook", 3 | "version": "0.1.0", 4 | "description": "Puppeteer email provider for Outlook.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "scripts": { 10 | "docs": "update-markdown-jsdoc", 11 | "test": "standard" 12 | }, 13 | "engines": { 14 | "node": ">=8" 15 | }, 16 | "keywords": [ 17 | "puppeteer", 18 | "email", 19 | "outlook", 20 | "microsoft" 21 | ], 22 | "devDependencies": { 23 | "ava": "^0.25.0", 24 | "standard": "^11.0.0", 25 | "update-markdown-jsdoc": "^1.0.2" 26 | }, 27 | "dependencies": { 28 | "delay": "^2.0.0", 29 | "faker": "^4.1.0", 30 | "ow": "^0.6.0", 31 | "p-map": "^1.2.0", 32 | "parse-email": "^1.0.0", 33 | "puppeteer-email-provider": "^0.0.5", 34 | "puppeteer-email-session": "^0.0.5", 35 | "tempy": "^0.2.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-outlook/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email-provider-outlook 2 | 3 | > Puppeteer email provider for [Outlook](https://outlook.live.com). 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email-provider-outlook.svg)](https://www.npmjs.com/package/puppeteer-email-provider-outlook) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email-provider-outlook 11 | ``` 12 | 13 | ## API 14 | 15 | 16 | 17 | #### Table of Contents 18 | 19 | - [PuppeteerEmailProviderOutlook](#puppeteeremailprovideroutlook) 20 | - [name](#name) 21 | - [signup](#signup) 22 | - [signin](#signin) 23 | - [signout](#signout) 24 | - [sendEmail](#sendemail) 25 | - [getEmails](#getemails) 26 | 27 | ### [PuppeteerEmailProviderOutlook](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L19-L167) 28 | 29 | **Extends: PuppeteerEmailProvider** 30 | 31 | Puppeteer email provider for [Outlook](https://outlook.live.com). 32 | 33 | Type: `function ()` 34 | 35 | * * * 36 | 37 | #### [name](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L25-L27) 38 | 39 | Email provider to automate. 40 | 41 | Type: PuppeteerEmailProvider 42 | 43 | * * * 44 | 45 | #### [signup](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L49-L66) 46 | 47 | Creates a new email account. 48 | 49 | Returns an email session with the authenticated puppeteer browser. 50 | 51 | Type: `function (user, opts)` 52 | 53 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User info for the account to create 54 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Username 55 | - `user.password` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Password 56 | - `user.firstName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's given name 57 | - `user.lastName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's family name 58 | - `user.birthday` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User's birthday 59 | - `user.birthday.month` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday month 60 | - `user.birthday.day` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday day 61 | - `user.birthday.year` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday year 62 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options 63 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer browser instance to use 64 | 65 | * * * 66 | 67 | #### [signin](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L85-L112) 68 | 69 | Signs into an existing email account. 70 | 71 | You must specify either `user.username` or `user.email`. 72 | 73 | Returns an email session with the authenticated puppeteer browser. 74 | 75 | Type: `function (user, opts)` 76 | 77 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User info for the account to sign into 78 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Username (implies email) 79 | - `user.email` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Email (implies username) 80 | - `user.password` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Password 81 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options 82 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer browser instance to use 83 | 84 | * * * 85 | 86 | #### [signout](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L121-L127) 87 | 88 | Signs out of an authenticated session. 89 | 90 | Type: `function (session): Promise` 91 | 92 | - `session` **PuppeteerEmailSession** 93 | 94 | * * * 95 | 96 | #### [sendEmail](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L138-L147) 97 | 98 | Sends an email from an authenticated session. 99 | 100 | Type: `function (session, email, opts): Promise` 101 | 102 | - `session` **PuppeteerEmailSession** 103 | - `email` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** TODO 104 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`) 105 | 106 | * * * 107 | 108 | #### [getEmails](https://github.com/transitive-bullshit/puppeteer-email/blob/a5e0c04002d628c370d4ed7dad0e5550c749ab3b/packages/puppeteer-email-provider-outlook/index.js#L158-L166) 109 | 110 | Fetches emails from the inbox of an authenticated session. 111 | 112 | Type: `function (session, opts)` 113 | 114 | - `session` **PuppeteerEmailSession** 115 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`) 116 | - `opts.query` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Search query to narrow down results 117 | 118 | * * * 119 | 120 | ## Related 121 | 122 | - [puppeteer-email](https://github.com/transitive-bullshit/puppeteer-email) - Email automation driven by headless chrome. 123 | 124 | ## License 125 | 126 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 127 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ow = require('ow') 4 | 5 | const PuppeteerEmailProvider = require('puppeteer-email-provider') 6 | const PuppeteerEmailSession = require('puppeteer-email-session') 7 | 8 | const signup = require('./lib/signup') 9 | const signin = require('./lib/signin') 10 | const signout = require('./lib/signout') 11 | const sendEmail = require('./lib/send-email') 12 | const getEmails = require('./lib/get-emails') 13 | 14 | /** 15 | * Puppeteer email provider for [Yahoo](https://mail.yahoo.com). 16 | * 17 | * @extends PuppeteerEmailProvider 18 | */ 19 | class PuppeteerEmailProviderYahoo extends PuppeteerEmailProvider { 20 | /** 21 | * Email provider to automate. 22 | * 23 | * @member {PuppeteerEmailProvider} 24 | */ 25 | get name () { 26 | return 'yahoo' 27 | } 28 | 29 | /** 30 | * Creates a new email account. 31 | * 32 | * Returns an email session with the authenticated puppeteer browser. 33 | * 34 | * @param {object} user - User info for the account to create 35 | * @param {string} user.username - Username 36 | * @param {string} user.password - Password 37 | * @param {string} user.firstName - User's given name 38 | * @param {string} user.lastName - User's family name 39 | * @param {object} user.birthday - User's birthday 40 | * @param {string} user.birthday.month - User's birthday month 41 | * @param {string} user.birthday.day - User's birthday day 42 | * @param {string} user.birthday.year - User's birthday year 43 | * 44 | * @param {object} opts - Options 45 | * @param {Object} opts.browser - Puppeteer browser instance to use 46 | * 47 | * @return {Promise} 48 | */ 49 | async signup (user, opts) { 50 | ow(user, ow.object.plain.nonEmpty.label('user')) 51 | ow(user.username, ow.string.nonEmpty.label('user.username')) 52 | ow(user.password, ow.string.nonEmpty.label('user.password')) 53 | ow(opts, ow.object.nonEmpty.label('opts')) 54 | ow(opts.browser, ow.object.nonEmpty.label('browser')) 55 | 56 | await signup(user, opts) 57 | 58 | return new PuppeteerEmailSession({ 59 | user: { 60 | username: user.username, 61 | email: `${user.username}@yahoo.com` 62 | }, 63 | browser: opts.browser, 64 | provider: this 65 | }) 66 | } 67 | 68 | /** 69 | * Signs into an existing email account. 70 | * 71 | * You must specify either `user.username` or `user.email`. 72 | * 73 | * Returns an email session with the authenticated puppeteer browser. 74 | * 75 | * @param {object} user - User info for the account to sign into 76 | * @param {string} [user.username] - Username (implies email) 77 | * @param {string} [user.email] - Email (implies username) 78 | * @param {string} user.password - Password 79 | * 80 | * @param {object} opts - Options 81 | * @param {Object} opts.browser - Puppeteer browser instance to use 82 | * 83 | * @return {Promise} 84 | */ 85 | async signin (user, opts) { 86 | ow(user, ow.object.plain.nonEmpty.label('user')) 87 | ow(user.password, ow.string.nonEmpty.label('user.password')) 88 | 89 | if (user.username) { 90 | ow(user.username, ow.string.nonEmpty) 91 | } else if (user.email) { 92 | ow(user.email, ow.string.nonEmpty) 93 | } else { 94 | throw new Error('missing required parameter "username" or "email"') 95 | } 96 | 97 | ow(opts, ow.object.nonEmpty) 98 | ow(opts.browser, ow.object.nonEmpty) 99 | 100 | user.email = user.email || `${user.username}@yahoo.com` 101 | 102 | await signin(user, opts) 103 | 104 | return new PuppeteerEmailSession({ 105 | user: { 106 | username: user.username, 107 | email: user.email 108 | }, 109 | browser: opts.browser, 110 | provider: this 111 | }) 112 | } 113 | 114 | /** 115 | * Signs out of an authenticated session. 116 | * 117 | * @param {PuppeteerEmailSession} session 118 | * 119 | * @return {Promise} 120 | */ 121 | async signout (session) { 122 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 123 | 124 | return signout({ 125 | browser: session.browser 126 | }) 127 | } 128 | 129 | /** 130 | * Sends an email from an authenticated session. 131 | * 132 | * @param {PuppeteerEmailSession} session 133 | * @param {object} email - TODO 134 | * @param {object} [opts] - Options 135 | * 136 | * @return {Promise} 137 | */ 138 | async sendEmail (session, email, opts = { }) { 139 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 140 | ow(email, ow.object) 141 | ow(opts, ow.object.plain) 142 | 143 | return sendEmail(email, { 144 | browser: session.browser, 145 | ...opts 146 | }) 147 | } 148 | 149 | /** 150 | * Fetches emails from the inbox of an authenticated session. 151 | * 152 | * @param {PuppeteerEmailSession} session 153 | * @param {object} [opts] - Options 154 | * @param {object} [opts.query] - Search query to narrow down results 155 | * 156 | * @return {Promise>} 157 | */ 158 | async getEmails (session, opts = { }) { 159 | ow(session, ow.object.instanceOf(PuppeteerEmailSession)) 160 | ow(opts, ow.object.plain) 161 | 162 | return getEmails({ 163 | browser: session.browser, 164 | ...opts 165 | }) 166 | } 167 | } 168 | 169 | module.exports = PuppeteerEmailProviderYahoo 170 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/get-email.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const delay = require('delay') 4 | const parseEmail = require('parse-email') 5 | 6 | module.exports = async (page) => { 7 | // TODO 8 | throw new Error('TODO: "PuppeteerEmailProviderYahoo.getEmail"') 9 | } 10 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/get-emails.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const delay = require('delay') 4 | const ow = require('ow') 5 | const pMap = require('p-map') 6 | 7 | const getEmail = require('./get-email') 8 | 9 | module.exports = async (opts) => { 10 | const { 11 | browser, 12 | query 13 | } = opts 14 | 15 | ow(query, ow.string.nonEmpty) 16 | 17 | const page = await browser.newPage() 18 | await page.goto('https://mail.yahoo.com') 19 | 20 | // TODO 21 | 22 | await page.close() 23 | return emails 24 | } 25 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/send-email.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (email, opts) => { 4 | // TODO 5 | throw new Error('TODO: "PuppeteerEmailProviderYahoo.sendEmail"') 6 | } 7 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/signin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (user, opts) => { 4 | const { 5 | browser 6 | } = opts 7 | 8 | // TODO 9 | 10 | await page.close() 11 | } 12 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/signout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = async (user, opts) => { 4 | // TODO 5 | throw new Error('TODO: "PuppeteerEmailProviderYahoo.signout"') 6 | } 7 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/lib/signup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // TODO: remove manual inputs for edge cases and bail instead -- breaks batch jobs 4 | 5 | const delay = require('delay') 6 | // const faker = require('faker') // TODO 7 | 8 | module.exports = async (user, opts) => { 9 | const { 10 | smsNumberVerifier, 11 | browser 12 | } = opts 13 | 14 | const page = await browser.newPage() 15 | await page.goto('https://login.yahoo.com/account/create') 16 | 17 | // basic user info 18 | // --------------- 19 | 20 | await page.type('input[name=firstName]', user.firstName, { delay: 40 }) 21 | await delay(250) 22 | await page.type('input[name=lastName]', user.lastName, { delay: 8 }) 23 | await delay(330) 24 | await page.type('input[name=yid]', user.username, { delay: 32 }) 25 | await delay(134) 26 | 27 | // sms validation 28 | // -------------- 29 | 30 | if (!smsNumberVerifier) { 31 | throw new Error('sms validation required') 32 | } 33 | 34 | let attempts = 0 35 | let service = 'yahoo' 36 | let number 37 | 38 | do { 39 | number = await smsNumberVerifier.getNumber({ service }) 40 | if (!number) throw new Error() // TODO 41 | 42 | const info = smsNumberVerifier.getNumberInfo(number) 43 | if (!info || !info.isValid()) throw new Error() // TODO 44 | 45 | // select country code prefix 46 | await page.select('select[name=shortCountryCode]', info.getRegionCode().toUpperCase()) 47 | 48 | // ignore country code prefix 49 | const shortNumber = info.getNumber('significant') 50 | 51 | await page.type('input[name=phone]', shortNumber, { delay: 13 }) 52 | 53 | // birth date 54 | // ---------- 55 | 56 | await delay(33) 57 | await page.type('input[name=password]', user.password, { delay: 3 }) 58 | await delay(47) 59 | await page.select('select[name=mm]', user.birthday.month) 60 | await delay(62) 61 | await page.type('input[name=dd]', user.birthday.day) 62 | await delay(23) 63 | await page.type('input[name=yyyy]', user.birthday.year) 64 | await delay(76) 65 | 66 | await Promise.all([ 67 | page.click('button[type=submit]', { delay: 9 }), 68 | page.waitForNavigation({ timeout: 0 }) 69 | ]) 70 | 71 | await delay(1000) 72 | 73 | let error = null 74 | 75 | if (await page.$('#reg-error-phone')) { 76 | await page.waitFor('#reg-error-phone', { visible: true }) 77 | .then(() => page.$eval('#reg-error-phone', (e) => e.innerText)) 78 | .then((e) => { error = e.trim() }) 79 | } 80 | 81 | if (error) { 82 | ++attempts 83 | console.warn(`phone number error "${number}" (${attempts} attempts):`, error) 84 | 85 | if (smsNumberVerifier.provider.addNumberToBlacklist) { 86 | const result = await smsNumberVerifier.provider.addNumberToBlacklist({ service, number }) 87 | console.warn('sms adding to blacklist', { service, number }, result) 88 | } 89 | 90 | if (++attempts > 3) { 91 | throw new Error(`phone number error: ${error}`) 92 | } 93 | 94 | await delay(5000) 95 | } else { 96 | break 97 | } 98 | } while (true) 99 | 100 | // TODO: waitForNavigation also happens for errors and wipes out most fields 101 | // birth date, password, and phone number stuffs 102 | // if there's an error, 103 | 104 | // sms validation 105 | // -------------- 106 | 107 | let waitForNavigation = true 108 | 109 | const waitForManualInput = async (msg) => { 110 | console.warn(msg) 111 | console.warn('waiting for manual input...') 112 | await page.waitForNavigation({ timeout: 200000 }) 113 | waitForNavigation = false 114 | } 115 | 116 | if (await page.$('button[type=submit][name=sendCode]')) { 117 | await page.click('button[type=submit][name=sendCode]', { delay: 9 }) 118 | await page.waitFor('input[name=code]') 119 | 120 | const authCodes = await smsNumberVerifier.getAuthCodes({ number, service }) 121 | console.log('sms request', service, number, authCodes) 122 | 123 | if (authCodes.length) { 124 | for (let i = 0; i < authCodes.length; ++i) { 125 | const code = authCodes[i] 126 | await page.type('input[name=code]', code) 127 | 128 | let error = false 129 | await Promise.all([ 130 | page.click('button[name=verifyCode]', { delay: 4 }), 131 | Promise.race([ 132 | page.waitForNavigation({ timeout: 0 }) 133 | .then(() => { waitForNavigation = false }), 134 | page.waitFor('.error-msg', { visible: true }) 135 | .then(() => page.$eval('.error-msg', (e) => e.innerText)) 136 | .then((e) => { error = e.trim() }) 137 | ]) 138 | ]) 139 | 140 | if (error) { 141 | console.warn('sms code error', { number, code }, error) 142 | 143 | await delay(1000) 144 | await page.focus('input[type=code]') 145 | for (let i = 0; i < code.length + 8; ++i) { 146 | await page.keyboard.press('Backspace') 147 | } 148 | await delay(1000) 149 | } else { 150 | break 151 | } 152 | } 153 | } 154 | 155 | if (waitForNavigation) { 156 | await waitForManualInput('sms number verification failed') 157 | } 158 | } 159 | 160 | await page.waitFor('.mail-button-wait button[type=submit]', { visible: true }) 161 | await Promise.all([ 162 | page.click('.mail-button-wait button[type=submit]', { delay: 9 }), 163 | page.waitForNavigation() 164 | ]) 165 | 166 | // main account page 167 | // ----------------- 168 | 169 | await page.goto('http://mail.yahoo.com/') 170 | 171 | // email inbox first-run 172 | // --------------------- 173 | 174 | await delay(800) 175 | 176 | // close any first-run dialogs 177 | while (true) { 178 | if (!await page.$('button[title=Close]')) break 179 | await page.click('button[title=Close]', { delay: 9 }) 180 | await delay(350) 181 | 182 | try { 183 | await page.click('button[title="Not now"]', { delay: 9 }) 184 | } catch (err) { } 185 | } 186 | 187 | // should now be at https://mail.yahoo.com/mail/inbox 188 | await page.close() 189 | } 190 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email-provider-yahoo", 3 | "version": "0.0.2", 4 | "description": "Puppeteer email provider for Yahoo.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "scripts": { 10 | "docs": "update-markdown-jsdoc", 11 | "test": "standard" 12 | }, 13 | "engines": { 14 | "node": ">=8" 15 | }, 16 | "keywords": [ 17 | "puppeteer", 18 | "email", 19 | "yahoo" 20 | ], 21 | "devDependencies": { 22 | "ava": "^0.25.0", 23 | "standard": "^11.0.0", 24 | "update-markdown-jsdoc": "^1.0.2" 25 | }, 26 | "dependencies": { 27 | "delay": "^2.0.0", 28 | "faker": "^4.1.0", 29 | "ow": "^0.6.0", 30 | "p-map": "^1.2.0", 31 | "parse-email": "^1.0.0", 32 | "puppeteer-email-provider": "^0.0.5", 33 | "puppeteer-email-session": "^0.0.5", 34 | "tempy": "^0.2.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider-yahoo/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email-provider-yahoo 2 | 3 | > Puppeteer email provider for [Yahoo](https://mail.yahoo.com). 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email-provider-yahoo.svg)](https://www.npmjs.com/package/puppeteer-email-provider-yahoo) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email-provider-yahoo 11 | ``` 12 | 13 | ## API 14 | 15 | TODO 16 | 17 | ## Related 18 | 19 | - [puppeteer-email](https://github.com/transitive-bullshit/puppeteer-email) - Email automation driven by headless chrome. 20 | 21 | ## License 22 | 23 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 24 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Abstract base class for puppeteer email providers. 5 | */ 6 | class PuppeteerEmailProvider { 7 | /** 8 | * Provider name. 9 | * 10 | * @member {string} 11 | */ 12 | get name () { 13 | throw new Error('email provider must override "name"') 14 | } 15 | 16 | /** 17 | * Creates a new email account using this provider. 18 | * 19 | * Some providers may require additional user information during signup. 20 | * 21 | * Returns an email session with the authenticated puppeteer browser. 22 | * 23 | * @param {object} user - User info for the account to create 24 | * @param {string} user.username - Username 25 | * @param {string} user.password - Password 26 | * @param {string} user.firstName - User's given name 27 | * @param {string} user.lastName - User's family name 28 | * @param {object} user.birthday - User's birthday 29 | * @param {string} user.birthday.month - User's birthday month 30 | * @param {string} user.birthday.day - User's birthday day 31 | * @param {string} user.birthday.year - User's birthday year 32 | * 33 | * @param {object} opts - Options 34 | * @param {Object} opts.browser - Puppeteer browser instance to use 35 | * 36 | * @return {Promise} 37 | */ 38 | async signup (user, opts) { 39 | throw new Error('email provider must override "signup"') 40 | } 41 | 42 | /** 43 | * Signs into an existing email account using this provider. 44 | * 45 | * You must specify either `user.username` or `user.email`. 46 | * 47 | * Returns an email session with the authenticated puppeteer browser. 48 | * 49 | * @param {object} user - User info for the account to sign into 50 | * @param {string} [user.username] - Username (implies email) 51 | * @param {string} [user.email] - Email (implies username) 52 | * 53 | * @param {object} opts - Options 54 | * @param {Object} opts.browser - Puppeteer browser instance to use 55 | * 56 | * @return {Promise} 57 | */ 58 | async signin (user, opts) { 59 | throw new Error('email provider must override "signin"') 60 | } 61 | 62 | /** 63 | * Signs out of the given authenticated session using this provider. 64 | * 65 | * @param {PuppeteerEmailSession} session 66 | * @return {Promise} 67 | */ 68 | async signout (session) { 69 | throw new Error('email provider must override "signout"') 70 | } 71 | 72 | /** 73 | * Sends an email from an authenticated session using this provider. 74 | * 75 | * @param {PuppeteerEmailSession} session 76 | * @param {object} email - Details of email to send 77 | * @param {object} [opts] - Options 78 | * @return {Promise} 79 | */ 80 | async sendEmail (session, email, opts) { 81 | throw new Error('email provider must override "sendEmail"') 82 | } 83 | 84 | /** 85 | * Fetches emails visible from the inbox of an authenticated session using 86 | * this provider. 87 | * 88 | * @param {PuppeteerEmailSession} session 89 | * @param {object} [opts] - Options 90 | * @param {string} [opts.query] - Search query to narrow down results 91 | * @return {Promise>} 92 | */ 93 | async getEmails (session, opts) { 94 | throw new Error('email provider must override "getEmails"') 95 | } 96 | } 97 | 98 | module.exports = PuppeteerEmailProvider 99 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email-provider", 3 | "version": "0.0.5", 4 | "description": "Abstract base class for puppeteer email providers.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "scripts": { 10 | "docs": "update-markdown-jsdoc", 11 | "test": "standard" 12 | }, 13 | "engines": { 14 | "node": ">=8" 15 | }, 16 | "keywords": [ 17 | "puppeteer", 18 | "email" 19 | ], 20 | "devDependencies": { 21 | "ava": "^0.25.0", 22 | "standard": "^11.0.0", 23 | "update-markdown-jsdoc": "^1.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/puppeteer-email-provider/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email-provider 2 | 3 | > Abstract base class for puppeteer email providers. 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email-provider.svg)](https://www.npmjs.com/package/puppeteer-email-provider) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email-provider 11 | ``` 12 | 13 | ## Usage 14 | 15 | **TODO** 16 | 17 | ## API 18 | 19 | 20 | 21 | #### Table of Contents 22 | 23 | - [PuppeteerEmailProvider](#puppeteeremailprovider) 24 | - [name](#name) 25 | - [signup](#signup) 26 | - [signin](#signin) 27 | - [signout](#signout) 28 | - [sendEmail](#sendemail) 29 | - [getEmails](#getemails) 30 | 31 | ### [PuppeteerEmailProvider](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L6-L96) 32 | 33 | Abstract base class for pupeteer email providers. 34 | 35 | Type: `function ()` 36 | 37 | * * * 38 | 39 | #### [name](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L12-L14) 40 | 41 | Provider name. 42 | 43 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 44 | 45 | * * * 46 | 47 | #### [signup](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L38-L40) 48 | 49 | Creates a new email account using this provider. 50 | 51 | Some providers may require additional user information during signup. 52 | 53 | Returns an email session with the authenticated puppeteer browser. 54 | 55 | Type: `function (user, opts)` 56 | 57 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User info for the account to create 58 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Username 59 | - `user.password` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Password 60 | - `user.firstName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's given name 61 | - `user.lastName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's family name 62 | - `user.birthday` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User's birthday 63 | - `user.birthday.month` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday month 64 | - `user.birthday.day` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday day 65 | - `user.birthday.year` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** User's birthday year 66 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options 67 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer browser instance to use 68 | 69 | * * * 70 | 71 | #### [signin](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L58-L60) 72 | 73 | Signs into an existing email account using this provider. 74 | 75 | You must specify either `user.username` or `user.email`. 76 | 77 | Returns an email session with the authenticated puppeteer browser. 78 | 79 | Type: `function (user, opts)` 80 | 81 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User info for the account to sign into 82 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Username (implies email) 83 | - `user.email` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Email (implies username) 84 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options 85 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer browser instance to use 86 | 87 | * * * 88 | 89 | #### [signout](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L68-L70) 90 | 91 | Signs out of the given authenticated session using this provider. 92 | 93 | Type: `function (session): Promise` 94 | 95 | - `session` **PuppeteerEmailSession** 96 | 97 | * * * 98 | 99 | #### [sendEmail](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L80-L82) 100 | 101 | Sends an email from an authenticated session using this provider. 102 | 103 | Type: `function (session, email, opts): Promise` 104 | 105 | - `session` **PuppeteerEmailSession** 106 | - `email` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Details of email to send 107 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options 108 | 109 | * * * 110 | 111 | #### [getEmails](https://github.com/transitive-bullshit/puppeteer-email/blob/f6f725aed9b5f60ce02c0f8fc24369937959796c/packages/puppeteer-email-provider/index.js#L93-L95) 112 | 113 | Fetches emails visible from the inbox of an authenticated session using 114 | this provider. 115 | 116 | Type: `function (session, opts)` 117 | 118 | - `session` **PuppeteerEmailSession** 119 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options 120 | - `opts.query` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Search query to narrow down results 121 | 122 | * * * 123 | 124 | ## Related 125 | 126 | - [puppeteer-email](https://github.com/transitive-bullshit/puppeteer-email) - Email automation driven by headless chrome. 127 | 128 | ## License 129 | 130 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 131 | -------------------------------------------------------------------------------- /packages/puppeteer-email-session/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ow = require('ow') 4 | const PuppeteerEmailProvider = require('puppeteer-email-provider') 5 | 6 | /** 7 | * Holds state for an authenticated puppeteer email session. 8 | * 9 | * @param {object} opts - Options 10 | * @param {object} opts.user - Authenticated user 11 | * @param {string} opts.user.username - Authenticated user's username 12 | * @param {string} opts.user.email - Authenticated user's email 13 | * @param {object} opts.browser - Puppeteer Browser to use 14 | * @param {PuppeteerEmailProvider} opts.provider - Email provider to use 15 | */ 16 | class PuppeteerEmailSession { 17 | constructor (opts) { 18 | ow(opts, ow.object.plain.nonEmpty) 19 | ow(opts.user, ow.object.plain.nonEmpty) 20 | ow(opts.user.username, ow.string.nonEmpty) 21 | ow(opts.user.email, ow.string.nonEmpty) 22 | ow(opts.browser, ow.object) 23 | ow(opts.provider, ow.object.instanceOf(PuppeteerEmailProvider)) 24 | 25 | this._opts = opts 26 | this._isAuthenticated = true 27 | } 28 | 29 | /** 30 | * Authenticated user's username. 31 | * 32 | * @member {string} 33 | */ 34 | get username () { return this._opts.user.username } 35 | 36 | /** 37 | * Authenticated user's email. 38 | * 39 | * @member {string} 40 | */ 41 | get email () { return this._opts.user.email } 42 | 43 | /** 44 | * Email provider to use. 45 | * 46 | * @member {PuppeteerEmailProvider} 47 | */ 48 | get provider () { return this._opts.provider } 49 | 50 | /** 51 | * Puppeteer Browser to use. 52 | * 53 | * @member {string} 54 | */ 55 | get browser () { return this._opts.browser } 56 | 57 | /** 58 | * Whether or not this session is currently authenticated with the given 59 | * email provider. 60 | * 61 | * @member {boolean} 62 | */ 63 | get isAuthenticated () { return this._isAuthenticated } 64 | 65 | /** 66 | * Signs out of this session. 67 | * 68 | * @return {Promise} 69 | */ 70 | async signout () { 71 | if (!this.isAuthenticated) { 72 | return 73 | } 74 | 75 | await this.provider.signout(this) 76 | this._isAuthenticated = false 77 | } 78 | 79 | /** 80 | * Sends an email from this session. 81 | * 82 | * @param {object} email - TODO 83 | * @param {object} [opts] - Options 84 | * 85 | * @return {Promise} 86 | */ 87 | async sendEmail (email, opts) { 88 | if (!this.isAuthenticated) { 89 | throw new Error(`"${this.email}" sendEmail not authenticated`) 90 | } 91 | 92 | return this.provider.sendEmail(this, email, opts) 93 | } 94 | 95 | /** 96 | * Fetches emails from the inbox of this session's account. 97 | * 98 | * @param {object} [opts] - Options 99 | * @param {object} [opts.query] - Search query to narrow down results 100 | * 101 | * @return {Promise>} 102 | */ 103 | async getEmails (opts) { 104 | if (!this.isAuthenticated) { 105 | throw new Error(`"${this.email}" getEmails not authenticated`) 106 | } 107 | 108 | return this.provider.getEmails(this, opts) 109 | } 110 | 111 | /** 112 | * Closes the underlying Puppeteer Browser instance, effectively ending this 113 | * session. 114 | * 115 | * @return {Promise} 116 | */ 117 | async close () { 118 | return this.browser.close() 119 | } 120 | } 121 | 122 | module.exports = PuppeteerEmailSession 123 | -------------------------------------------------------------------------------- /packages/puppeteer-email-session/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email-session", 3 | "version": "0.0.5", 4 | "description": "Holds state for an authenticated puppeteer email session.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "scripts": { 10 | "docs": "update-markdown-jsdoc", 11 | "test": "standard" 12 | }, 13 | "engines": { 14 | "node": ">=8" 15 | }, 16 | "keywords": [ 17 | "puppeteer", 18 | "email" 19 | ], 20 | "devDependencies": { 21 | "ava": "^0.25.0", 22 | "standard": "^11.0.0", 23 | "update-markdown-jsdoc": "^1.0.2" 24 | }, 25 | "dependencies": { 26 | "ow": "^0.3.0", 27 | "puppeteer-email-provider": "^0.0.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/puppeteer-email-session/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email-session 2 | 3 | > Holds state for an authenticated puppeteer email session. 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email-session.svg)](https://www.npmjs.com/package/puppeteer-email-session) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email-session 11 | ``` 12 | 13 | ## API 14 | 15 | 16 | 17 | #### Table of Contents 18 | 19 | - [PuppeteerEmailSession](#puppeteeremailsession) 20 | - [username](#username) 21 | - [email](#email) 22 | - [provider](#provider) 23 | - [browser](#browser) 24 | - [isAuthenticated](#isauthenticated) 25 | - [signout](#signout) 26 | - [sendEmail](#sendemail) 27 | - [getEmails](#getemails) 28 | - [close](#close) 29 | 30 | ### [PuppeteerEmailSession](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L16-L120) 31 | 32 | Holds state for an authenticated puppeteer email session. 33 | 34 | Type: `function (opts)` 35 | 36 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Options 37 | - `opts.user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Authenticated user 38 | - `opts.user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Authenticated user's username 39 | - `opts.user.email` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Authenticated user's email 40 | - `opts.browser` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Puppeteer Browser to use 41 | - `opts.provider` **PuppeteerEmailProvider** Email provider to use 42 | 43 | * * * 44 | 45 | #### [username](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L34-L34) 46 | 47 | Authenticated user's username. 48 | 49 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 50 | 51 | * * * 52 | 53 | #### [email](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L41-L41) 54 | 55 | Authenticated user's email. 56 | 57 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 58 | 59 | * * * 60 | 61 | #### [provider](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L48-L48) 62 | 63 | Email provider to use. 64 | 65 | Type: PuppeteerEmailProvider 66 | 67 | * * * 68 | 69 | #### [browser](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L55-L55) 70 | 71 | Puppeteer Browser to use. 72 | 73 | Type: [string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) 74 | 75 | * * * 76 | 77 | #### [isAuthenticated](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L63-L63) 78 | 79 | Whether or not this session is currently authenticated with the given 80 | email provider. 81 | 82 | Type: [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean) 83 | 84 | * * * 85 | 86 | #### [signout](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L70-L77) 87 | 88 | Signs out of this session. 89 | 90 | Type: `function (): Promise` 91 | 92 | * * * 93 | 94 | #### [sendEmail](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L87-L93) 95 | 96 | Sends an email from this session. 97 | 98 | Type: `function (email, opts): Promise` 99 | 100 | - `email` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** TODO 101 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options 102 | 103 | * * * 104 | 105 | #### [getEmails](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L103-L109) 106 | 107 | Fetches emails from the inbox of this session's account. 108 | 109 | Type: `function (opts)` 110 | 111 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options 112 | - `opts.query` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Search query to narrow down results 113 | 114 | * * * 115 | 116 | #### [close](https://github.com/transitive-bullshit/puppeteer-email/blob/d2ce2dbdb8ee7041c8fd447f3d23f302acf28966/packages/puppeteer-email-session/index.js#L117-L119) 117 | 118 | Closes the underlying Puppeteer Browser instance, effectively ending this 119 | session. 120 | 121 | Type: `function (): Promise` 122 | 123 | * * * 124 | 125 | ## Related 126 | 127 | - [puppeteer-email](https://github.com/transitive-bullshit/puppeteer-email) - Email automation driven by headless chrome. 128 | 129 | ## License 130 | 131 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 132 | -------------------------------------------------------------------------------- /packages/puppeteer-email/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const faker = require('faker') 4 | const ow = require('ow') 5 | const puppeteer = require('puppeteer-extra') 6 | 7 | const PuppeteerEmailProvider = require('puppeteer-email-provider') 8 | const providers = require('./lib/providers') 9 | 10 | puppeteer.use(require('puppeteer-extra-plugin-stealth')()) 11 | 12 | /** 13 | * Main entrypoint for authenticating and automating a given email provider. 14 | * 15 | * @param {string|PuppeteerEmailProvider} provider - Name of built-in email provider or an 16 | * email address belonging to a built-in email provider. May also be an instance of a 17 | * custom PuppeteerEmailProvider. 18 | * 19 | * @example 20 | * const client = new PuppeteerEmail('outlook') 21 | * const session = await client.signin({ username: 'xxx', password: 'xxx' }) 22 | * const emails = await session.getEmails({ query: 'from:amazon' }) 23 | * await session.close() 24 | * 25 | * @example 26 | * const client = new PuppeteerEmail('test@outlook.com') 27 | * const session = await client.signin({ email: 'test@outlook.com', password: 'xxx' }) 28 | * await session.close() 29 | */ 30 | class PuppeteerEmail { 31 | constructor (provider) { 32 | const p = typeof provider === 'object' 33 | ? provider 34 | : (provider.indexOf('@') >= 0) 35 | ? providers.getProviderByEmail(provider) 36 | : providers.getProviderByName(provider) 37 | ow(p, ow.object.instanceOf(PuppeteerEmailProvider).label('provider')) 38 | 39 | this._provider = p 40 | } 41 | 42 | /** 43 | * Email provider to automate. 44 | * 45 | * @member {PuppeteerEmailProvider} 46 | */ 47 | get provider () { return this._provider } 48 | 49 | /** 50 | * Creates a new email account using the set email provider. 51 | * 52 | * Any user information that isn't provided will be filled in using 53 | * [faker.js](https://github.com/Marak/Faker.js). 54 | * 55 | * Returns an email session with the authenticated puppeteer browser. 56 | * 57 | * @param {object} [user] - User info for the account to create 58 | * @param {string} [user.username] - Username 59 | * @param {string} [user.password] - Password 60 | * @param {string} [user.firstName] - User's given name 61 | * @param {string} [user.lastName] - User's family name 62 | * @param {object} [user.birthday] - User's birthday 63 | * @param {string} [user.birthday.month] - User's birthday month 64 | * @param {string} [user.birthday.day] - User's birthday day 65 | * @param {string} [user.birthday.year] - User's birthday year 66 | * 67 | * @param {object} [opts] - Options 68 | * @param {Object} [opts.browser] - Puppeteer browser instance to use 69 | * @param {Object} [opts.puppeteer] - Puppeteer [launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) 70 | * 71 | * @return {Promise} 72 | */ 73 | async signup (user, opts = { }) { 74 | if (!user) user = { } 75 | 76 | if (!user.username) { 77 | if (user.email) { 78 | ow(user.email, ow.string.nonEmpty.label('user.email')) 79 | user.username = user.email.split('@')[0].trim() 80 | } else { 81 | user.username = faker.internet.userName() + (Math.random() * 10000 | 0) 82 | } 83 | } 84 | 85 | if (!user.password) { 86 | user.password = faker.internet.password() 87 | } 88 | 89 | ow(user.username, ow.string.nonEmpty.label('user.username')) 90 | ow(user.password, ow.string.nonEmpty.label('user.password')) 91 | 92 | user.firstName = user.firstName || faker.name.firstName() 93 | user.lastName = user.lastName || faker.name.lastName() 94 | user.password = user.password || faker.internet.password() 95 | user.birthday = user.birthday || { } 96 | user.birthday.month = user.birthday.month || '' + (random(1, 12) | 0) 97 | user.birthday.day = user.birthday.day || '' + (random(1, 30) | 0) 98 | user.birthday.year = user.birthday.year || '' + (random(1960, 1992) | 0) 99 | 100 | let browser 101 | try { 102 | browser = opts.browser || await puppeteer.launch(opts.puppeteer) 103 | 104 | const session = await this._provider.signup(user, { 105 | browser, 106 | ...opts 107 | }) 108 | 109 | return session 110 | } catch (err) { 111 | if (!opts.browser) await browser.close() 112 | throw err 113 | } 114 | } 115 | 116 | /** 117 | * Signs into an existing email account using the set email provider. 118 | * 119 | * You must specify either `user.username` or `user.email`. 120 | * 121 | * Returns an email session with the authenticated puppeteer browser. 122 | * 123 | * @param {object} user - User info for the account to sign into 124 | * @param {string} [user.username] - Username (implies email) 125 | * @param {string} [user.email] - Email (implies username) 126 | * @param {string} user.password - Password 127 | * 128 | * @param {object} [opts] - Options 129 | * @param {Object} [opts.browser] - Puppeteer browser instance to use 130 | * @param {Object} [opts.puppeteer] - Puppeteer [launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) 131 | * 132 | * @return {Promise} 133 | */ 134 | async signin (user, opts = { }) { 135 | let browser 136 | try { 137 | browser = opts.browser || await puppeteer.launch(opts.puppeteer) 138 | 139 | ow(user, ow.object.plain.nonEmpty.label('user')) 140 | ow(user.password, ow.string.nonEmpty.label('user.password')) 141 | 142 | if (user.username) { 143 | ow(user.username, ow.string.nonEmpty.label('user.username')) 144 | user.email = `${user.username}@${this._provider.name}.com` 145 | ow(user.email, ow.string.nonEmpty.label('user.email')) 146 | } else if (user.email) { 147 | ow(user.email, ow.string.nonEmpty.label('user.email')) 148 | user.username = user.email.split('@')[0].trim() 149 | ow(user.username, ow.string.nonEmpty.label('user.username')) 150 | } else { 151 | throw new Error('missing required parameter "username" or "email"') 152 | } 153 | 154 | const session = await this._provider.signin(user, { 155 | browser, 156 | ...opts 157 | }) 158 | 159 | return session 160 | } catch (err) { 161 | if (!opts.browser) await browser.close() 162 | throw err 163 | } 164 | } 165 | } 166 | 167 | function random (min, max) { 168 | return Math.random() * (max - min) + min 169 | } 170 | 171 | module.exports = PuppeteerEmail 172 | -------------------------------------------------------------------------------- /packages/puppeteer-email/lib/providers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ow = require('ow') 4 | const PuppeteerEmailProviderOutlook = require('puppeteer-email-provider-outlook') 5 | const PuppeteerEmailProviderYahoo = require('puppeteer-email-provider-yahoo') 6 | 7 | exports.providers = { 8 | 'outlook': PuppeteerEmailProviderOutlook, 9 | 'yahoo': PuppeteerEmailProviderYahoo 10 | } 11 | 12 | exports.getProviderByName = (name, opts) => { 13 | ow(name, ow.string.nonEmpty) 14 | const Provider = module.exports.providers[name.toLowerCase()] 15 | 16 | if (!Provider) throw new Error(`unrecognized provider name "${name}"`) 17 | return new Provider(opts) 18 | } 19 | 20 | exports.getProviderByEmail = (email, opts) => { 21 | ow(email, ow.string.nonEmpty) 22 | 23 | let Provider 24 | if (/@outlook\.com/i.test(email)) { 25 | Provider = module.exports.providers.outlook 26 | } else if (/@yahoo\.com/i.test(email)) { 27 | Provider = module.exports.providers.yahoo 28 | } 29 | 30 | if (!Provider) throw new Error(`unrecognized provider email "${email}"`) 31 | return new Provider(opts) 32 | } 33 | -------------------------------------------------------------------------------- /packages/puppeteer-email/lib/providers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('ava') 4 | const PuppeteerEmailProviderOutlook = require('puppeteer-email-provider-outlook') 5 | const PuppeteerEmailProviderYahoo = require('puppeteer-email-provider-yahoo') 6 | 7 | const factory = require('./providers') 8 | 9 | test('outlook', (t) => { 10 | t.is(factory.providers.outlook, PuppeteerEmailProviderOutlook) 11 | t.true(factory.getProviderByName('outlook') instanceof PuppeteerEmailProviderOutlook) 12 | t.true(factory.getProviderByEmail('test1234n@outlook.com') instanceof PuppeteerEmailProviderOutlook) 13 | }) 14 | 15 | test('yahoo', (t) => { 16 | t.is(factory.providers.yahoo, PuppeteerEmailProviderYahoo) 17 | t.true(factory.getProviderByName('yahoo') instanceof PuppeteerEmailProviderYahoo) 18 | t.true(factory.getProviderByEmail('test1234n@yahoo.com') instanceof PuppeteerEmailProviderYahoo) 19 | }) 20 | 21 | test('unrecognized provider', (t) => { 22 | try { 23 | factory.getProviderByName('nala') 24 | t.fail('provider should throw') 25 | } catch (err) { 26 | t.pass() 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /packages/puppeteer-email/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppeteer-email", 3 | "version": "0.1.0", 4 | "description": "Email automation driven by headless chrome.", 5 | "main": "index.js", 6 | "repository": "transitive-bullshit/puppeteer-email", 7 | "author": "Travis Fischer ", 8 | "license": "MIT", 9 | "reveal": true, 10 | "scripts": { 11 | "docs": "update-markdown-jsdoc", 12 | "test": "ava -v && standard" 13 | }, 14 | "engines": { 15 | "node": ">=8" 16 | }, 17 | "keywords": [ 18 | "puppeteer", 19 | "email" 20 | ], 21 | "devDependencies": { 22 | "ava": "^0.25.0", 23 | "standard": "^11.0.0", 24 | "update-markdown-jsdoc": "^1.0.2" 25 | }, 26 | "dependencies": { 27 | "delay": "^3.0.0", 28 | "faker": "^4.1.0", 29 | "ow": "^0.6.0", 30 | "puppeteer": "^1.6.0", 31 | "puppeteer-email-provider": "^0.0.5", 32 | "puppeteer-email-provider-outlook": "^0.1.0", 33 | "puppeteer-email-provider-yahoo": "^0.0.2", 34 | "puppeteer-extra": "^2.0.7", 35 | "puppeteer-extra-plugin-stealth": "^2.0.7" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/puppeteer-email/readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email 2 | 3 | > Email automation drive by headless chrome. 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email.svg)](https://www.npmjs.com/package/puppeteer-email) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | ## Install 8 | 9 | ```bash 10 | npm install --save puppeteer-email 11 | ``` 12 | 13 | ## Usage 14 | 15 | This example signs into an [Outlook](https://outlook.live.com) account, searches for a given query, and then parses and returns all emails returned for that query. 16 | 17 | ```js 18 | const PuppeteerEmail = require('puppeteer-email') 19 | 20 | const client = new PuppeteerEmail('outlook') 21 | 22 | const username = 'xxx' 23 | const password = 'xxx' 24 | 25 | const session = await client.signin({ username, password }) 26 | const emails = await session.getEmails({ query: 'from:github' }) 27 | await session.close() 28 | console.log(emails) 29 | ``` 30 | 31 | Example parsed email output: 32 | 33 | ```js 34 | [ 35 | { 36 | "attachments": [ /* ... */ ], 37 | "headers": { /* ... */ }, 38 | "html": "\n...", 39 | "text": "...", 40 | "textAsHtml": "

...

", 41 | "subject": "Example email subject", 42 | "date": "2018-05-09T14:17:02.000Z", 43 | "to": { 44 | "value": [ 45 | { 46 | "address": "fischxxxx@outlook.com", 47 | "name": "Travis Fischer" 48 | } 49 | ], 50 | "html": "Travis Fischer <fischxxxx@outlook.com>", 51 | "text": "Travis Fischer " 52 | }, 53 | "from": { 54 | "value": [ 55 | { 56 | "address": "noreply@github.com", 57 | "name": "GitHub" 58 | } 59 | ], 60 | "html": "GitHub <noreply@github.com>", 61 | "text": "GitHub " 62 | }, 63 | "messageId": "<01.B3.11399.xxxxxxxx@momentum1-mta1>" 64 | } 65 | ] 66 | ``` 67 | 68 | See [parse-email](https://github.com/transitive-bullshit/parse-email) for details on email model properties. 69 | 70 | ## API 71 | 72 | 73 | 74 | #### Table of Contents 75 | 76 | - [PuppeteerEmail](#puppeteeremail) 77 | - [provider](#provider) 78 | - [signup](#signup) 79 | - [signin](#signin) 80 | 81 | ### [PuppeteerEmail](https://github.com/transitive-bullshit/puppeteer-email/blob/f5e0b1eac196eb5665c862c6e2556b32c3ce6c0e/packages/puppeteer-email/index.js#L28-L131) 82 | 83 | Main entrypoint for authenticating and automating a given email provider. 84 | 85 | Type: `function (provider)` 86 | 87 | - `provider` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) | PuppeteerEmailProvider)** Name of built-in email provider or an 88 | email address belonging to a built-in email provider. May also be an instance of a 89 | custom PuppeteerEmailProvider. 90 | 91 | Examples: 92 | 93 | ```javascript 94 | const client = new PuppeteerEmail('outlook') 95 | const session = await client.signin({ username: 'xxx', password: 'xxx' }) 96 | const emails = await session.getEmails({ query: 'from:amazon' }) 97 | await session.close() 98 | ``` 99 | 100 | ```javascript 101 | const client = new PuppeteerEmail('test@outlook.com') 102 | const session = await client.signin({ email: 'test@outlook.com', password: 'xxx' }) 103 | await session.close() 104 | ``` 105 | 106 | * * * 107 | 108 | #### [provider](https://github.com/transitive-bullshit/puppeteer-email/blob/f5e0b1eac196eb5665c862c6e2556b32c3ce6c0e/packages/puppeteer-email/index.js#L45-L45) 109 | 110 | Email provider to automate. 111 | 112 | Type: PuppeteerEmailProvider 113 | 114 | * * * 115 | 116 | #### [signup](https://github.com/transitive-bullshit/puppeteer-email/blob/f5e0b1eac196eb5665c862c6e2556b32c3ce6c0e/packages/puppeteer-email/index.js#L71-L88) 117 | 118 | Creates a new email account using the set email provider. 119 | 120 | Any user information that isn't provided will be filled in using 121 | [faker.js](https://github.com/Marak/Faker.js). 122 | 123 | Returns an email session with the authenticated puppeteer browser. 124 | 125 | Type: `function (user, opts)` 126 | 127 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** User info for the account to create 128 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Username 129 | - `user.password` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Password 130 | - `user.firstName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** User's given name 131 | - `user.lastName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** User's family name 132 | - `user.birthday` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** User's birthday 133 | - `user.birthday.month` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** User's birthday month 134 | - `user.birthday.day` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** User's birthday day 135 | - `user.birthday.year` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** User's birthday year 136 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`) 137 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Puppeteer browser instance to use 138 | - `opts.puppeteer` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Puppeteer [launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) 139 | 140 | * * * 141 | 142 | #### [signin](https://github.com/transitive-bullshit/puppeteer-email/blob/f5e0b1eac196eb5665c862c6e2556b32c3ce6c0e/packages/puppeteer-email/index.js#L108-L130) 143 | 144 | Signs into an existing email account using the set email provider. 145 | 146 | You must specify either `user.username` or `user.email`. 147 | 148 | Returns an email session with the authenticated puppeteer browser. 149 | 150 | Type: `function (user, opts)` 151 | 152 | - `user` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** User info for the account to sign into 153 | - `user.username` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Username (implies email) 154 | - `user.email` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Email (implies username) 155 | - `user.password` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Password 156 | - `opts` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Options (optional, default `{}`) 157 | - `opts.browser` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Puppeteer browser instance to use 158 | - `opts.puppeteer` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Puppeteer [launch options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) 159 | 160 | * * * 161 | 162 | ## Related 163 | 164 | - [puppeteer-email-cli](../puppeteer-email-cli) - CLI for executing one-off email automation tasks. 165 | - [puppeteer-email-session](../puppeteer-email-session) - Holds state for an authenticated puppeteer email session. 166 | - [parse-email](https://github.com/transitive-bullshit/parse-email) - Parses mime-encoded email messages. 167 | 168 | ## Disclaimer 169 | 170 | Using this software to violate the terms and conditions of any third-party service is strictly against the intent of this software. By using this software, you are acknowledging this fact and absolving the author or any potential liability or wrongdoing it may cause. This software is meant for testing and experimental purposes only, so please act responsibly. 171 | 172 | ## License 173 | 174 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 175 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # puppeteer-email 2 | 3 | > Email automation driven by headless chrome. 4 | 5 | [![NPM](https://img.shields.io/npm/v/puppeteer-email.svg)](https://www.npmjs.com/package/puppeteer-email) [![Build Status](https://travis-ci.com/transitive-bullshit/puppeteer-email.svg?branch=master)](https://travis-ci.com/transitive-bullshit/puppeteer-email) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 6 | 7 | 8 | ## Features 9 | 10 | - automate email account creation 11 | - automate email sending 12 | - automate email fetching 13 | - automate email verification from third-party services 14 | - twitter 15 | - github 16 | - facebook 17 | - etc. 18 | - comes with a [library](packages/puppeteer-email) and [CLI](packages/puppeteer-email-cli) 19 | - uses [puppeteer](https://github.com/GoogleChrome/puppeteer) under the hood 20 | - perfect for bots... 21 | 22 | 23 | ## Status 24 | 25 | This project is an early WIP, but the [CLI](packages/puppeteer-email-cli) currently works to automate [Outlook](https://outlook.live.com). 26 | 27 | 28 | ## Packages 29 | 30 | - [puppeteer-email](packages/puppeteer-email) - Main library entrypoint. 31 | - [puppeteer-email-cli](packages/puppeteer-email-cli) - CLI for executing one-off email automation tasks. 32 | - [puppeteer-email-session](packages/puppeteer-email-session) - Holds state for an authenticated puppeteer email session. 33 | - [puppeteer-email-provider](packages/puppeteer-email-provider) - Abstract base class for puppeteer email providers. 34 | - [puppeteer-email-provider-outlook](packages/puppeteer-email-provider-outlook) - Puppeteer email provider for [Outlook](https://outlook.live.com). 35 | - [puppeteer-email-provider-gmail](packages/puppeteer-email-provider-gmail) - Puppeteer email provider for [Gmail](https://www.google.com/gmail). (TODO) 36 | - [puppeteer-email-provider-yahoo](packages/puppeteer-email-provider-yahoo) - Puppeteer email provider for [Yahoo Mail](https://mail.yahoo.com/). (TODO) 37 | - [parse-email](https://github.com/transitive-bullshit/parse-email) - Parses mime-encoded email messages. 38 | - [sms-number-verifier](https://github.com/transitive-bullshit/sms-number-verifier) - Allows you to spoof SMS number verification. 39 | 40 | 41 | ## Usage 42 | 43 | ### CLI 44 | 45 | ```bash 46 | npm install -g puppeteer-email-cli 47 | ``` 48 | 49 | ```bash 50 | Usage: puppeteer-email [options] [command] 51 | 52 | Options: 53 | 54 | -V, --version output the version number 55 | -u, --username email account username 56 | -p, --password email account password 57 | -P, --provider email provider (default: outlook) 58 | -H, --no-headless (puppeteer) disable headless mode 59 | -s, --slow-mo (puppeteer) slows down operations by the given ms (default: 0) 60 | -h, --help output usage information 61 | 62 | Commands: 63 | 64 | signup [options] 65 | signin 66 | get-emails [options] 67 | ``` 68 | 69 | See the [CLI](packages/puppeteer-email-cli) for more in-depth CLI docs. 70 | 71 | 72 | ### Library 73 | 74 | This example signs into an [Outlook](https://outlook.live.com) account, searches for a given query, and then parses and returns all emails returned for that query. 75 | 76 | ```bash 77 | npm install --save puppeteer-email 78 | ``` 79 | 80 | ```js 81 | const PuppeteerEmail = require('puppeteer-email') 82 | 83 | const client = new PuppeteerEmail('outlook') 84 | 85 | const username = 'XXX' 86 | const password = 'XXX' 87 | 88 | const session = await client.signin({ username, password }) 89 | const emails = await session.getEmails({ query: 'from:github' }) 90 | await session.close() 91 | 92 | console.log(emails) 93 | ``` 94 | 95 | Example parsed email output: 96 | 97 | ```js 98 | [ 99 | { 100 | "attachments": [ /* ... */ ], 101 | "headers": { /* ... */ }, 102 | "html": "\n...", 103 | "text": "...", 104 | "textAsHtml": "

...

", 105 | "subject": "Example email subject", 106 | "date": "2018-05-09T14:17:02.000Z", 107 | "to": { 108 | "value": [ 109 | { 110 | "address": "fischxxxx@outlook.com", 111 | "name": "Travis Fischer" 112 | } 113 | ], 114 | "html": "Travis Fischer <fischxxxx@outlook.com>", 115 | "text": "Travis Fischer " 116 | }, 117 | "from": { 118 | "value": [ 119 | { 120 | "address": "noreply@github.com", 121 | "name": "GitHub" 122 | } 123 | ], 124 | "html": "GitHub <noreply@github.com>", 125 | "text": "GitHub " 126 | }, 127 | "messageId": "<01.B3.11399.xxxxxxxx@momentum1-mta1>" 128 | } 129 | ] 130 | ``` 131 | 132 | See the [library](packages/puppeteer-email) for more in-depth library docs. 133 | 134 | See [parse-email](https://github.com/transitive-bullshit/parse-email) for details on email model properties. 135 | 136 | 137 | ## Related 138 | 139 | - [puppeteer](https://github.com/GoogleChrome/puppeteer) - Headless Chrome Node API used under the hood. 140 | - [parse-email](https://github.com/transitive-bullshit/parse-email) - Parses mime-encoded email messages. 141 | - [sms-number-verifier](https://github.com/transitive-bullshit/sms-number-verifier) - Allows you to spoof SMS number verification. 142 | - [puppeteer-github](https://github.com/transitive-bullshit/puppeteer-github) - GitHub automation driven by headless chrome. 143 | - [awesome-puppeteer](https://github.com/transitive-bullshit/awesome-puppeteer) - A curated list of awesome puppeteer resources. 144 | 145 | 146 | ## Disclaimer 147 | 148 | Using this software to violate the terms and conditions of any third-party service is strictly against the intent of this software. By using this software, you are acknowledging this fact and absolving the author or any potential liability or wrongdoing it may cause. This software is meant for testing and experimental purposes only, so please act responsibly. 149 | 150 | 151 | ## License 152 | 153 | MIT © [Travis Fischer](https://github.com/transitive-bullshit) 154 | 155 | Support my OSS work by following me on twitter twitter 156 | --------------------------------------------------------------------------------