ao-to-mail

arthur online to email interface

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         ao-to-mail
// @description  arthur online to email interface
// @version      1.0.5
// @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()
    if (!id) return;

    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.replace(' - ', ' ').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

        // does email field contain content already?
        let replyExisting = $('.oL.aDm span').text()
        if (replyExisting.includes('barrons') || replyExisting.includes('mayfields') || replyExisting === '') {

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

        }

        // SUBJECT
        subject.val(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.?\d+\.\d{1,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);
    });
}