Last active
September 2, 2022 07:13
-
-
Save sitecode/529fe19c2da0b2dc6520dae2eccb0519 to your computer and use it in GitHub Desktop.
Puppeteer node.js script to export juno.com my folders and emails via POP3 to Thunderbird
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//file: package.json dependencies | |
"dependencies": { | |
"fs": "0.0.1-security", | |
"puppeteer": "^1.20.0" | |
//used Thunderbird 78.7.1 | |
} | |
//STEP 1 login to juno.com with this new window | |
//$ node start.js | |
//file: start.js: | |
const puppeteer = require('puppeteer'); | |
let fs = require('fs'); | |
(async () => { | |
//chrome://version/ | |
const browser = await puppeteer.launch({ | |
defaultViewport: null, | |
headless: false, // Puppeteer is 'headless' by default. | |
//ignoreDefaultArgs: ["--disable-extensions","--enable-automation"],//uncomment for ublock origin | |
args: [ | |
`--start-maximized`, | |
//`--load-extension=../Extensions/cjpalhdlnbpafiamejdnhcphjbkeiagm/1.33.2_0/` //ublock origin | |
] | |
}); | |
const wsEndpoint = browser.wsEndpoint() //easier way to get endpoint | |
const wsfile = "currentbrowser.wsurl" | |
fs.writeFile(wsfile, wsEndpoint, function(err) { | |
if(err) { | |
return console.log(err); | |
} | |
}); | |
const page = await browser.newPage() | |
let pageUrl = 'https://www.juno.com' | |
//load cookies | |
try{ | |
const cookiesString = fs.readFileSync('./cookies.json') | |
if (cookiestring) { // load webmail previous session | |
const cookies = JSON.parse(cookiesString) | |
await page.setCookie(...cookies) | |
pageUrl = 'https://webmaila.juno.com/webmail/new/' | |
} | |
} catch (e) {} | |
await page.goto(pageUrl, { | |
waitUntil: 'networkidle0' // 'networkidle0' is very useful for SPAs. | |
}) | |
.catch((err) => console.log(err)) | |
browser.on('disconnected', () => { | |
//const cookies = await page.cookies(); | |
//fs.writeFileSync('./cookies.json', JSON.stringify(cookies, null, 2)); | |
fs.unlinkSync(wsfile) | |
console.log('starter is done') | |
}) | |
})(); | |
//STEP 2 edit the file to go through the 4 states and run the correcsponding code in Thunderbird console when the time comes, start the worker after each stage is completed and your ready to go to the next | |
//$ node work.js | |
//file: work.js | |
const puppeteer = require('puppeteer'); | |
let fs = require('fs'); | |
//https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pageurl | |
(async () => { | |
//connect to the puppeteer instance started in start.js (this script will run multiple times) | |
let wsChromeEndpointUrl; | |
try{ | |
wsChromeEndpointUrl = fs.readFileSync('./currentbrowser.wsurl', 'utf8') | |
console.log(`Found ws url ${wsChromeEndpointUrl}`) | |
} catch (err) { | |
//console.error(err) | |
} | |
if (!wsChromeEndpointUrl) { | |
console.log('`node start.js` first') | |
return | |
} | |
const browser = await puppeteer.connect({ | |
browserWSEndpoint: wsChromeEndpointUrl | |
}).catch(err => console.log(err.message) || process.exit()); | |
//make sure to always grab the tab we want!!! will cause unexpected javascript evaluate errors otherwise :) | |
const pages = await browser.pages().catch(err => console.log(err.message)); | |
const [page] = pages.filter(p => p.url().indexOf('juno.com')>-1) | |
if (!page) { | |
console.log("NUMBER TABS:", pages.length); | |
console.log('juno tab not found') | |
process.exit() | |
} | |
delete pages | |
await page.bringToFront() | |
class Juno { | |
state = {} | |
constructor() { | |
console.log('new juno') | |
this.loadState() | |
} | |
loadState() { | |
try { | |
let state = fs.readFileSync('./state.json', 'utf8') | |
this.state = JSON.parse(state) | |
//console.log(this.state) | |
} catch (e) {} | |
} | |
saveState() { | |
try { | |
fs.writeFileSync('./state.json', JSON.stringify(this.state)); | |
} catch (e) {} | |
} | |
//low level | |
//TODO getFolders | |
async getMyFolders() { | |
return await page.evaluate(() => { | |
let list = []; | |
$('#fBoxMyList > div.folder > div') | |
.each(function(i) { | |
if ($(this).data('info')!='__tempFolder__') { //skip temp folder name | |
list[i] = {index: i, numofmsgs: $(this).data('numofmsgs'), name: $(this).data('info')} | |
} | |
}) | |
return list; | |
}) | |
.catch(err => console.log(err)) | |
} | |
async selectFolder(name) { | |
await page.evaluate(name => $('#fBoxSys > div[data-info="' + name + '"] a.folderLink').eq(0).trigger('click'), name) | |
.catch(err => console.log(err)) | |
await this.waitForLoadingToFinish() | |
} | |
async selectMyFolder(name) { | |
console.log('Select ' + this.cleanName(name)) | |
await page.evaluate(name => $('#fBoxMyList > div.folder > div[data-info="' + name + '"] a.folderLink').eq(0).trigger('click'), name) | |
.catch(err => console.log(err)) | |
await page.waitFor(200) //slow things down a little | |
await this.waitForLoadingToFinish() | |
console.log('Selected ' + this.cleanName(name)) | |
} | |
async waitForLoadingToFinish() { | |
await page.waitFor(400) //slow things down a little | |
await page.waitForSelector('#loadingMsgDiv', {visible:false}) | |
.catch(err => console.log(err)) | |
} | |
async pageSelectAll() { | |
await page.click('#selectAll_top') | |
.catch((err) => console.log(err)) | |
await page.waitFor(40) | |
} | |
async getPageItemCount() { | |
return await page.evaluate(() => $('#gridContent > div').length) | |
.catch((err) => console.log(err)) | |
} | |
async getPageItemSelectedCount() { | |
return await page.evaluate(() => $('#gridContent > div input:checkbox:checked').length) | |
.catch((err) => console.log(err)) | |
} | |
async clickMoveTo(name) { | |
var cleanname = this.cleanName(name) | |
console.log('clickMoveTo ' + cleanname) | |
page.click('#move_top') | |
.catch((err) => console.log(err)) | |
await page.waitForSelector('#cMenu', {visible:true}) | |
.catch((err) => console.log(err)) | |
await page.evaluate(cleanname => $('#cMenu > div').filter(function() { return $(this).text() === cleanname }).trigger('click'), cleanname) | |
.catch((err) => console.log(err)) | |
await this.waitForLoadingToFinish() | |
await page.waitFor(80) | |
console.log('clickMoveTo ' + cleanname + ' is Done') | |
} | |
cleanName(name) { | |
//remove encoded space | |
return (name || '').replace(/%20/g,' ') | |
} | |
async savePageCookies() { //doesn't work to hold a logged in session | |
//https://stackoverflow.com/questions/56514877/how-to-save-cookies-and-load-it-in-another-puppeteer-session | |
const cookies = await page.cookies(); | |
fs.writeFileSync('./cookies.json', JSON.stringify(cookies, null, 2)); | |
} | |
async waitForConfirm(message) { | |
let go = await page.evaluate(message => confirm(message), message) | |
if (!go) { | |
console.log('canceled by user') | |
process.exit() | |
} | |
console.log('continued by user') | |
return go | |
} | |
//high level | |
async moveAllMessagesToFolder(toName) { | |
let count = await this.getPageItemCount() | |
console.log('starting with: ' + count); | |
while (count > 0) { | |
await this.pageSelectAll() | |
await this.clickMoveTo(toName) | |
await page.waitFor(500) | |
count = await this.getPageItemCount() | |
console.log('continuing with ' + count); | |
//count = 0; | |
if (count > 0) { | |
await page.waitFor(3000) | |
} | |
} | |
} | |
async moveMyFolderToInbox(fromName) { | |
await this.selectMyFolder(fromName) | |
await this.moveAllMessagesToFolder('Inbox') | |
} | |
async moveInboxToFolder(toName) { | |
await this.selectFolder('Inbox') | |
await this.moveAllMessagesToFolder(toName) | |
} | |
//state machine | |
async main() { | |
switch (this.state.current) { | |
default: | |
case 'load-my-folders': | |
var list = await juno.getMyFolders() | |
//juno.state.list = {my: list} | |
juno.state.lists={myfolders:list} | |
juno.saveState() | |
//generate console code to create all the folders in thunderbird | |
thunderbird.createSubFolders() | |
break; | |
case 'empty-inbox': | |
await this.moveInboxToFolder('old_Inbox') | |
break; | |
case 'unempty-inbox': | |
await this.moveMyFolderToInbox('old_Inbox') | |
break; | |
case 'transfer-my-folders': | |
let startFolder = false | |
if (this.state.last && this.state.last.folder) { | |
startFolder = this.state.last.folder | |
console.log('found startFolder' + startFolder + ' Skiiping till we get to that folder') | |
} | |
//startFolder = 'NCOSE' //random startFolder, fix an error | |
for (let i = 0; i < juno.state.lists.myfolders.length; i++) { | |
let myfolder = juno.state.lists.myfolders[i] | |
//skip this special folder, it should never be set as startFolder since skipping here | |
if (myfolder.name == 'old_Inbox') { | |
console.log('SKIIPING ' + myfolder.name) | |
continue; | |
} | |
//skip to where last left off | |
if (startFolder && startFolder != myfolder.name) { | |
console.log('skiiping ' + myfolder.name) | |
continue; | |
} | |
console.log(myfolder) | |
startFolder = false | |
//****TODO verify that inbox is empty before starting a new folder transfer from step 1 | |
//if (this.state.last && this.state.last.step == 1) { | |
//} | |
this.state.last = {folder:myfolder.name,item:myfolder,step:1} | |
this.saveState() | |
await this.moveMyFolderToInbox(myfolder.name) | |
//TODO check total num with expected num | |
console.log('check total is same as ' + myfolder.numofmsgs) | |
this.state.last.step = 2 | |
//this.saveState() | |
//---- | |
//await thunderbird.getMessages() | |
//await thunderbird.moveToFolder(myfolder.name) | |
//move more of the interaction with the webpage, don't have to switch to console!! | |
await juno.waitForConfirm('Run: getJuno(); in the Thunderbird console. Are you ready to continue with ' + this.cleanName(myfolder.name) + '? expect ' + myfolder.numofmsgs) | |
await thunderbird.quick(myfolder.name) | |
//---- | |
this.state.last.step = 3 | |
//this.saveState() | |
await this.moveInboxToFolder(myfolder.name) | |
//await juno.waitForConfirm('Are you ready to continue with next') | |
} | |
delete this.state.last | |
this.saveState() | |
break; | |
} | |
} | |
} | |
class Thunderbird { | |
constructor() { | |
console.log('new thunderbird') | |
} | |
getMessages() { | |
console.log("RUN IN THUNDERBIRD--------------------") | |
console.log("GetMessagesForInboxOnServer( accountManager.allServers.filter(a=>a.prettyName.indexOf('juno.com')>-1)[0] )") | |
console.log("-----------------------") | |
} | |
async quick(name) { | |
//NOTE this functions need to be defined in Thunderbird console beforhand | |
console.log("Run the following in Thunderbird to setup (just once): \ | |
let junoInboxFolder = accountManager.allServers.filter(a=>a.prettyName.indexOf('juno.com')>-1)[0]\ | |
junoInboxFolder = MailUtils.getInboxFolder(junoInboxFolder)\ | |
let destInboxFolder = accountManager.allServers.filter(a=>a.prettyName.indexOf('[DESTINATION.COM--TODO change to domain name of Thunderbird account that you created the my folders on]')>-1)[0]\ | |
destInboxFolder = MailUtils.getInboxFolder(destInboxFolder)\ | |
async function getJuno() {\ | |
gFolderTreeController.selectFolder(junoInboxFolder);\ | |
return await GetMessagesForInboxOnServer( accountManager.allServers.filter( a => a.prettyName.indexOf( 'juno.com') > -1)[0])\ | |
}\ | |
async function moveToDestFolder(name) {\ | |
gFolderTreeController.selectFolder(junoInboxFolder); goDoCommand('cmd_selectAll');MsgMarkMsgAsRead(true);\ | |
let folder = destInboxFolder.descendants.filter(f => f.prettyName.match(new RegExp('^' + name + '$')))[0]; MsgMoveMessage(folder);\ | |
gFolderTreeController.selectFolder(folder);\ | |
}\ | |
"; | |
await juno.waitForConfirm("moveToDestFolder('"+ juno.cleanName(name) + "');" ) | |
} | |
createSubFolders() { | |
console.log("RUN IN THUNDERBIRD--------------------") | |
console.log("let junoInboxFolder = MailUtils.getInboxFolder(accountManager.allServers.filter(a=>a.prettyName.indexOf('juno.com')>-1)[0])") | |
let commands = juno.state.lists.myfolders.map(a => "junoInboxFolder.createSubfolder('" + juno.cleanName(a.name) + "', msgWindow)") | |
//console.log(commands.join(';')) | |
commands.map(f => console.log(f+";")) | |
console.log("-----------------------") | |
} | |
async moveToFolder(name) { | |
console.log("RUN IN THUNDERBIRD--------------------") | |
console.log("let junoInboxFolder = accountManager.allServers.filter(a=>a.prettyName.indexOf('juno.com')>-1)[0]") | |
console.log("junoInboxFolder = MailUtils.getInboxFolder(junoInboxFolder)") | |
console.log("folder = junoInboxFolder.descendants.filter(f => f.prettyName.match(/^" + juno.cleanName(name) + "$/))[0]") | |
console.log("goDoCommand('cmd_selectAll');MsgMarkMsgAsRead(true);MsgMoveMessage(folder);") | |
console.log("-----------------------") | |
await page.evaluate(message => alert(message), "gFolderTreeController.selectFolder(junoInboxFolder);goDoCommand('cmd_selectAll');MsgMarkMsgAsRead(true);MsgMoveMessage(destInboxFolder.descendants.filter(f => f.prettyName.match(/^" + juno.cleanName(name) + "$/))[0]);") | |
} | |
selectFolder(name) { | |
console.log("RUN IN THUNDERBIRD--------------------") | |
console.log("folder = junoInboxFolder.descendants.filter(f => f.prettyName.match(/^" + juno.cleanName(name) + "$/))[0]") | |
console.log("gFolderTreeController.selectFolder(folder)") | |
console.log("-----------------------") | |
} | |
} | |
var juno = new Juno(); | |
var thunderbird = new Thunderbird(); | |
//console.log(juno.state.lists.myfolders[0].name) | |
//juno.selectMyFolder(juno.state.lists.myfolders[0].name) | |
//console.log(await juno.getPageItemCount()) | |
//console.log(await juno.getPageItemSelectedCount()) | |
//savePageCookies() | |
//thunderbird.createSubFolders() | |
//thunderbird.moveToFolder(juno.state.lists.myfolders[20].name) | |
//thunderbird.selectFolder(juno.state.lists.myfolders[20].name) | |
//await juno.savePageCookies() | |
//START hERE, uncomment and run, one by one, work through these 4 states to transfer email | |
juno.state.current = 'load-my-folders' | |
//juno.state.current = 'empty-inbox' //use inbox to transfer each folder contents one at a time, since POP3 can only read from Inbox | |
//juno.state.current = 'transfer-my-folders' //Sent and Draft have to be done separately, BEWARE once emails are removed out of Sent or Draft folders and into Inbox, they cannot be put back in original folder. | |
//juno.state.current = 'unempty-inbox' | |
await juno.main() | |
console.log('done') | |
//disconnect so script can close and save resources | |
browser.disconnect(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment