Compare commits

..

16 Commits

Author SHA1 Message Date
18ceef8351 d
All checks were successful
Deploy App to Docker / deploy (push) Successful in 3m3s
2025-04-07 11:56:34 -04:00
87c84fd0a8 g 2025-04-07 11:54:24 -04:00
0c78276b12 j 2025-04-07 11:54:02 -04:00
47a1b1d3ac actions? 2025-04-07 11:52:35 -04:00
c889a84c34 h 2025-04-06 18:59:37 -04:00
1ef706afe5 a 2025-04-06 18:59:21 -04:00
3454f24451 a 2025-04-06 18:50:11 -04:00
7786655db0 time 2025-04-06 18:49:06 -04:00
ed52e7da04 json? 2025-04-06 18:48:47 -04:00
bf3f4ddb38 clean 2025-04-06 18:41:07 -04:00
3f53513c36 fixy req 2025-04-06 18:38:32 -04:00
d3bd696d67 1 more 2025-04-06 18:23:52 -04:00
113a920da7 fix order 2025-04-06 18:23:31 -04:00
9d11adaf6c hourly orders 2025-04-06 18:20:24 -04:00
8de5bec523 a 2025-04-06 15:07:45 -04:00
0414018099 asdf 2025-04-06 14:44:18 -04:00
6 changed files with 98 additions and 67 deletions

View File

@ -0,0 +1,32 @@
# .gitea/workflows/deploy.yml
name: Deploy App to Docker
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker
uses: docker/setup-buildx-action@v1
with:
version: 'latest'
- name: Build Docker Image
run: |
docker build -t giga_tcg .
- name: Remove existing Docker container
run: |
docker rm -f giga_tcg_container || true
- name: Run Docker container
run: |
docker run -d -p 8000:8000 --name giga_tcg_container giga_tcg

View File

