Skip to content

Instantly share code, notes, and snippets.

@jikamens
Last active August 27, 2025 15:30
Show Gist options
  • Save jikamens/b2bcb94ae0cab8551db59635ff534d72 to your computer and use it in GitHub Desktop.
Save jikamens/b2bcb94ae0cab8551db59635ff534d72 to your computer and use it in GitHub Desktop.
Scripts and configs for "enhancing" Eastern Bank emailed deposit notifications
#!/bin/bash -e
TF=/fillmein/deposit_notification.$$
trap "rm -f $TF" EXIT
echo "Content-type: message/rfc822"
echo
if /fillmein/eastern-deposit-notification.py >| $TF; then
cat $TF
fi
#!/usr/bin/env python3
# Objective: decide whether to notify about a deposit notification email
# from Eastern Bank, and if so, try to fetch a description of the deposit
# from online banking.
#
# 1. Read notification email from Eastern Bank on stdin.
# 2. Extract deposit amount from email.
# 3. Check if deposit is in GnuCash file. If so, then:
# a. Add a header to the message indicating that for procmail to read.
# b. Add the description of the deposit to the email subject and body.
# c. Spit the modified message to stdout.
# d. Exit.
# 4. Wait for MFA code to be saved to the filesystem by a different script.
# 5. Finish logging in using saved MFA code and then delete it.
# 6. Go to checking account page.
# 7. Download transactions and find the right description in the download.
# 8. Add description of transaction to email.
# 9. Spit the modified message to stdout.
# 10. Exit.
import argparse
from contextlib import contextmanager
import csv
import datetime
import email
import email.policy
import html
import os
from pandas import Timestamp
import re
from selenium_wrapper import ChromeWebDriver, FirefoxWebDriver
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import (
NoSuchElementException,
StaleElementReferenceException,
)
from selenium.webdriver.common.action_chains import ActionChains # noqa
from selenium.webdriver.common.keys import Keys
import sys
import time
from types import SimpleNamespace
import xml.etree.ElementTree as ET
gnucash_file = os.path.expanduser('/fillmein')
mfacode_file = os.path.expanduser('/fillmein/mfacode_eastern.txt')
password_file = os.path.expanduser('/fillmein')
ns = {
'gnc': 'http://www.gnucash.org/XML/gnc',
'act': 'http://www.gnucash.org/XML/act',
'trn': 'http://www.gnucash.org/XML/trn',
'ts': 'http://www.gnucash.org/XML/ts',
'split': 'http://www.gnucash.org/XML/split',
}
crlf = False
wait_time = 10
username_xpath = '//*[@id="bb-username-then-password-input"]'
password_xpath = '//*[@id="bb-username-then-password-input"]'
login_button_xpath = '//*[@id="VALIDATE_CREDENTIALS1"]'
mfa_code_xpath = '//*[@id="identity-mfa-otp-entry-otp-input"]'
trust_browser_xpath = '//*[@id="accept"]'
active_continue_xpath = '//*/button[contains(text(), "Continue")]'
account_xpath = '//div[contains(text(), "PREMIER CHECKING")]'
download_button_xpath = \
'//*/bb-transactions-list//*/bb-dropdown-menu-ui/div/button'
download_csv_xpath = '//*[contains(text(), "CSV")]'
username = 'fillmein'
password = open(password_file).read().strip()
download_directory = os.path.expanduser('/fillmein')
def get_email(args):
global crlf
email_str = args.input_file.read()
if '\r\n' in email_str:
email_str = re.sub(r'\r\n', r'\n', email_str)
crlf = True
return email.message_from_string(email_str, policy=email.policy.default)
def print_email(email_obj):
email_str = email_obj.as_string()
if crlf:
email_str = re.sub(r'\n', r'\r\n', email_str)
print(email_str)
def get_deposit_amount(email_str):
r = r'A deposit in the amount of \$\s*([\d.,]+)'
match = re.search(r, email_str)
if not match:
raise Exception(f'No match for {r} in email')
return float(match.group(1).replace(',', ''))
def is_in_gnucash_file(amount):
tree = ET.parse(gnucash_file)
root = tree.getroot()
accounts = root.findall('*/gnc:account', ns)
# Assumes accounts are sorted by depth
assets_guid = None
for account in accounts:
name = account.find('act:name', ns).text
guid = account.find('act:id', ns).text
if not assets_guid and name == 'Assets':
assets_guid = guid
continue
parent = account.find('act:parent', ns)
if parent is not None:
parent = parent.text
if assets_guid and name == 'Checking' and parent == assets_guid:
checking_guid = guid
break
else:
raise Exception('Failed to find checking account in GnuCash file')
all_transactions = (t for t in root.findall('*/gnc:transaction', ns))
before = (datetime.date.today() - datetime.timedelta(days=7)).\
strftime('%F')
recent_transactions = (
t for t in all_transactions
if t.find('trn:date-posted/ts:date', ns).text > before
)
for t in recent_transactions:
checking_splits = (
s
for s in t.findall('trn:splits/trn:split', ns)
if s.find('split:account', ns).text == checking_guid
)
deposit_amounts = (
int(match.group(1)) / 100
for s in checking_splits
for value in s.findall('split:value', ns)
for match in (re.match(r'([0-9]+)/100', value.text),)
if match is not None
)
if amount in deposit_amounts:
return (t.find('trn:description', ns).text or
'(NO DESCRIPTION IN GNUCASH)')
return False
def reset_mfa_code():
try:
os.remove(os.path.realpath(mfacode_file))
except FileNotFoundError:
pass
def get_mfa_code(wait=30):
when = time.time() + wait
while time.time() < when:
try:
code = open(mfacode_file).read().strip()
break
except FileNotFoundError:
time.sleep(1)
else:
raise Exception('Failed to read MFA code')
os.remove(os.path.realpath(mfacode_file))
return code
def wait_for_stale(elt):
end = time.time() + wait_time
while time.time() < end:
try:
elt.find_elements(By.ID, 'nosuchid')
time.sleep(1)
except StaleElementReferenceException:
break
else:
raise Exception("New page didn't load")
def wait_for_xpath(driver, xpath):
end = time.time() + wait_time
while True:
try:
return driver.find_element(By.XPATH, xpath)
except NoSuchElementException:
if time.time() > end:
raise
time.sleep(1)
def find_one_of_several(driver, xpaths, timeout=wait_time):
end_at = time.time() + timeout
while time.time() < end_at:
for xpath in xpaths:
try:
driver.find_element(By.XPATH, xpath)
return xpath
except Exception:
continue
time.sleep(1)
else:
print(driver.page_source, file=sys.stderr)
raise TimeoutError(f'Could not find any of {xpaths}')
def add_description(email_obj, amount, description):
description = html.escape(description)
email_obj.replace_header('Subject', email_obj['Subject'] +
f' ({format_amount(amount)}, {description})')
content = email_obj.get_body().get_content()
content = re.sub(r'(A deposit )(in the amount of)',
f'\\1with description <strong>{description}</strong> '
f'\\2', content)
email_obj.get_body().set_content(content, subtype='html')
return email_obj
@contextmanager
def wait_for_new_file(directory, delay=5):
mgr = SimpleNamespace()
mgr.directory = directory
mgr.before = os.listdir(download_directory)
yield mgr
start = time.time()
while time.time() - start < delay:
mgr.after = os.listdir(download_directory)
if len(mgr.after) < len(mgr.before):
raise Exception(f'Files disappeared unexpectedly from {directory}')
if len(mgr.after) > len(mgr.before):
break
time.sleep(1)
new_file = list(set(mgr.after) - set(mgr.before))[0]
mgr.new_path = os.path.join(directory, new_file)
def get_description_from_csv(path, amount):
cutoff = datetime.date.today() - datetime.timedelta(days=7)
with open(path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
# Transactions are in reverse order
if Timestamp(row['Transaction date']).date() < cutoff:
break
if row['Credit/debit indicator'] != 'Credit':
continue
if float(row['Amount']) == amount:
description = row['Original description']
description = re.sub(r'^(?:Preauthorized|Transfer) Credit ',
'', description, 1, re.I)
return description
return None
def add_description_from_web(args, amount, email_obj):
if args.browser == 'firefox':
options = webdriver.FirefoxOptions()
if args.headless:
options.add_argument('-headless')
driver = FirefoxWebDriver(options=options)
else:
options = webdriver.ChromeOptions()
options.add_argument('--disable-gpu')
if args.headless:
options.add_argument('headless=new')
driver = ChromeWebDriver(options=options)
try:
reset_mfa_code()
driver.get('https://online.eastern.bank')
username_field = wait_for_xpath(driver, username_xpath)
username_field.send_keys(username)
username_field.send_keys(Keys.ENTER)
wait_for_stale(username_field)
password_field = driver.find_element(By.XPATH, password_xpath)
password_field.send_keys(password)
password_field.send_keys(Keys.ENTER)
wait_for_stale(password_field)
while True:
xpath = find_one_of_several(
driver, (account_xpath, mfa_code_xpath, active_continue_xpath))
if xpath == mfa_code_xpath:
code_input = wait_for_xpath(driver, mfa_code_xpath)
mfa_code = get_mfa_code()
code_input.send_keys(mfa_code)
code_input.send_keys(Keys.ENTER)
wait_for_stale(code_input)
trust_button = driver.find_element(
By.XPATH, trust_browser_xpath)
trust_button.click()
elif xpath == active_continue_xpath:
wait_for_xpath(driver, active_continue_xpath).click()
else:
break
account_tile = wait_for_xpath(driver, account_xpath)
account_tile.click()
wait_for_stale(account_tile)
wait_for_xpath(driver, download_button_xpath)
with wait_for_new_file(download_directory) as downloader:
driver.find_element(By.XPATH, download_button_xpath).click()
driver.find_element(By.XPATH, download_csv_xpath).click()
csv_path = downloader.new_path
description = get_description_from_csv(csv_path, amount)
if not description:
print(f'Did not find {format_amount(amount)} deposit in online '
f'banking transactions file {csv_path}', file=sys.stderr)
return
os.unlink(csv_path)
description = re.sub(r'^(?:Preauthorized|Transfer) Credit ', '',
description)
email_obj = add_description(email_obj, amount, description)
print_email(email_obj)
sys.exit()
except NoSuchElementException:
screenshot_file = \
f'/tmp/eastern-deposit-notification.{os.getpid()}.png'
driver.save_screenshot(screenshot_file)
print(f'Missing element. Screenshot saved as {screenshot_file}. '
f'Page source:', file=sys.stderr)
print(driver.page_source, file=sys.stderr)
raise
finally:
driver.quit()
def parse_args():
parser = argparse.ArgumentParser(description='Add deposit description to '
'Eastern Bank deposit notification email')
parser.add_argument('--firefox', dest='browser', action='store_const',
const='firefox', default='firefox')
parser.add_argument('--chrome', dest='browser', action='store_const',
const='chrome')
parser.add_argument('--no-headless', '--noheadless', dest='headless',
action='store_false', default=True)
parser.add_argument('input_file', nargs='?', type=argparse.FileType('r'),
default=sys.stdin)
return parser.parse_args()
def format_amount(amount):
return f"${amount:,.2f}".replace(".00", "")
def main():
args = parse_args()
email_obj = get_email(args)
deposit_amount = get_deposit_amount(email_obj.get_body().get_content())
print(f'Deposit amount is {format_amount(deposit_amount)}',
file=sys.stderr)
if description := is_in_gnucash_file(deposit_amount):
email_obj['X-In-GnuCash-File'] = 'Yes'
email_obj = add_description(email_obj, deposit_amount, description)
print_email(email_obj)
sys.exit()
add_description_from_web(args, deposit_amount, email_obj)
sys.exit(1)
if __name__ == '__main__':
main()
<FilesMatch "^deposit_notification\.cgi$">
Order deny,allow
Deny from all
Allow from fillmein
Allow from fillmein
Allow from fillmein
Allow from fillmein
</FilesMatch>
#!/bin/bash -e
declare -A params
while read kv; do
key="${kv%%=*}"
if [ ! "$key" ]; then
continue
fi
if [[ "$kv" =~ = ]]; then
value="${kv#*=}"
else
value=""
fi
params[$key]="$value"
done < <(echo "${QUERY_STRING}" | sed -E "s/&/\n/g")
# For debugging
#for key in "${!params[@]}"; do
# echo "params[$key]=${params[$key]}"
#done
#exit
safe() {
echo "$1" | tr -c -d -- -_A-Za-z0-9
}
source=$(safe "${params[source]}")
code=$(safe "${params[c]}")
D=/fillmein
if [ -n "$source" ]; then
F="$D/mfacode_$source.txt"
else
F="$D/mfacode.txt"
fi
if [ -n "$code" ]; then
echo "$code" >| $F.tmp
mv -f $F.tmp $F
fi
echo "Content-Type: text/plain"
echo
echo ok
:0
* ^From: .*Eastern ?Bank
* ^Subject: New deposit
{
:0 fw : $MAILDIR/deposit-notification$LOCKEXT
|tf=/tmp/notif.$$; curl --silent --data-binary '@-' http://fillmein/deposit_notification.cgi > $tf; if [ -s $tf ]; then cat $tf; e=0; else e=1; fi; exit $e
:0
* ^X-In-GnuCash-File: Yes
/dev/null
}
:0 wHB
* ^From: .*voice.*google\.com
* ^Subject: New text message
* Eastern Bank will not call you for this code
|set -x; c=`sed -n -e '/Your security code is [0-9][0-9]*/{s/.*Your security code is \([0-9][0-9]*\).*/\1/p;q}'`; if [ -n "$c" ]; then curl --silent "http://fillmein/mfacode.cgi?source=eastern&c=$c"; else exit 1; fi
<TaskerData sr="" dvi="1" tv="5.14.6">
<Profile sr="prof51" ve="2">
<cdate>1598996703108</cdate>
<edate>1640480198905</edate>
<flags>8</flags>
<id>51</id>
<mid0>52</mid0>
<nme>Forward Eastern MFA Codes</nme>
<Event sr="con0" ve="2">
<code>1520257414</code>
<pri>0</pri>
<Bundle sr="arg0">
<Vals sr="val">
<AllFields>false</AllFields>
<AllFields-type>java.lang.Boolean</AllFields-type>
<App>&lt;null&gt;</App>
<App-type>java.lang.String</App-type>
<BigImageNames>&lt;null&gt;</BigImageNames>
<BigImageNames-type>java.lang.String</BigImageNames-type>
<BigTextNames>&lt;null&gt;</BigTextNames>
<BigTextNames-type>java.lang.String</BigTextNames-type>
<CancelReason>&lt;StringArray sr=""/&gt;</CancelReason>
<CancelReason-type>[Ljava.lang.String;</CancelReason-type>
<CaseinsensitiveApp>false</CaseinsensitiveApp>
<CaseinsensitiveApp-type>java.lang.Boolean</CaseinsensitiveApp-type>
<CaseinsensitiveCategoryName>false</CaseinsensitiveCategoryName>
<CaseinsensitiveCategoryName-type>java.lang.Boolean</CaseinsensitiveCategoryName-type>
<CaseinsensitivePackage>false</CaseinsensitivePackage>
<CaseinsensitivePackage-type>java.lang.Boolean</CaseinsensitivePackage-type>
<CaseinsensitiveText>false</CaseinsensitiveText>
<CaseinsensitiveText-type>java.lang.Boolean</CaseinsensitiveText-type>
<CaseinsensitiveTitle>false</CaseinsensitiveTitle>
<CaseinsensitiveTitle-type>java.lang.Boolean</CaseinsensitiveTitle-type>
<CategoryImportance>&lt;StringArray sr=""/&gt;</CategoryImportance>
<CategoryImportance-type>[Ljava.lang.String;</CategoryImportance-type>
<CategoryName>&lt;null&gt;</CategoryName>
<CategoryName-type>java.lang.String</CategoryName-type>
<ExactApp>false</ExactApp>
<ExactApp-type>java.lang.Boolean</ExactApp-type>
<ExactCategoryName>false</ExactCategoryName>
<ExactCategoryName-type>java.lang.Boolean</ExactCategoryName-type>
<ExactPackage>false</ExactPackage>
<ExactPackage-type>java.lang.Boolean</ExactPackage-type>
<ExactText>false</ExactText>
<ExactText-type>java.lang.Boolean</ExactText-type>
<ExactTitle>false</ExactTitle>
<ExactTitle-type>java.lang.Boolean</ExactTitle-type>
<HasMediaSession>false</HasMediaSession>
<HasMediaSession-type>java.lang.Boolean</HasMediaSession-type>
<HasReplyAction>false</HasReplyAction>
<HasReplyAction-type>java.lang.Boolean</HasReplyAction-type>
<Id>&lt;null&gt;</Id>
<Id-type>java.lang.String</Id-type>
<IgnoreGroupSummary>false</IgnoreGroupSummary>
<IgnoreGroupSummary-type>java.lang.Boolean</IgnoreGroupSummary-type>
<ImageNames>&lt;null&gt;</ImageNames>
<ImageNames-type>java.lang.String</ImageNames-type>
<InterceptApps>&lt;StringArray sr=""&gt;&lt;_array_InterceptApps0&gt;com.google.android.talk&lt;/_array_InterceptApps0&gt;&lt;_array_InterceptApps1&gt;com.google.android.apps.messaging&lt;/_array_InterceptApps1&gt;&lt;/StringArray&gt;</InterceptApps>
<InterceptApps-type>[Ljava.lang.String;</InterceptApps-type>
<InterceptPersistent>1</InterceptPersistent>
<InterceptPersistent-type>java.lang.String</InterceptPersistent-type>
<InvertApp>false</InvertApp>
<InvertApp-type>java.lang.Boolean</InvertApp-type>
<InvertCategoryName>false</InvertCategoryName>
<InvertCategoryName-type>java.lang.Boolean</InvertCategoryName-type>
<InvertPackage>false</InvertPackage>
<InvertPackage-type>java.lang.Boolean</InvertPackage-type>
<InvertText>false</InvertText>
<InvertText-type>java.lang.Boolean</InvertText-type>
<InvertTitle>false</InvertTitle>
<InvertTitle-type>java.lang.Boolean</InvertTitle-type>
<PackageName>&lt;null&gt;</PackageName>
<PackageName-type>java.lang.String</PackageName-type>
<RegexApp>false</RegexApp>
<RegexApp-type>java.lang.Boolean</RegexApp-type>
<RegexCategoryName>false</RegexCategoryName>
<RegexCategoryName-type>java.lang.Boolean</RegexCategoryName-type>
<RegexPackage>false</RegexPackage>
<RegexPackage-type>java.lang.Boolean</RegexPackage-type>
<RegexText>false</RegexText>
<RegexText-type>java.lang.Boolean</RegexText-type>
<RegexTitle>false</RegexTitle>
<RegexTitle-type>java.lang.Boolean</RegexTitle-type>
<Text>Eastern Bank will not call you for this code.</Text>
<Text-type>java.lang.String</Text-type>
<TextNames>&lt;null&gt;</TextNames>
<TextNames-type>java.lang.String</TextNames-type>
<Title>&lt;null&gt;</Title>
<Title-type>java.lang.String</Title-type>
<Type>0</Type>
<Type-type>java.lang.String</Type-type>
<com.twofortyfouram.locale.intent.extra.BLURB>Event Behaviour: true
Notification Type: Only Created Notifications
Persistency Type: Non-Persistent Only
Notification Apps: Messages
Notification Text: Eastern Bank will not call you for this code.</com.twofortyfouram.locale.intent.extra.BLURB>
<com.twofortyfouram.locale.intent.extra.BLURB-type>java.lang.String</com.twofortyfouram.locale.intent.extra.BLURB-type>
<net.dinglisch.android.tasker.EXTRA_NSR_DEPRECATED>true</net.dinglisch.android.tasker.EXTRA_NSR_DEPRECATED>
<net.dinglisch.android.tasker.EXTRA_NSR_DEPRECATED-type>java.lang.Boolean</net.dinglisch.android.tasker.EXTRA_NSR_DEPRECATED-type>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES>&lt;StringArray sr=""&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;%anapp
01. App name
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;%anbackgroundimage
Background Image
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&gt;%anbigicon
04. Icon When Expanded
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&gt;%anbutton1action
13. Button Action 1
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&gt;%anbutton1icon
13. Button 1 Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5&gt;%anbutton1text
12. Button 1 Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES6&gt;%anbutton2action
15. Button 2 Action
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES6&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES7&gt;%anbutton2icon
15. Button 2 Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES7&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES8&gt;%anbutton2text
14. Button 2 Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES8&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES9&gt;%anbutton3action
17. Button 3 Action
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES9&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES10&gt;%anbutton3icon
17. Button 3 Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES10&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES11&gt;%anbutton3text
16. Button 3 Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES11&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES12&gt;%anbutton4action
18. Button 4 Action
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES12&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES13&gt;%anbutton4icon
18. Button 4 Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES13&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES14&gt;%anbutton4text
18. Button 4 Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES14&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES15&gt;%anbutton5action
19. Button 5 Action
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES15&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES16&gt;%anbutton5icon
19. Button 5 Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES16&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES17&gt;%anbutton5text
19. Button 5 Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES17&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES18&gt;%ancancelreason
Reason Cancelled Code
Code of the reason the notification was cancelled&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES18&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES19&gt;%ancancelreasontext
Reason Cancelled Text
Reason the notification was cancelled&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES19&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES20&gt;%ancategoryid
Category ID
Notification's category ID&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES20&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES21&gt;%ancategoryimportance
Category Importance
Notification's category Importance (0-5)&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES21&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES22&gt;%ancategoryname
Category Name
Notification's category Name&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES22&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES23&gt;%ancolor
Color
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES23&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES24&gt;%anconversation
Conversation
JSON Structure with all conversation related fields, i.e. text, sender, image, etc&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES24&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES25&gt;%andismissaction
23. Action On Dismiss Id
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES25&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES26&gt;%anextrainfo
Extra Info
Extra info that you can add to an AutoNotification notification when you create it. Empty for others.&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES26&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES27&gt;%anextras
All Extras
All extras from the notification that are not already in one of the other variables in JSON format&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES27&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES28&gt;%angroup
Group
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES28&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES29&gt;%anhasmediasession
Has Media Session
All extras from the notification that are not already in one of the other variables in JSON format&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES29&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES30&gt;%anicon
04. Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES30&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES31&gt;%anid
Id
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES31&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES32&gt;%aninfotext
10. Info Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES32&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES33&gt;%ankey
Key
A unique instance key for this notification&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES33&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES34&gt;%annumber
Number
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES34&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES35&gt;%anpackage
Package Name
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES35&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES36&gt;%anpeople
People
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES36&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES37&gt;%anpersistent
Is Persistent
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES37&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES38&gt;%anpicture
05. Picture
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES38&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES39&gt;%anpriority
Priority
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES39&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES40&gt;%anprogress
Progress
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES40&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES41&gt;%anprogressindeterminate
Progress Indeterminate
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES41&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES42&gt;%anprogressmax
Progress Max
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES42&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES43&gt;%anreplyaction
20. Action Reply
Use with AutoNotification Reply&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES43&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES44&gt;%anreplychoices()
Reply Choices
Choices for reply available as quick actions in the notification&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES44&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES45&gt;%anshowchronometer
Show Chronometer
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES45&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES46&gt;%ansortkey
Sort Key
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES46&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES47&gt;%anstatus
Cancelled Or Created
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES47&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES48&gt;%anstatusbaricon
07. Status Bar Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES48&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES49&gt;%anstatusbaricon
Status Bar Icon
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES49&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES50&gt;%ansubtext
08. SubText
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES50&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES51&gt;%ansummary
Is Summary
Will be set to &amp;lt;b&amp;gt;1&amp;lt;/b&amp;gt; if it's a summary and &amp;lt;b&amp;gt;will not be set&amp;lt;/b&amp;gt; otherwise.&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES51&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES52&gt;%ansummarytext
09. Summary Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES52&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES53&gt;%antag
Tag
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES53&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES54&gt;%antext
03. Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES54&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES55&gt;%antextbig
03. Text When Expanded
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES55&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES56&gt;%antextlines()
11. Text Lines
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES56&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES57&gt;%anticker
03. Ticker Text
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES57&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES58&gt;%antitle
02. Title
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES58&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES59&gt;%antitlebig
02. Title When Expanded
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES59&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES60&gt;%antouchaction
22. Action On Touch Id
Use with AutoNotification Actions&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES60&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES61&gt;%anuserid
User ID
User ID for whom this notification is intended&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES61&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES62&gt;%anwhen
Show When
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES62&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES63&gt;%anwhentime
When
&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES63&gt;&lt;/StringArray&gt;</net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
<net.dinglisch.android.tasker.extras.REQUESTED_TIMEOUT>0</net.dinglisch.android.tasker.extras.REQUESTED_TIMEOUT>
<net.dinglisch.android.tasker.extras.REQUESTED_TIMEOUT-type>java.lang.Integer</net.dinglisch.android.tasker.extras.REQUESTED_TIMEOUT-type>
<net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS>InterceptApps Text Type InterceptPersistent plugininstanceid plugintypeid </net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS>
<net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS-type>java.lang.String</net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS-type>
<net.dinglisch.android.tasker.subbundled>true</net.dinglisch.android.tasker.subbundled>
<net.dinglisch.android.tasker.subbundled-type>java.lang.Boolean</net.dinglisch.android.tasker.subbundled-type>
<plugininstanceid>70cfbb43-b7b7-436e-b9d6-f66dc720523a</plugininstanceid>
<plugininstanceid-type>java.lang.String</plugininstanceid-type>
<plugintypeid>com.joaomgcd.autonotification.intent.IntentInterceptNotificationEvent</plugintypeid>
<plugintypeid-type>java.lang.String</plugintypeid-type>
</Vals>
</Bundle>
<Str sr="arg1" ve="3">com.joaomgcd.autonotification</Str>
<Str sr="arg2" ve="3">com.joaomgcd.autonotification.activity.ActivityConfigNotificationInterceptTaskerEvent</Str>
<Int sr="arg3" val="0"/>
</Event>
</Profile>
<Task sr="task52">
<cdate>1598996100855</cdate>
<edate>1640480403349</edate>
<id>52</id>
<nme>Send Eastern MFA Code</nme>
<pri>6</pri>
<Action sr="act0" ve="7">
<code>598</code>
<Str sr="arg0" ve="3">%antext</Str>
<Str sr="arg1" ve="3">Your security code is \d+</Str>
<Int sr="arg2" val="0"/>
<Int sr="arg3" val="1"/>
<Int sr="arg4" val="1"/>
<Str sr="arg5" ve="3">%mfamatch</Str>
<Int sr="arg6" val="0"/>
<Str sr="arg7" ve="3"/>
</Action>
<Action sr="act1" ve="7">
<code>598</code>
<Str sr="arg0" ve="3">%mfamatch1</Str>
<Str sr="arg1" ve="3">\d+</Str>
<Int sr="arg2" val="0"/>
<Int sr="arg3" val="0"/>
<Int sr="arg4" val="1"/>
<Str sr="arg5" ve="3">%mfacode</Str>
<Int sr="arg6" val="0"/>
<Str sr="arg7" ve="3"/>
</Action>
<Action sr="act2" ve="7">
<code>339</code>
<Bundle sr="arg0">
<Vals sr="val">
<net.dinglisch.android.tasker.RELEVANT_VARIABLES>&lt;StringArray sr=""&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;%http_cookies
Cookies
The cookies the server sent in the response in the Cookie:COOKIE_VALUE format. You can use this directly in the 'Headers' field of the HTTP Request action&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;%http_data
Data
Data that the server responded from the HTTP request.&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&gt;%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&gt;%http_response_code
Response Code
The HTTP Code the server responded&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&gt;%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5&gt;%http_response_length
Response Length
The size of the response in bytes&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5&gt;&lt;/StringArray&gt;</net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
</Vals>
</Bundle>
<Int sr="arg1" val="0"/>
<Int sr="arg10" val="0"/>
<Int sr="arg11" val="0"/>
<Int sr="arg12" val="0"/>
<Str sr="arg2" ve="3">http://fillmein/mfacode.cgi?source=eastern&amp;c=%mfacode1</Str>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3"/>
<Str sr="arg6" ve="3"/>
<Str sr="arg7" ve="3"/>
<Int sr="arg8" val="30"/>
<Int sr="arg9" val="0"/>
</Action>
<Action sr="act3" ve="7">
<code>2046367074</code>
<Bundle sr="arg0">
<Vals sr="val">
<App>&lt;null&gt;</App>
<App-type>java.lang.String</App-type>
<CancelAll>false</CancelAll>
<CancelAll-type>java.lang.Boolean</CancelAll-type>
<CancelPersistent>false</CancelPersistent>
<CancelPersistent-type>java.lang.Boolean</CancelPersistent-type>
<CaseinsensitiveApp>false</CaseinsensitiveApp>
<CaseinsensitiveApp-type>java.lang.Boolean</CaseinsensitiveApp-type>
<CaseinsensitivePackage>false</CaseinsensitivePackage>
<CaseinsensitivePackage-type>java.lang.Boolean</CaseinsensitivePackage-type>
<CaseinsensitiveText>false</CaseinsensitiveText>
<CaseinsensitiveText-type>java.lang.Boolean</CaseinsensitiveText-type>
<CaseinsensitiveTitle>false</CaseinsensitiveTitle>
<CaseinsensitiveTitle-type>java.lang.Boolean</CaseinsensitiveTitle-type>
<ExactApp>false</ExactApp>
<ExactApp-type>java.lang.Boolean</ExactApp-type>
<ExactPackage>false</ExactPackage>
<ExactPackage-type>java.lang.Boolean</ExactPackage-type>
<ExactText>false</ExactText>
<ExactText-type>java.lang.Boolean</ExactText-type>
<ExactTitle>false</ExactTitle>
<ExactTitle-type>java.lang.Boolean</ExactTitle-type>
<InterceptApps>&lt;StringArray sr=""/&gt;</InterceptApps>
<InterceptApps-type>[Ljava.lang.String;</InterceptApps-type>
<InvertApp>false</InvertApp>
<InvertApp-type>java.lang.Boolean</InvertApp-type>
<InvertPackage>false</InvertPackage>
<InvertPackage-type>java.lang.Boolean</InvertPackage-type>
<InvertText>false</InvertText>
<InvertText-type>java.lang.Boolean</InvertText-type>
<InvertTitle>false</InvertTitle>
<InvertTitle-type>java.lang.Boolean</InvertTitle-type>
<OtherId>&lt;null&gt;</OtherId>
<OtherId-type>java.lang.String</OtherId-type>
<OtherPackage>&lt;null&gt;</OtherPackage>
<OtherPackage-type>java.lang.String</OtherPackage-type>
<OtherTag>&lt;null&gt;</OtherTag>
<OtherTag-type>java.lang.String</OtherTag-type>
<PackageName>&lt;null&gt;</PackageName>
<PackageName-type>java.lang.String</PackageName-type>
<RegexApp>false</RegexApp>
<RegexApp-type>java.lang.Boolean</RegexApp-type>
<RegexPackage>false</RegexPackage>
<RegexPackage-type>java.lang.Boolean</RegexPackage-type>
<RegexText>false</RegexText>
<RegexText-type>java.lang.Boolean</RegexText-type>
<RegexTitle>false</RegexTitle>
<RegexTitle-type>java.lang.Boolean</RegexTitle-type>
<Text>&lt;null&gt;</Text>
<Text-type>java.lang.String</Text-type>
<Title>&lt;null&gt;</Title>
<Title-type>java.lang.String</Title-type>
<com.twofortyfouram.locale.intent.extra.BLURB>Id: %anid</com.twofortyfouram.locale.intent.extra.BLURB>
<com.twofortyfouram.locale.intent.extra.BLURB-type>java.lang.String</com.twofortyfouram.locale.intent.extra.BLURB-type>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES>&lt;StringArray sr=""&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;%err
Error Code
Only available if you select &amp;lt;b&amp;gt;Continue Task After Error&amp;lt;/b&amp;gt; and the action ends in error&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0&gt;&lt;_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;%errmsg
Error Message
Only available if you select &amp;lt;b&amp;gt;Continue Task After Error&amp;lt;/b&amp;gt; and the action ends in error&lt;/_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1&gt;&lt;/StringArray&gt;</net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
<net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS>notificaitionid plugininstanceid plugintypeid </net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS>
<net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS-type>java.lang.String</net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS-type>
<net.dinglisch.android.tasker.subbundled>true</net.dinglisch.android.tasker.subbundled>
<net.dinglisch.android.tasker.subbundled-type>java.lang.Boolean</net.dinglisch.android.tasker.subbundled-type>
<notificaitionid>%anid</notificaitionid>
<notificaitionid-type>java.lang.String</notificaitionid-type>
<plugininstanceid>98a9b1aa-64c3-41b8-8d7c-ba4b5779b568</plugininstanceid>
<plugininstanceid-type>java.lang.String</plugininstanceid-type>
<plugintypeid>com.joaomgcd.autonotification.intent.IntentCancelNotification</plugintypeid>
<plugintypeid-type>java.lang.String</plugintypeid-type>
</Vals>
</Bundle>
<Str sr="arg1" ve="3">com.joaomgcd.autonotification</Str>
<Str sr="arg2" ve="3">com.joaomgcd.autonotification.activity.ActivityConfigCancelNotification</Str>
<Int sr="arg3" val="20"/>
<Int sr="arg4" val="0"/>
</Action>
</Task>
</TaskerData>
Profile: Forward Eastern MFA Codes
Settings: Restore: no
Event: AutoNotification Intercept [ Configuration:Event Behaviour: true
Notification Type: Only Created Notifications
Persistency Type: Non-Persistent Only
Notification Apps: Messages
Notification Text: Eastern Bank will not call you for this code. ]
Enter Task: Send Eastern MFA Code
Task: Send Eastern MFA Code
A1: Variable Search Replace [
Variable: %antext
Search: Your security code is \d+
Multi-Line: On
One Match Only: On
Store Matches In Array: %mfamatch ]
A2: Variable Search Replace [
Variable: %mfamatch1
Search: \d+
One Match Only: On
Store Matches In Array: %mfacode ]
A3: HTTP Request [
Method: GET
URL: http://fillmein/mfacode.cgi?source=eastern&c=%mfacode1
Timeout (Seconds): 30 ]
A4: AutoNotification Cancel [
Configuration: Id: %anid
Timeout (Seconds): 20 ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment