Skip to content

Instantly share code, notes, and snippets.

@sitecode
Last active September 2, 2022 07:13
Show Gist options
  • Save sitecode/529fe19c2da0b2dc6520dae2eccb0519 to your computer and use it in GitHub Desktop.
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
//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