@ -317,6 +317,7 @@ async def update_cookies(
detail=f"Failed to update cookies: {str(e)}" detail=f"Failed to update cookies: {str(e)}"
) )
### DEPRECATED ###
class TCGPlayerOrderRequest(BaseModel): class TCGPlayerOrderRequest(BaseModel):
order_ids: List[str] order_ids: List[str]
@ -336,3 +337,5 @@ async def process_orders(
except Exception as e: except Exception as e:
logger.error(f"Process orders failed: {str(e)}") logger.error(f"Process orders failed: {str(e)}")
raise HTTPException(status_code=400, detail=str(e)) raise HTTPException(status_code=400, detail=str(e))
### END DEPRECATED ###

View File

@ -26,6 +26,7 @@ class TaskService:
def register_scheduled_tasks(self): def register_scheduled_tasks(self):
self.scheduler.add_job(self.hourly_pricing, 'cron', minute='36') self.scheduler.add_job(self.hourly_pricing, 'cron', minute='36')
self.scheduler.add_job(self.hourly_orders, 'cron', hour='*', minute='03')
# every 5 hours on the 24th minute # every 5 hours on the 24th minute
#self.scheduler.add_job(self.inventory_pricing, 'cron', hour='*', minute='44') #self.scheduler.add_job(self.inventory_pricing, 'cron', hour='*', minute='44')
self.logger.info("Scheduled tasks registered.") self.logger.info("Scheduled tasks registered.")
@ -35,6 +36,11 @@ class TaskService:
self.pricing_service.cron_load_prices() self.pricing_service.cron_load_prices()
self.logger.info("Finished hourly pricing task") self.logger.info("Finished hourly pricing task")
def hourly_orders(self):
self.logger.info("Running hourly orders task")
self.tcgplayer_api_service.process_orders_task()
self.logger.info("Finished hourly orders task")
def inventory_pricing(self): def inventory_pricing(self):
self.tcgplayer_api_service.cron_tcgplayer_api_pricing() self.tcgplayer_api_service.cron_tcgplayer_api_pricing()

View File

@ -16,6 +16,7 @@ class TCGPlayerAPIConfig:
"""Configuration for TCGPlayer API""" """Configuration for TCGPlayer API"""
ORDER_BASE_URL: str = "https://order-management-api.tcgplayer.com/orders" ORDER_BASE_URL: str = "https://order-management-api.tcgplayer.com/orders"
API_VERSION: str = "?api-version=2.0" API_VERSION: str = "?api-version=2.0"
SELLER_KEY: str = "e576ed4c"
class TCGPlayerAPIService: class TCGPlayerAPIService:
def __init__(self, db: Session): def __init__(self, db: Session):
@ -40,6 +41,25 @@ class TCGPlayerAPIService:
return response.json() return response.json()
return None return None
def get_orders(self, size: int = 25) -> dict:
url = f"{self.config.ORDER_BASE_URL}/search{self.config.API_VERSION}"
payload = {
"searchRange": "LastThreeMonths",
"filters": {
"sellerKey": self.config.SELLER_KEY
},
"sortBy": [
{"sortingType": "orderStatus", "direction": "ascending"},
{"sortingType": "orderDate", "direction": "descending"}
],
"from": 0,
"size": size
}
response = self.requests_util.send_request(url, method='POST', cookies=self.cookies, json=payload)
if response:
return response.json()
return None
def get_product_ids_from_sku(self, sku_ids: list[str]) -> dict: def get_product_ids_from_sku(self, sku_ids: list[str]) -> dict:
"""Get product IDs from TCGPlayer SKU IDs""" """Get product IDs from TCGPlayer SKU IDs"""
# convert SKU IDs to integers # convert SKU IDs to integers
@ -101,14 +121,30 @@ class TCGPlayerAPIService:
self.db.add_all(order_products) self.db.add_all(order_products)
return db_order return db_order
def process_orders(self, orders: list[str]): def process_orders_task(self):
# get last 25 orders from tcgplayer
orders = self.get_orders(size=100)
if orders:
# get list of order ids
order_ids = [order['orderNumber'] for order in orders['orders']]
# get a list of order ids that are not in the database
existing_orders = self.db.query(Orders).filter(Orders.order_id.in_(order_ids)).all()
existing_order_ids = [order.order_id for order in existing_orders]
# get a list of order ids that are not in the database
new_order_ids = [order_id for order_id in order_ids if order_id not in existing_order_ids]
# process new orders
processed_orders = [] processed_orders = []
for order_id in orders: if new_order_ids:
order = self.get_order(order_id) logger.info(f"Processing {len(new_order_ids)} new orders")
if order: new_orders = [order for order in orders['orders'] if order['orderNumber'] in new_order_ids]
for new_order in new_orders:
order = self.get_order(new_order['orderNumber'])
self.save_order(order) self.save_order(order)
processed_orders.append(order_id) processed_orders.append(order['orderNumber'])
logger.info(f"Processed {len(processed_orders)} new orders")
return processed_orders return processed_orders
else:
logger.info("No new orders to process")
def get_scryfall_data(self, scryfall_id: str): def get_scryfall_data(self, scryfall_id: str):
url = f"https://api.scryfall.com/cards/{scryfall_id}?format=json" url = f"https://api.scryfall.com/cards/{scryfall_id}?format=json"
@ -133,7 +169,6 @@ class TCGPlayerAPIService:
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0 Safari/537.36' 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0 Safari/537.36'
} }
url = f"https://mp-search-api.tcgplayer.com/v2/product/{tcgplayer_id}/details?mpfev=3279" url = f"https://mp-search-api.tcgplayer.com/v2/product/{tcgplayer_id}/details?mpfev=3279"
#self.requests_util.rate_limit()
response = self.session.get(url, headers=headers) response = self.session.get(url, headers=headers)
self.requests_util.previous_request_time = datetime.now() self.requests_util.previous_request_time = datetime.now()
return response return response

View File

