您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
arthur online to email interface
// ==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); }); }