ao-to-mail

arthur online to email interface

当前为 2020-02-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         ao-to-mail
// @description  arthur online to email interface
// @version      1.0.0
// @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'])
        )

        // 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);
    });
}