@ -61,10 +61,14 @@ class RequestHeaders:
'origin': Headers.SELLER_ORIGIN, 'origin': Headers.SELLER_ORIGIN,
'referer': Headers.SELLER_REFERER 'referer': Headers.SELLER_REFERER
} }
POST_HEADERS = {
'content-type': 'application/json'
}
class URLHeaders: class URLHeaders:
# combine base and seller headers # combine base and seller headers
ORDER_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS} ORDER_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS}
POST_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS, **RequestHeaders.POST_HEADERS}
class RequestsUtil: class RequestsUtil:
def __init__(self, browser_type: Browser = Browser.BRAVE): def __init__(self, browser_type: Browser = Browser.BRAVE):
@ -113,19 +117,18 @@ class RequestsUtil:
"""Rate limit requests by waiting for a specified time between requests""" """Rate limit requests by waiting for a specified time between requests"""
time_diff = (datetime.now() - self.previous_request_time).total_seconds() time_diff = (datetime.now() - self.previous_request_time).total_seconds()
if time_diff < time_between_requests: if time_diff < time_between_requests:
# logger.info(f"Waiting {time_between_requests - time_diff} seconds before next request...")
time.sleep(time_between_requests - time_diff) time.sleep(time_between_requests - time_diff)
def send_request(self, url: str, method: str, cookies: dict, data=None) -> requests.Response: def send_request(self, url: str, method: str, cookies: dict, data=None, json=None) -> requests.Response:
"""Send a request with the specified cookies""" """Send a request with the specified cookies"""
headers = self.set_headers(url) headers = self.set_headers(url, method)
if not headers: if not headers:
raise ValueError("Headers not set") raise ValueError("Headers not set")
try: try:
self.rate_limit() self.rate_limit()
response = requests.request(method, url, headers=headers, cookies=cookies, data=data) response = requests.request(method, url, headers=headers, cookies=cookies, data=data, json=json)
response.raise_for_status() response.raise_for_status()
self.previous_request_time = datetime.now() self.previous_request_time = datetime.now()
@ -135,57 +138,12 @@ class RequestsUtil:
logger.error(f"Request failed: {str(e)}") logger.error(f"Request failed: {str(e)}")
return None return None
def set_headers(self, url: str): def set_headers(self, url: str, method: str) -> Dict:
# use tcgplayerendpoints enum to set headers where url partially matches enum value # use tcgplayerendpoints enum to set headers where url partially matches enum value
for endpoint in TCGPlayerEndpoints: for endpoint in TCGPlayerEndpoints:
if endpoint.value in url: if endpoint.value in url and str.upper(method) == "POST":
return URLHeaders.POST_HEADERS
elif endpoint.value in url:
return URLHeaders.ORDER_HEADERS return URLHeaders.ORDER_HEADERS
else: else:
raise ValueError(f"Endpoint not found in TCGPlayerEndpoints: {url}") raise ValueError(f"Endpoint not found in TCGPlayerEndpoints: {url}")
def old_set_headers(self, method: str) -> Dict:
base_headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'accept-language': 'en-US,en;q=0.8',
'priority': 'u=0, i',
'referer': 'https://store.tcgplayer.com/admin/pricing',
'sec-ch-ua': '"Not A(Brand";v="8", "Chromium";v="132", "Brave";v="132"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'same-origin',
'sec-fetch-user': '?1',
'sec-gpc': '1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
}
if method == 'POST':
post_headers = {
'cache-control': 'max-age=0',
'content-type': 'application/x-www-form-urlencoded',
'origin': 'https://store.tcgplayer.com'
}
base_headers.update(post_headers)
return base_headers
"""
curl 'https://order-management-api.tcgplayer.com/orders/E576ED4C-38871F-B0277?api-version=2.0' \
-H 'accept: application/json, text/plain, */*' \
-H 'accept-language: en-US,en;q=0.8' \
-b 'tcgpartner=PK=TRADECARDS&M=1; valid=set=true; product-display-settings=sort=price+shipping&size=10; TCG_Data=M=1&SearchGameNameID=magic; tcg-uuid=ab16b5f8-dd66-446d-b217-d394328a5cf1; setting=CD=US&M=1; tracking-preferences={%22version%22:1%2C%22destinations%22:{%22Actions%20Amplitude%22:false%2C%22AdWords%22:false%2C%22Google%20AdWords%20New%22:false%2C%22Google%20Enhanced%20Conversions%22:false%2C%22Google%20Tag%20Manager%22:false%2C%22Impact%20Partnership%20Cloud%22:false%2C%22Optimizely%22:false}%2C%22custom%22:{%22advertising%22:false%2C%22functional%22:false%2C%22marketingAndAnalytics%22:false}}; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; TCGAuthTicket_Production=F453BFF2FA3FAA3D1ACA23F319314F8273713DECEB06C69BB7C208A77C81559B46E1AA22A7E70FABC8D7F681A423C86870FAE318B76048CDE7BF6D73D220631B899BEBA86C422E1EBBF2ACD1921E0846F708AFE203C844031364E13B047465E7B41CB6460E4F4AAB278B614445B93E722E976688; BuyerRevalidationKey=; ASP.NET_SessionId=oouwzrh3jkhdrmaioooqhr4k; TCG_VisitorKey=431efcca-2d5b-404d-a04f-3ae979696051; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; fileDownloadToken=1740145585435; spDisabledUIFeatures=' \
-H 'origin: https://sellerportal.tcgplayer.com' \
-H 'priority: u=1, i' \
-H 'referer: https://sellerportal.tcgplayer.com/' \
-H 'sec-ch-ua: "Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-site' \
-H 'sec-gpc: 1' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'
"""

