ao-to-mail

arthur online to email interface

目前为 2020-02-11 提交的版本。查看 最新版本

// ==UserScript==
// @name         ao-to-mail
// @description  arthur online to email interface
// @version      1.0.1
// @author       yuze
// @namespace    yuze
// @include      https://system.arthuronline.co.uk/*
// @include      https://mail.google.com/*
// @include      https://mail.one.com/*
// @connect      arthuronline.co.uk
// @connect      ea-api.yuze.now.sh
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.xmlHttpRequest
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
// ==/UserScript==

/* eslint-env jquery, greasemonkey */

let target = {}

window.addEventListener('load', function() {
    if (window.location.href.includes('system.arthuronline.co.uk')){
        arthur()
    }
    if (window.location.href.includes('mail.google.com')){
        init_gmail()
    }
    if (window.location.href.includes('mail.one.com')){
        init_webmail()
    }}
)

function init_gmail() {
    console.log('init_gmail')

    target.email = () => $('.wO.nr textarea')
    target.subject = () => $('input[name="subjectbox"]')
    target.body = () => $('div[aria-label="Message Body"]')

    mail()
}

function init_webmail() {
    console.log('init_webmail')

    target.email = () => $('#to')
    target.subject = () => $('in-place-editor #subject')
    target.body = () => $('.rte-frame iframe').contents().find('body')

    mail()
}

