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