Compare commits
16 Commits
ec6ca4ea08
...
18ceef8351
Author | SHA1 | Date | |
---|---|---|---|
18ceef8351 | |||
87c84fd0a8 | |||
0c78276b12 | |||
47a1b1d3ac | |||
c889a84c34 | |||
1ef706afe5 | |||
3454f24451 | |||
7786655db0 | |||
ed52e7da04 | |||
bf3f4ddb38 | |||
3f53513c36 | |||
d3bd696d67 | |||
113a920da7 | |||
9d11adaf6c | |||
8de5bec523 | |||
0414018099 |
32
.gitea/workflows/deploy.yaml
Normal file
32
.gitea/workflows/deploy.yaml
Normal 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
|
@ -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 ###
|
@ -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()
|
||||||
|
|
||||||
|
@ -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):
|
||||||
processed_orders = []
|
# get last 25 orders from tcgplayer
|
||||||
for order_id in orders:
|
orders = self.get_orders(size=100)
|
||||||
order = self.get_order(order_id)
|
if orders:
|
||||||
if order:
|
# get list of order ids
|
||||||
self.save_order(order)
|
order_ids = [order['orderNumber'] for order in orders['orders']]
|
||||||
processed_orders.append(order_id)
|
# get a list of order ids that are not in the database
|
||||||
return processed_orders
|
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 = []
|
||||||
|
if new_order_ids:
|
||||||
|
logger.info(f"Processing {len(new_order_ids)} new orders")
|
||||||
|
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)
|
||||||
|
processed_orders.append(order['orderNumber'])
|
||||||
|
logger.info(f"Processed {len(processed_orders)} new 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
|
||||||
|
@ -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'
|
|
||||||
"""
|
|
@ -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
|
|
Loading…
x
Reference in New Issue
Block a user