async function arthur() {

    let id = $('.text-logo span').text().toLowerCase()

    init()
    function init() {

        appendCSS()
        function appendCSS() {

            const magicButton = `.magicBtn {
                                    margin-top: 8px;
                                    padding: 5px;
                                    transition: 250ms;
                                    position: absolute;
                                    box-sizing: border-box;
                                    background: #e91e63;
                                    height: 48px;
                                    width: 48px;
                                    border-radius: 6px;
                                    color: white;
                                    user-select: none;
                                }
                                .magicBtn:hover {
                                    filter: brightness(125%);
                                }
                                .magicBtn:active {
                                    filter: brightness(75%);
                                }
                                .magicAnim {
                                    transition: transform 0.6s cubic-bezier(0.19, 1, 0.22, 1);
                                    padding: 5px 5px 0 5px;
                                }
                                .magicFlip {
                                    transform: rotateY(180deg);
                                }
                                .magicToast {
                                    padding: 4px;
                                    font-size: 14px;
                                    position: absolute;
                                    color: #e91e63;
                                    font-weight: bold;
                                    top: 50px;
                                    left: 0;
                                }
                                .magicSnail{
                                    font-size: 1em;
                                    display: inline-block;
                                    animation: snail 4.75s  infinite;
                                    animation-timing-function: linear;
                                }
                                @-webkit-keyframes snail {
                                    0% {
                                        -webkit-transform: translateX(0) rotateY(90deg)
                                    }
                                    5% {
                                        -webkit-transform: translateX(0) rotateY(0deg)
                                    }
                                    45% {
                                        -webkit-transform: translateX(100px) rotateY(0deg)
                                    }
                                    55% {
                                        -webkit-transform: translateX(100px) rotateY(180deg)
                                    }
                                    95% {
                                        -webkit-transform: translateX(0) rotateY(180deg)
                                    }
                                    100% {
                                    -webkit-transform: translateX(0) rotateY(90deg)
                                    }
                                }`
        
            const style = ` <style>
                                ${magicButton}
                            </style>`
            
            $('head').append(style)                
        }

        tokenCheck()
        async function tokenCheck() {
            let token = await GM.getValue(`${id}-ao-token`)
            if (!token) {
                console.log('No token exists, getting token from DB')
                tokenProvider()
            }
        }

        checkLocation()

    }
    
    let data = {}
    let response;
    let savedLoc;

    $('body').on('click', checkLocation)
    $(window).on('focus', checkLocation)

    async function checkLocation() {
        await wait(100)
        if( /tenancies\/view\/\d{6}/.test(window.location.href) ) {
            if ( !($('.magicBtn').length) ) {
                new Promise( function(resolve) {
                    waitForExistance('.identifier-icon', resolve)
                }).then( () => {
                    magicBtn()
                } )
            }
        } else {
            $('.magicBtn').remove()
        }
    }

    function magicBtn() {
        append()
        function append() {
            const html =    `<div class="magicBtn"><div class="magicAnim"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                                <path fill="currentColor" d="M224 96l16-32 32-16-32-16-16-32-16 32-32 16 32 16 16 32zM80 160l26.66-53.33L160 80l-53.34-26.67L80 0 53.34 53.33 0 80l53.34 26.67L80 160zm352 128l-26.66 53.33L352 368l53.34 26.67L432 448l26.66-53.33L512 368l-53.34-26.67L432 288zm70.62-193.77L417.77 9.38C411.53 3.12 403.34 0 395.15 0c-8.19 0-16.38 3.12-22.63 9.38L9.38 372.52c-12.5 12.5-12.5 32.76 0 45.25l84.85 84.85c6.25 6.25 14.44 9.37 22.62 9.37 8.19 0 16.38-3.12 22.63-9.37l363.14-363.15c12.5-12.48 12.5-32.75 0-45.24zM359.45 203.46l-50.91-50.91 86.6-86.6 50.91 50.91-86.6 86.6z"></path>
                                </svg></div></div>`    
                                
            $('.identifier-icon').append(html)
            $('.magicAnim').on('click', anim)
            $('.magicBtn').on('click', getData) 
        }

        function anim() {
            $(this).toggleClass('magicFlip')
            setTimeout( () => $(this).toggleClass('magicFlip'), 512 )
        }
    }

    async function getData() {
        disableAccess('<b style="font-size: 1.5em;">Please wait <div class="magicSnail">🐌</div>&emsp;🥬</b><br>Gathering leafy greens...')
    
        // wipe data on entry
        GM.setValue('ao-data', '')

        savedLoc = window.location.href;

        let extractId = savedLoc.match(/(?!tenancies\/view\/)\d{6}/)[0] 
        let url = `https://api.arthuronline.co.uk/v2/tenancies/${extractId}`

        let token = await GM.getValue(`${id}-ao-token`)
        let xEntityId = await GM.getValue(`${id}-ao-xEntityId`)

        GM.xmlHttpRequest({
            method: "GET",
            url: url,
            headers: {
                'Authorization': `Bearer ${token}`,
                'X-EntityID': `${xEntityId}`,
            },
            onload: function(xhr) {
             response = JSON.parse(xhr.responseText);
             if ( response.error ) {
                console.log('error')
                tokenProvider(true)
             } else {
                console.log('success')
                assignData(response.data)
             }
           }
        })
    }

    async function assignData(response) {

        data['id']          = id
        data['ref']         = response.ref
        data['startDate']   = response.start_date
        data['property']    = $('.identifier-detail .sub-title a')[0].innerHTML.split(',')[0].replace(' Room', ', Room')
        data['names']       = []
        data['emails']      = []
        data['total']       = $('.overdue .number').text()

        for (let i = 0; i < response.tenants.length; i++ ) {
            data['names']  .push((response.tenants[i].first_name + ' ' + response.tenants[i].last_name).replace(/ {2}/g, ' '))
            data['emails'] .push(response.tenants[i].email)
        }
        let mode = GM.getValue('mode')

        if ( await mode == 'overdue') {
            getArrears() 
        } else {
            saveToLocalStorage()
        }
    }

    function getArrears() {
        $('.nav.nav-tabs [href^="#tab-transactions"]')[0].click()

        new Promise( function(resolve) {
            waitForExistance('.transactions tbody', resolve)
        }).then(() => {
            $('#genOverdueBtn')[0].click()
            data['arrears'] =  $('#genOverdueText')[0].value.split('\n').join('<br>')
            $('#genOverdueText').css('display', 'none')
            saveToLocalStorage()
            returnToSavedLocation()
        })
    }

    function returnToSavedLocation() {
        if( /tenancies\/view\/\d{6}\/ident:Datatable.{5}$/.test(savedLoc) ){
            setTimeout( async () => {
                $(`.nav.nav-tabs [href^="#tab-summary"]`)[0].click()
                await wait(512)
                checkLocation()
            }, 1)
        } else if ( /tenancies\/view\/\d{6}\/ident:Datatable.{5}#tab-.+-/.test(savedLoc) ) {
            let match = savedLoc.match(/tab-.+(?=-)/)[0]
            setTimeout( async () => {
                $(`.nav.nav-tabs [href^="#${match}"]`)[0].click()
                await wait(512)
                checkLocation()
            }, 1)
        }
    }

    function saveToLocalStorage() {        
        GM.setValue('ao-data', JSON.stringify(data))

        data = {}
        disableAccess('', true)
    }

    function disableAccess(desc, remove) {

        if ( remove ) {
            removeDisableAccess()
            return;
        }
        if ( $('#disableAccess').length ) return;
    
        $('body').append(`  <div id="disableAccess">
                                <div id="disableDesc">${desc}</div>
                            </div>`)
    
        $('#disableAccess').hide().fadeIn(618)
    
        setTimeout( () => {
            if ( $('#disableAccess').length ) {
                $('#disableDesc').append('<br><div class="btn" style="transform: scale(1.75,1.75); margin-top: 48px" id="disableExit">This is taking too long! Get me out of here. 😠</div>')
                $('#disableExit').hide().fadeIn(1024)
                $('#disableExit').on('click', function() {
                    removeDisableAccess()
                })
            }
        }, 5500)
    
        $('#disableClickCover').on('mousedown keydown', disableAccess)
        function disableAccess(e){
            e.preventDefault()
            return;
        }
    
        function removeDisableAccess() {
            $('#disableAccess').off('mousedown keydown scroll', disableAccess)
            $('#disableAccess').fadeOut(314, function() {
                $('#disableAccess').remove()
            })
        }
    
    }

    function tokenProvider(retry) {

        GM.xmlHttpRequest({
            method: "POST",
            url: 'https://ea-api.yuze.now.sh/api/refresh-token',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            data: `for=${id}_ao`,
            onload: function(res) {
                let json = (JSON.parse(res.response))
                GM.setValue(`${id}-ao-token`, json.token)
                GM.setValue(`${id}-ao-xEntityId`, json.xEntityId)
                console.log(json.note)

                if (retry) {
                    console.log('retrying')
                    getData()
                }
           }
        })

    }
}

async function mail() {

    $(window).on('focus', processData)

    async function processData() {

        let data = await GM.getValue('ao-data')
        let mode = await GM.getValue('mode')

        let email = target.email()
        let subject = target.subject()
        let body = target.body()
        
        if ( !data ) return;

        data = JSON.parse(data)        

        // EMAIL
        email.val( data['emails'].join(', ') )
        email.trigger('change')

        // SUBJECT
        if ( mode == 'overdue' ) {
            subject.val( subject.val() + ` (${data['property']})` )
        } else {
            subject.val( data['id'][0].toUpperCase() + data['id'].slice(1) + ' - ' + subject.val() + ` (${data['property']})` )
        }
        subject.trigger('change')

        // BODY --> name
        let firstNames = []
        for (let i = 0; i < data['names'].length; i++) {
            firstNames.push( data['names'][i].split(' ')[0] )
        }
        if( data['names'].length > 2) {
            firstNames = firstNames.join(', ').replace(/(,)(?!.+\1)/, ' and')
        } else {
            firstNames = firstNames.join(' and ')
        }

        // BODY --> due-date
        let dueDate = ''
        if ( await mode == 'overdue' ) {
            dueDate = data['arrears'].split('<br>')

            for (let i = 0; i <= dueDate.length; i++) {
                
                if ( !dueDate[i] && i === 0 ) {
                    break;
                } else if ( i == dueDate.length - 1 ) {
                    try {
                        dueDate = dueDate[0].match(/\((\d.+)-/)[1].trim()
                        break;
                    } catch(err) {
                        dueDate = dueDate[0].match(/\((\d.+)\)/)[1].trim()
                        break;
                    }

                } else if ( dueDate[i].includes('Outstanding') ) {
                    dueDate = dueDate[i].match(/\((.+)-/)[1].trim()
                    break;
                }
            }
            GM.setValue('mode', '')
        }

        body.html(
            body.html()
                .replace('{name}', firstNames)
                .replace('{ref}', data['ref'])
                .replace('{property}', data['property'])
                .replace('{arrears}',  data['arrears'])
                .replace('{due-date}', dueDate)
                .replace('{total}', data['total'])
        )

        if ( data['arrears'] ) {
            body.html( body.html().replace(/(Total to be paid: £\d{1,4}\.\d{2})/, '<b><u>$1</u></b>') )
        }

        // CONCLUDE
        GM.setValue('ao-data', '')
    }

    btnListeners()
    async function btnListeners() {

        await wait(1024)

        $('#template-overdue').on('click', function() {
            GM.setValue('mode', 'overdue')
        })

    }

}

async function waitForExistance(elem, resolve) {

    if ( $(elem).length ) {
        resolve()
    }

    let interval;
    if ( !$(elem).length ) {
        interval = setInterval( () => checkExistance(), 150)
    }
    function checkExistance() {
        if ( $(elem).length ) {
            clearInterval(interval)
            resolve()
        }
    }
}

async function wait(ms) {
    return new Promise(resolve => {
        setTimeout( () => { resolve() }, ms);
    });
}