View File

@ -19,6 +19,3 @@ curl -X POST "http://192.168.1.41:8000/api/boxes/d95d26a8-1f82-47f2-89fa-3f88a46
curl -X POST "http://192.168.1.41:8000/api/processOrders" \ curl -X POST "http://192.168.1.41:8000/api/processOrders" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"order_ids": ["E576ED4C-A472E7-36237","E576ED4C-16EDF6-A340B","E576ED4C-A25B9C-A8855","E576ED4C-0E1A20-C6350","E576ED4C-9E8C21-3A249","E576ED4C-3825F0-5A5CC","E576ED4C-628925-6348B","E576ED4C-5F4314-6E3D2","E576ED4C-60E0B9-69D1D","E576ED4C-4BEC42-B2D0A","E576ED4C-5253F2-E2E16","E576ED4C-C08EA2-F51B4","E576ED4C-EE350E-BA82C","E576ED4C-CB067C-21150","E576ED4C-85DE3E-4E518","E576ED4C-27DB4A-A7729","E576ED4C-91A537-2AEBA","E576ED4C-3961D0-4F5A9","E576ED4C-EB7B7D-DBE0D","E576ED4C-1F9576-A9351","E576ED4C-7EBF1E-6FDB9","E576ED4C-F549E2-C558B","E576ED4C-215B45-4F177","E576ED4C-572FAA-004F7","E576ED4C-9D5F33-1A3C4","E576ED4C-87276B-63EC8","E576ED4C-2143E7-4DE1B","E576ED4C-41E56A-04D55","E576ED4C-789397-BF6AD","E576ED4C-2F3F46-154FE","E576ED4C-EFCBEE-3FE93","E576ED4C-3ADBAE-7CA1B","E576ED4C-D9F68F-A5E6F","E576ED4C-DEA6E2-8B590","E576ED4C-86D96B-DC5C4","E576ED4C-EDFABA-67C3C","E576ED4C-C57373-3F638","E576ED4C-B2C2B4-FF53B","E576ED4C-3788E5-B3653","E576ED4C-8A573A-BB51B","E576ED4C-497380-63F5C","E576ED4C-A6C3F2-C7BF2","E576ED4C-FAC80B-148F3","E576ED4C-ECF1F3-AF3A4"]}' -d '{"order_ids": ["E576ED4C-A472E7-36237","E576ED4C-16EDF6-A340B","E576ED4C-A25B9C-A8855","E576ED4C-0E1A20-C6350","E576ED4C-9E8C21-3A249","E576ED4C-3825F0-5A5CC","E576ED4C-628925-6348B","E576ED4C-5F4314-6E3D2","E576ED4C-60E0B9-69D1D","E576ED4C-4BEC42-B2D0A","E576ED4C-5253F2-E2E16","E576ED4C-C08EA2-F51B4","E576ED4C-EE350E-BA82C","E576ED4C-CB067C-21150","E576ED4C-85DE3E-4E518","E576ED4C-27DB4A-A7729","E576ED4C-91A537-2AEBA","E576ED4C-3961D0-4F5A9","E576ED4C-EB7B7D-DBE0D","E576ED4C-1F9576-A9351","E576ED4C-7EBF1E-6FDB9","E576ED4C-F549E2-C558B","E576ED4C-215B45-4F177","E576ED4C-572FAA-004F7","E576ED4C-9D5F33-1A3C4","E576ED4C-87276B-63EC8","E576ED4C-2143E7-4DE1B","E576ED4C-41E56A-04D55","E576ED4C-789397-BF6AD","E576ED4C-2F3F46-154FE","E576ED4C-EFCBEE-3FE93","E576ED4C-3ADBAE-7CA1B","E576ED4C-D9F68F-A5E6F","E576ED4C-DEA6E2-8B590","E576ED4C-86D96B-DC5C4","E576ED4C-EDFABA-67C3C","E576ED4C-C57373-3F638","E576ED4C-B2C2B4-FF53B","E576ED4C-3788E5-B3653","E576ED4C-8A573A-BB51B","E576ED4C-497380-63F5C","E576ED4C-A6C3F2-C7BF2","E576ED4C-FAC80B-148F3","E576ED4C-ECF1F3-AF3A4"]}'
asdfasdf