feat(webscrape-bank): added code to webscrape my bank, api is next
This commit is contained in:
commit
b8552eaefa
6 changed files with 3974 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# PERSONAL INFO
|
||||||
|
.env
|
||||||
|
**/My-Finances*
|
||||||
|
notes
|
||||||
|
**/*qfx
|
||||||
|
|
||||||
|
# Others
|
||||||
|
node_modules/
|
||||||
32
actual-api/src/index.js
Normal file
32
actual-api/src/index.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
require("dotenv").config();
|
||||||
|
let api = require("@actual-app/api");
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const ACTUAL_URL = process.env.ACTUAL_URL
|
||||||
|
const ACTUAL_PASSWORD = process.env.ACTUAL_PASSWORD
|
||||||
|
const ACTUAL_SYNC_ID = process.env.ACTUAL_SYNC_ID
|
||||||
|
const ACTUAL_CHECKING_ID = process.env.ACTUAL_CHECKING_ID
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
await api.init({
|
||||||
|
dataDir: "./",
|
||||||
|
serverURL: ACTUAL_URL,
|
||||||
|
password: ACTUAL_PASSWORD,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Downloading the Budget Data")
|
||||||
|
await api.downloadBudget(ACTUAL_SYNC_ID);
|
||||||
|
|
||||||
|
console.log("Getting Account Info")
|
||||||
|
let accountInfo = await api.getAccounts()
|
||||||
|
console.log(accountInfo)
|
||||||
|
|
||||||
|
function inspectBankFile() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//await api.shutdown();
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
||||||
3817
package-lock.json
generated
Normal file
3817
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
18
package.json
Normal file
18
package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "transaction-sync",
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"actual": "node actual-api/src/index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actual-app/api": "^26.1.0",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
|
"dotenv": "^17.2.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-plugin-import": "^2.32.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
99
webscrape-bank/src/extract.py
Normal file
99
webscrape-bank/src/extract.py
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
from playwright.sync_api import Playwright, sync_playwright, Page
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
BANK_USER = os.getenv("BANK_USER")
|
||||||
|
BANK_PASS = os.getenv("BANK_PASS")
|
||||||
|
LOGIN_LINK = os.getenv("LOGIN_LINK")
|
||||||
|
ACCOUNT_LINK = os.getenv("ACCOUNT_LINK")
|
||||||
|
|
||||||
|
def main(playwright: Playwright) -> None:
|
||||||
|
browser = playwright.chromium.launch(headless=False)
|
||||||
|
context = browser.new_context()
|
||||||
|
|
||||||
|
page = context.new_page()
|
||||||
|
page.goto(LOGIN_LINK)
|
||||||
|
page.locator("#username").click()
|
||||||
|
page.locator("#username").fill(BANK_USER)
|
||||||
|
page.locator("#password").click()
|
||||||
|
page.locator("#password").fill(BANK_PASS)
|
||||||
|
page.get_by_role("button", name="Login", exact=True).click()
|
||||||
|
|
||||||
|
# Wait a little bit
|
||||||
|
page.wait_for_load_state()
|
||||||
|
page.get_by_role("button", name="Text").click()
|
||||||
|
page.get_by_test_id("text-field").click()
|
||||||
|
|
||||||
|
# Bank prompts 2FA (hopefully only once)
|
||||||
|
two_fac = input("2FA Code: ")
|
||||||
|
|
||||||
|
page.get_by_test_id("text-field").fill(two_fac)
|
||||||
|
page.get_by_test_id("private-button").click()
|
||||||
|
|
||||||
|
# Wait for everything to load...
|
||||||
|
page.wait_for_timeout(5000)
|
||||||
|
|
||||||
|
# We should be at the home page here
|
||||||
|
download_file(page)
|
||||||
|
|
||||||
|
|
||||||
|
def switchingForever(page: Page):
|
||||||
|
print("Switching to not get timed out!")
|
||||||
|
page.goto(ACCOUNT_LINK)
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
while (count < 12):
|
||||||
|
page.wait_for_timeout(5000)
|
||||||
|
page.get_by_role("link", name="Cards").hover()
|
||||||
|
page.get_by_role("link", name="Activate Card").click(timeout=0)
|
||||||
|
sleep(300)
|
||||||
|
page.get_by_role("link", name="My Accounts").click(timeout=0)
|
||||||
|
count = count + 1
|
||||||
|
print("Count updated: " + str(count))
|
||||||
|
|
||||||
|
download_file(page)
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(page: Page):
|
||||||
|
print("Going to download a new QFX File")
|
||||||
|
page.goto(ACCOUNT_LINK)
|
||||||
|
|
||||||
|
page.locator('iframe[title="Next Gen Home Page"]').content_frame.get_by_test_id(
|
||||||
|
"com.ncr.dbk.olb.widgets.my-accounts"
|
||||||
|
).get_by_test_id("feature-link").click()
|
||||||
|
|
||||||
|
page.wait_for_timeout(5000)
|
||||||
|
|
||||||
|
page.locator("#ncr-la-chat-win-notif-close path").click()
|
||||||
|
|
||||||
|
page.locator(
|
||||||
|
'iframe[title="NextGen account history page"]'
|
||||||
|
).content_frame.get_by_role("button", name="Download Transactions Button").click()
|
||||||
|
|
||||||
|
with page.expect_download(timeout=0) as download_info:
|
||||||
|
page.mouse.wheel(0, 70)
|
||||||
|
|
||||||
|
page.locator(
|
||||||
|
'iframe[title="NextGen account history page"]'
|
||||||
|
).content_frame.get_by_role("menuitem", name="Export QFX").dblclick()
|
||||||
|
|
||||||
|
|
||||||
|
download = download_info.value
|
||||||
|
|
||||||
|
download.save_as("./test.qfx")
|
||||||
|
|
||||||
|
print("Downloaded!")
|
||||||
|
|
||||||
|
switchingForever(page)
|
||||||
|
|
||||||
|
# context.close()
|
||||||
|
# browser.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with sync_playwright() as playwright:
|
||||||
|
main(playwright)
|
||||||
0
webscrape-bank/src/main.py
Normal file
0
webscrape-bank/src/main.py
Normal file
Loading…
Add table
Add a link
Reference in a new issue