パルカワ2

最近はFlutterをやっています

Moneytreeで未対応のTHEOを勝手に対応する

Moneytreeは現金とかタンス預金用に手動入力のみの「その他の口座」項目がある。
それは手動でしか入力できないんだけど、チョットプログラム書けば未対応のTHEOも自動で入力できるじゃんと思ったのでやってみた。puppeteer便利すぎ

流れ

  • THEOにログイン
  • THEOで表示されてる額を取得
  • Moneytreeにログイン
  • つくっておいた口座に額を追記

これをなんらかの方法で定期実行すればよい

実装

こういう感じ

import * as puppeteer from 'puppeteer';
import {Page} from "puppeteer";

class Theo {
    public static get username(): string { return ""; }
    public static get password(): string { return ""; }
}

class Moneytree {
    public static get username(): string { return ""; }
    public static get password(): string { return ""; }
}


class LoadTheoMarketPrice {
    async execute(page: Page): Promise<string> {
        await page.goto('https://app.theo.blue/account/login');

        await page.type("input[name=email]", Theo.username);
        await page.type("input[name=password]", Theo.password);
        await page.tap("#app > section > section > form > div > button");
        await page.waitForNavigation();

        return await page.$eval("span.number-value", (span) => span.textContent.trim());
    }
}

class SaveTheoMarketPrice {
    async execute(page: Page, price: string) {
        await page.goto('https://app.getmoneytree.com/login');

        await page.type("input[name=email]", Moneytree.username);
        await page.type("input[name=password]", Moneytree.password);
        await page.tap("#app > div > div > div.onboardingPageBody > form > div > button");
        await page.waitForNavigation();

        // 口座残高を表示
        await page.goto("https://app.getmoneytree.com/app/vault");
        await page.waitFor(3000);

        // THEOの項目を表示/人によって違うよ
        await page.tap("div > ul > li:nth-child(4) > div.row.institution-header > a");
        await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.left.sidebar > div:nth-child(2) > left-column-body > mt-credentials > div > ul > li:nth-child(4) > div:nth-child(2) > ul > li.ng-scope > div > mt-credential > div > mt-credential-template > div > div.credential-accounts > div.credential-body > mt-list-secondary > div > ul > li:nth-child(2)");
        await page.waitFor(3000);

        // 編集ダイアログを表示
        await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > a");

        // 保存されてる内容を消す
        await page.$eval("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-right > input",
            value => (value as HTMLInputElement).value = "");

        // priceを入力
        await page.type("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-right > input",
            price);

        // + にする
        await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(5) > mt-amount-input > div > div.pull-left.ng-scope > div > i");

        // 更新する
        await page.tap("#mt-webapp > section > mt-webapp-layout > div > div.content.ng-scope > mt-accounts > div > mt-accounts-personal > div > mt-link-vault > div > mt-two-column-layout > div > div.column.center.content > mt-awesome-layout > div > div > div:nth-child(1) > awesome-body > div > right-column-body > div:nth-child(2) > div.ng-scope > mt-manual-account-balances > div > div.current-account-balance-header > span:nth-child(2) > mt-manual-account-balances-action > div > div:nth-child(7) > a");
        await page.waitFor(3000);
    }
}

exports.saveTheoMarketPrice = () => {
    (async () => {
        const browser = await puppeteer.launch();
        const theo = await browser.newPage();
        let price = await new LoadTheoMarketPrice().execute(theo);
        price = price.replace(",", "");

        const moneytree = await browser.newPage();
        await new SaveTheoMarketPrice().execute(moneytree, price);

        await browser.close();
    })();
};

一応書いておくとこのコードによってなにか起きても責任は取りません。