From 1ff0631915563c77c1ffd2ab03e095a763f47a22 Mon Sep 17 00:00:00 2001 From: Emily Doherty Date: Fri, 8 Sep 2023 23:20:22 -0700 Subject: [PATCH] Initial implementations Okay not really, but I have lost the original git repo so this is where we're at qq --- Makefile | 14 ++++ wow-token-updater.py | 162 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 Makefile create mode 100644 wow-token-updater.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34be169 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +token-current: + zip wow-token-current.zip wow-token-current.py + +token-historical: + zip wow-token-historical.zip wow-token-historical.py + +token-updater: + cd venv/lib/python3.9/site-packages && zip -r ../../../../wow-token-updater.zip . + zip -g wow-token-updater.zip wow-token-updater.py + +clean: + rm -v wow-token-current.zip wow-token-historical.zip wow-token-updater.zip + +all: token-current token-historical token-updater diff --git a/wow-token-updater.py b/wow-token-updater.py new file mode 100644 index 0000000..4dc7ad0 --- /dev/null +++ b/wow-token-updater.py @@ -0,0 +1,162 @@ +import datetime +import time +import boto3 +import json +import os +import requests + + +dynamo_client = boto3.client('dynamodb', region_name='us-east-1') +timestream_client = boto3.client('timestream-write', region_name='us-east-1') +current_table = os.environ['TABLE_NAME'] +recent_table = os.environ['RECENT_TABLE_NAME'] + + +# a lambda handler to handle eventbridge triggered vents +def lambda_handler(event, context): + regions = ['us', 'eu', 'kr', 'tw'] + live_prices = get_combined_live_price() + print(live_prices) + current_time_epoch = int(datetime.datetime.utcnow().timestamp()) + for region in regions: + print(f'Updating {region.upper()}...') + update_token_price(region, current_time_epoch, live_prices[region]) + + +def update_token_price(region: str, current_time_epoch: int, live_price: int): + stored_price = get_stored_price(region) + print(f'Current stored price {stored_price}') + if stored_price != live_price: + update_stored_token_price(region, live_price, current_time_epoch) + update_recent_token_price(region, live_price, current_time_epoch) + update_timestream_token_price(region, live_price, current_time_epoch) + else: + # price hasn't changed + print(f'Price has not changed for {region.upper()}') + + +# update the current price records in DynamoDB +def update_stored_token_price(region: str, price: int, current_time_epoch: int) -> None: + dynamo_client.update_item( + TableName=current_table, + Key={ + 'region': { + 'S': region + } + }, + UpdateExpression='SET price = :p, current_time = :t', + ExpressionAttributeValues={ + ':p': { + 'S': str(price) + }, + ':t': { + 'S': str(current_time_epoch) + } + } + # ReturnValues="UPDATED_NEW" + ) + print(f'Updated {region.upper()} price to {price}') + + +# add a record to the recent token price table in DynamoDB +def update_recent_token_price(region: str, price: int, current_time_epoch: int) -> None: + dynamo_client.put_item( + TableName=recent_table, + Item={ + 'region': { + 'S': region + }, + 'price': { + 'S': str(price) + }, + 'timestamp': { + 'N': str(current_time_epoch) + }, + 'expire': { + 'N': str(current_time_epoch + (60 * 60 * 24 * 31 * 12)) + } + } + # ReturnValues="UPDATED_NEW" + ) + print(f'Added {region.upper()} price {price} to {recent_table} table') + + +def update_timestream_token_price(region: str, price: int, current_time_epoch: int) -> None: + record_inserted = False + while not record_inserted: + try: + print('Attempting to write to Timestream') + timestream_client.write_records( + DatabaseName=os.environ['TIMESTREAM_DATABASE'], + TableName=f'{region}-price-history', + Records=[ + build_timestream_record(region, price, current_time_epoch), + ] + ) + record_inserted = True + except Exception as e: + print(f'Error writing to Timestream: {e}') + time.sleep(2) + print(f'Updated {region.upper()} price to {price} in Timestream') + + +def build_timestream_record(region: str, price: int, current_time_epoch: int) -> dict: + return { + 'Dimensions': [ + { + 'Name': 'region', + 'Value': region, + 'DimensionValueType': 'VARCHAR' + } + ], + 'MeasureName': 'price', + 'MeasureValue': str(price), + 'MeasureValueType': 'BIGINT', + 'Time': str(current_time_epoch), + 'TimeUnit': 'SECONDS', + } + + +# get the current stored token price from dynamodb +def get_stored_price(region: str) -> int: + response = dynamo_client.get_item( + TableName=current_table, + Key={ + 'region': { + 'S': region + } + } + ) + return int(response['Item']['price']['S']) + + +def get_combined_live_price() -> dict: + return { + 'us': get_token_price_from_blizzard('us'), + 'eu': get_token_price_from_blizzard('eu'), + 'kr': get_token_price_from_blizzard('kr'), + 'tw': get_token_price_from_blizzard('tw') + } + + +def get_token_price_from_blizzard(region: str): + api_endpoint = f'https://{region}.api.blizzard.com/data/wow/token/index' + params = {'namespace': f'{os.environ.get("BLIZZARD_NAMESPACE")}-{region}'} + headers = {'Authorization': f'Bearer {get_oauth_token()}'} + response = requests.get(api_endpoint, params=params, headers=headers) + return int(response.json()['price']) + + +def get_oauth_token(): + url = 'https://us.battle.net/oauth/token' + payload = { + 'grant_type': 'client_credentials', + 'client_id': os.environ.get('BLIZZARD_CLIENT_ID'), + 'client_secret': os.environ.get('BLIZZARD_CLIENT_SECRET') + } + response = requests.post(url, data=payload) + return json.loads(response.text)['access_token'] + + +if __name__ == '__main__': + lambda_handler(None, None)