Compare commits
No commits in common. "544f789e2e453e6ccbf46a76134894da1b05326e" and "721b26ce9725b462afec179add4c24eeea7697ea" have entirely different histories.
544f789e2e
...
721b26ce97
2
.gitignore
vendored
2
.gitignore
vendored
@ -174,5 +174,3 @@ temp/
|
||||
.DS_Store
|
||||
*.db-journal
|
||||
cookies/
|
||||
alembic/versions/*
|
||||
*.csv
|
@ -328,73 +328,6 @@ class TCGPlayerGroups(Base):
|
||||
modified_on = Column(String)
|
||||
category_id = Column(Integer)
|
||||
|
||||
class Orders(Base):
|
||||
__tablename__ = 'orders'
|
||||
|
||||
id = Column(String, primary_key=True)
|
||||
order_id = Column(String, unique=True)
|
||||
buyer_name = Column(String)
|
||||
recipient_name = Column(String)
|
||||
recipient_address_one = Column(String)
|
||||
recipient_address_two = Column(String)
|
||||
recipient_city = Column(String)
|
||||
recipient_state = Column(String)
|
||||
recipient_zip = Column(String)
|
||||
recipient_country = Column(String)
|
||||
order_date = Column(String)
|
||||
status = Column(String)
|
||||
num_products = Column(Integer)
|
||||
num_cards = Column(Integer)
|
||||
product_amount = Column(Float)
|
||||
shipping_amount = Column(Float)
|
||||
gross_amount = Column(Float)
|
||||
fee_amount = Column(Float)
|
||||
net_amount = Column(Float)
|
||||
direct_fee_amount = Column(Float)
|
||||
date_created = Column(DateTime, default=datetime.now)
|
||||
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class OrderProducts(Base):
|
||||
__tablename__ = 'order_products'
|
||||
|
||||
id = Column(String, primary_key=True)
|
||||
order_id = Column(String, ForeignKey('orders.id'))
|
||||
product_id = Column(String, ForeignKey('products.id'))
|
||||
quantity = Column(Integer)
|
||||
unit_price = Column(Float)
|
||||
|
||||
class APIPricing(Base):
|
||||
__tablename__ = 'api_pricing'
|
||||
|
||||
id = Column(String, primary_key=True)
|
||||
product_id = Column(String, ForeignKey('products.id'))
|
||||
pricing_data = Column(String)
|
||||
date_created = Column(DateTime, default=datetime.now)
|
||||
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class TCGPlayerInventory(Base):
|
||||
__tablename__ = 'tcgplayer_inventory'
|
||||
|
||||
id = Column(String, primary_key=True)
|
||||
tcgplayer_id = Column(Integer)
|
||||
product_line = Column(String)
|
||||
set_name = Column(String)
|
||||
product_name = Column(String)
|
||||
title = Column(String)
|
||||
number = Column(String)
|
||||
rarity = Column(String)
|
||||
condition = Column(String)
|
||||
tcg_market_price = Column(Float)
|
||||
tcg_direct_low = Column(Float)
|
||||
tcg_low_price_with_shipping = Column(Float)
|
||||
tcg_low_price = Column(Float)
|
||||
total_quantity = Column(Integer)
|
||||
add_to_quantity = Column(Integer)
|
||||
tcg_marketplace_price = Column(Float)
|
||||
photo_url = Column(String)
|
||||
date_created = Column(DateTime, default=datetime.now)
|
||||
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
# enums
|
||||
|
||||
class RarityEnum(str, Enum):
|
||||
|
@ -10,7 +10,6 @@ from app.services.product import ProductService
|
||||
from app.services.inventory import InventoryService
|
||||
from app.services.task import TaskService
|
||||
from app.services.storage import StorageService
|
||||
from app.services.tcgplayer_api import TCGPlayerAPIService
|
||||
from app.db.database import get_db
|
||||
from app.schemas.file import CreateFileRequest
|
||||
from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
|
||||
@ -19,10 +18,6 @@ from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxReq
|
||||
DB = Annotated[Session, Depends(get_db)]
|
||||
|
||||
# Base Services (no dependencies besides DB)
|
||||
def get_tcgplayer_api_service(db: DB) -> TCGPlayerAPIService:
|
||||
"""TCGPlayerAPIService with only database dependency"""
|
||||
return TCGPlayerAPIService(db)
|
||||
|
||||
def get_file_service(db: DB) -> FileService:
|
||||
"""FileService with only database dependency"""
|
||||
return FileService(db)
|
||||
@ -66,11 +61,10 @@ def get_box_service(
|
||||
def get_task_service(
|
||||
db: DB,
|
||||
product_service: Annotated[ProductService, Depends(get_product_service)],
|
||||
pricing_service: Annotated[PricingService, Depends(get_pricing_service)],
|
||||
tcgplayer_api_service: Annotated[TCGPlayerAPIService, Depends(get_tcgplayer_api_service)]
|
||||
pricing_service: Annotated[PricingService, Depends(get_pricing_service)]
|
||||
) -> TaskService:
|
||||
"""TaskService depends on ProductService and TCGPlayerService"""
|
||||
return TaskService(db, product_service, pricing_service, tcgplayer_api_service)
|
||||
return TaskService(db, product_service, pricing_service)
|
||||
|
||||
# Form data dependencies
|
||||
def get_create_file_metadata(
|
||||
|
@ -15,7 +15,6 @@ from app.dependencies import (
|
||||
get_product_service,
|
||||
get_storage_service,
|
||||
get_inventory_service,
|
||||
get_tcgplayer_api_service
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
@ -70,8 +69,7 @@ async def startup_event():
|
||||
tcgplayer_service = get_tcgplayer_service(db, file_service)
|
||||
pricing_service = get_pricing_service(db, file_service, tcgplayer_service)
|
||||
product_service = get_product_service(db, file_service, tcgplayer_service, storage_service)
|
||||
tcgplayer_api_service = get_tcgplayer_api_service(db)
|
||||
task_service = get_task_service(db, product_service, pricing_service, tcgplayer_api_service)
|
||||
task_service = get_task_service(db, product_service, pricing_service)
|
||||
|
||||
# Start task service
|
||||
await task_service.start()
|
||||
|
@ -25,12 +25,10 @@ from app.schemas.box import (
|
||||
CreateOpenBoxResponse,
|
||||
OpenBoxSchema
|
||||
)
|
||||
from app.schemas.orders import ProcessOrdersResponse
|
||||
from app.services.file import FileService
|
||||
from app.services.box import BoxService
|
||||
from app.services.task import TaskService
|
||||
from app.services.pricing import PricingService
|
||||
from app.services.tcgplayer_api import TCGPlayerAPIService
|
||||
from app.dependencies import (
|
||||
get_file_service,
|
||||
get_box_service,
|
||||
@ -39,8 +37,7 @@ from app.dependencies import (
|
||||
get_box_data,
|
||||
get_box_update_data,
|
||||
get_open_box_data,
|
||||
get_pricing_service,
|
||||
get_tcgplayer_api_service
|
||||
get_pricing_service
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -316,23 +313,3 @@ async def update_cookies(
|
||||
status_code=500,
|
||||
detail=f"Failed to update cookies: {str(e)}"
|
||||
)
|
||||
|
||||
class TCGPlayerOrderRequest(BaseModel):
|
||||
order_ids: List[str]
|
||||
|
||||
@router.post("/processOrders", response_model=ProcessOrdersResponse)
|
||||
async def process_orders(
|
||||
body: TCGPlayerOrderRequest,
|
||||
tcgplayer_api_service: TCGPlayerAPIService = Depends(get_tcgplayer_api_service),
|
||||
) -> ProcessOrdersResponse:
|
||||
"""Process TCGPlayer orders."""
|
||||
try:
|
||||
orders = tcgplayer_api_service.process_orders(body.order_ids)
|
||||
return ProcessOrdersResponse(
|
||||
status_code=200,
|
||||
success=True,
|
||||
orders=orders
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Process orders failed: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
19
app/schemas/order.py
Normal file
19
app/schemas/order.py
Normal file
@ -0,0 +1,19 @@
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# FILE
|
||||
class OrderSchema(BaseModel):
|
||||
id: str = Field(..., title="id")
|
||||
filename: str = Field(..., title="filename")
|
||||
type: str = Field(..., title="type")
|
||||
filesize_kb: float = Field(..., title="filesize_kb")
|
||||
source: str = Field(..., title="source")
|
||||
status: str = Field(..., title="status")
|
||||
service: Optional[str] = Field(None, title="service")
|
||||
date_created: datetime = Field(..., title="date_created")
|
||||
date_modified: datetime = Field(..., title="date_modified")
|
||||
|
||||
# This enables ORM mode
|
||||
model_config = ConfigDict(from_attributes=True)
|
@ -1,9 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class OrderSchema(BaseModel):
|
||||
order_id: str
|
||||
|
||||
class ProcessOrdersResponse(BaseModel):
|
||||
status_code: int
|
||||
success: bool
|
||||
orders: list[str]
|
@ -1,12 +1,11 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.models import File, CardTCGPlayer, Price, TCGPlayerInventory
|
||||
from app.db.models import File, CardTCGPlayer, Price
|
||||
from app.services.util._dataframe import TCGPlayerPricingRow, DataframeUtil
|
||||
from app.services.file import FileService
|
||||
from app.services.tcgplayer import TCGPlayerService
|
||||
from uuid import uuid4
|
||||
from app.db.utils import db_transaction
|
||||
from typing import List, Dict
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
import pandas as pd
|
||||
import logging
|
||||
|
||||
@ -94,7 +93,6 @@ class PricingService:
|
||||
|
||||
def cron_load_prices(self, file: File = None):
|
||||
file_content = self.get_pricing_export_content(file)
|
||||
self.tcgplayer_service.load_tcgplayer_cards(file_content)
|
||||
self.load_pricing_csv_content_to_db(file_content)
|
||||
|
||||
def get_all_prices_for_products(self, product_ids: List[str]) -> Dict[str, Dict[str, float]]:
|
||||
@ -117,52 +115,34 @@ class PricingService:
|
||||
|
||||
def default_pricing_algo(self, row: pd.Series) -> pd.Series:
|
||||
"""Default pricing algorithm with complex pricing rules"""
|
||||
tcg_low = row.get('tcg_low_price')
|
||||
tcg_low_shipping = row.get('tcg_low_price_with_shipping')
|
||||
tcg_market_price = row.get('tcg_market_price')
|
||||
|
||||
# Convert input values to Decimal for precise arithmetic
|
||||
tcg_low = Decimal(str(row.get('tcg_low_price'))) if not pd.isna(row.get('tcg_low_price')) else None
|
||||
tcg_low_shipping = Decimal(str(row.get('tcg_low_price_with_shipping'))) if not pd.isna(row.get('tcg_low_price_with_shipping')) else None
|
||||
tcg_market_price = Decimal(str(row.get('tcg_market_price'))) if not pd.isna(row.get('tcg_market_price')) else None
|
||||
total_quantity = str(row.get('total_quantity')) if not pd.isna(row.get('total_quantity')) else "0"
|
||||
added_quantity = str(row.get('add_to_quantity')) if not pd.isna(row.get('add_to_quantity')) else "0"
|
||||
quantity = int(total_quantity) + int(added_quantity)
|
||||
|
||||
if tcg_market_price is None:
|
||||
if pd.isna(tcg_low) or pd.isna(tcg_low_shipping):
|
||||
logger.warning(f"Missing pricing data for row: {row}")
|
||||
row['new_price'] = None
|
||||
return row
|
||||
|
||||
# Define precision for rounding
|
||||
TWO_PLACES = Decimal('0.01')
|
||||
|
||||
# Apply pricing rules
|
||||
if tcg_market_price < Decimal('1') and tcg_market_price > Decimal('0.25'):
|
||||
new_price = tcg_market_price * Decimal('1.25')
|
||||
elif tcg_market_price < Decimal('0.25'):
|
||||
new_price = Decimal('0.25')
|
||||
elif tcg_market_price < Decimal('5'):
|
||||
new_price = tcg_market_price * Decimal('1.08')
|
||||
elif tcg_market_price < Decimal('10'):
|
||||
new_price = tcg_market_price * Decimal('1.06')
|
||||
elif tcg_market_price < Decimal('20'):
|
||||
new_price = tcg_market_price * Decimal('1.0125')
|
||||
elif tcg_market_price < Decimal('50'):
|
||||
new_price = tcg_market_price * Decimal('0.99')
|
||||
elif tcg_market_price < Decimal('100'):
|
||||
new_price = tcg_market_price * Decimal('0.98')
|
||||
if tcg_market_price < 1 and tcg_market_price > 0.25:
|
||||
new_price = tcg_market_price * 1.05
|
||||
elif tcg_market_price < 0.25:
|
||||
new_price = 0.25
|
||||
elif tcg_low < 5 or tcg_low_shipping < 5:
|
||||
new_price = round(tcg_low+((abs(tcg_market_price-tcg_low))*.75), 2)
|
||||
elif tcg_low_shipping > 20:
|
||||
new_price = round(tcg_low_shipping * 1.0125, 2)
|
||||
else:
|
||||
new_price = tcg_market_price * Decimal('1.09')
|
||||
# new_price = round(tcg_low_shipping * 1.08, 2)
|
||||
new_price = round(tcg_market_price * 1.03)
|
||||
# if new price is less than half of market price, set to 90% market
|
||||
if new_price < (tcg_market_price / 2):
|
||||
new_price = round(tcg_market_price * 0.85, 2)
|
||||
if new_price < 0.25:
|
||||
new_price = 0.25
|
||||
|
||||
if new_price < Decimal('0.25'):
|
||||
new_price = Decimal('0.25')
|
||||
|
||||
if quantity > 3:
|
||||
new_price = new_price * Decimal('1.1')
|
||||
|
||||
# Ensure exactly 2 decimal places
|
||||
new_price = new_price.quantize(TWO_PLACES, rounding=ROUND_HALF_UP)
|
||||
|
||||
# Convert back to float or string as needed for your dataframe
|
||||
row['new_price'] = float(new_price)
|
||||
row['new_price'] = new_price
|
||||
return row
|
||||
|
||||
def apply_pricing_algo(self, row: pd.Series, pricing_algo: callable = None) -> pd.Series:
|
||||
@ -216,8 +196,6 @@ class PricingService:
|
||||
# Set marketplace price
|
||||
df['TCG Marketplace Price'] = df['new_price']
|
||||
|
||||
df['Title'] = ''
|
||||
|
||||
column_mapping = {
|
||||
'tcgplayer_id': 'TCGplayer Id',
|
||||
'product_line': 'Product Line',
|
||||
@ -240,19 +218,6 @@ class PricingService:
|
||||
# Now do your column selection
|
||||
df = df[desired_columns]
|
||||
|
||||
if update_type == 'update':
|
||||
with db_transaction(self.db):
|
||||
self.db.query(TCGPlayerInventory).delete()
|
||||
self.db.flush()
|
||||
# copy df to modify before inserting
|
||||
df_copy = df.copy()
|
||||
df_copy['id'] = df_copy.apply(lambda x: str(uuid4()), axis=1)
|
||||
# rename columns lowercase no space
|
||||
df_copy.columns = df_copy.columns.str.lower().str.replace(' ', '_')
|
||||
for index, row in df_copy.iterrows():
|
||||
tcgplayer_inventory = TCGPlayerInventory(**row.to_dict())
|
||||
self.db.add(tcgplayer_inventory)
|
||||
|
||||
# remove any rows with no price
|
||||
#df = df[df['TCG Marketplace Price'] != 0]
|
||||
#df = df[df['TCG Marketplace Price'].notna()]
|
||||
|
@ -10,7 +10,7 @@ import io
|
||||
|
||||
# Printer settings
|
||||
printer_model = "QL-1100"
|
||||
backend = 'pyusb'
|
||||
backend = 'pyusb' # Changed from network to USB
|
||||
printer = 'usb://0x04f9:0x20a7'
|
||||
|
||||
def convert_pdf_to_image(pdf_path):
|
||||
@ -45,8 +45,6 @@ def create_address_label(input_data, font_size=30, is_pdf=False):
|
||||
font = ImageFont.truetype("C:\\Windows\\Fonts\\arial.ttf", size=font_size)
|
||||
elif platform.system() == 'Darwin':
|
||||
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", size=font_size)
|
||||
elif platform.system() == 'Linux':
|
||||
font = ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/arial.ttf", size=font_size)
|
||||
|
||||
margin = 20
|
||||
lines = input_data.split('\n')
|
||||
@ -73,8 +71,10 @@ def print_address_label(input_data, font_size=30, is_pdf=False, label_size='29x9
|
||||
if not image:
|
||||
raise Exception("Failed to create label image")
|
||||
|
||||
# For 4x6 shipping labels from Pirate Ship
|
||||
if label_size == '4x6':
|
||||
target_width = 1164
|
||||
# Resize image to fit 4x6 format if needed
|
||||
target_width = 1164 # Adjusted for 4x6 format
|
||||
target_height = 1660
|
||||
image = image.resize((target_width, target_height), Image.LANCZOS)
|
||||
|
||||
@ -92,6 +92,7 @@ def print_address_label(input_data, font_size=30, is_pdf=False, label_size='29x9
|
||||
red=False,
|
||||
dpi_600=False,
|
||||
hq=True,
|
||||
#cut=True
|
||||
cut=False
|
||||
)
|
||||
|
||||
@ -134,23 +135,33 @@ def process_tcg_shipping_export(file_path, require_input=False, font_size=60, pr
|
||||
else:
|
||||
sleep(1)
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Example for regular address label
|
||||
address = """John Doe
|
||||
123 Main Street
|
||||
Apt 4B
|
||||
City, State 12345"""
|
||||
|
||||
# Example for TCG Player export
|
||||
shipping_export_file = "_TCGplayer_ShippingExport_20250201_115949.csv"
|
||||
|
||||
# Example for Pirate Ship PDF
|
||||
pirate_ship_pdf = "C:\\Users\\joshu\\Downloads\\2025-02-10---greg-creek---9400136208070411592215.pdf"
|
||||
|
||||
# Choose which type to process
|
||||
label_type = input("Enter label type (1 for regular, 2 for TCG, 3 for Pirate Ship): ")
|
||||
|
||||
if label_type == "1":
|
||||
address = input("Enter the address to print: ")
|
||||
preview_label(address, font_size=60)
|
||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
||||
if user_input.lower() == 'p':
|
||||
print_address_label(address, font_size=60)
|
||||
|
||||
elif label_type == "2":
|
||||
shipping_export_file = input("Enter the path to the TCG Player shipping export CSV file: ")
|
||||
process_tcg_shipping_export(shipping_export_file, font_size=60, preview=False)
|
||||
|
||||
elif label_type == "3":
|
||||
pirate_ship_pdf = input("Enter the path to the Pirate Ship PDF file: ")
|
||||
process_pirate_ship_pdf(pirate_ship_pdf, preview=True)
|
||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
||||
if user_input.lower() == 'p':
|
||||
|
@ -5,18 +5,16 @@ from sqlalchemy.orm import Session
|
||||
from app.services.product import ProductService
|
||||
from app.db.models import File
|
||||
from app.services.pricing import PricingService
|
||||
from app.services.tcgplayer_api import TCGPlayerAPIService
|
||||
|
||||
|
||||
class TaskService:
|
||||
def __init__(self, db: Session, product_service: ProductService, pricing_service: PricingService, tcgplayer_api_service: TCGPlayerAPIService):
|
||||
def __init__(self, db: Session, product_service: ProductService, pricing_service: PricingService):
|
||||
self.scheduler = BackgroundScheduler()
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.tasks: Dict[str, Callable] = {}
|
||||
self.db = db
|
||||
self.product_service = product_service
|
||||
self.pricing_service = pricing_service
|
||||
self.tcgplayer_api_service = tcgplayer_api_service
|
||||
|
||||
async def start(self):
|
||||
self.scheduler.start()
|
||||
@ -25,9 +23,7 @@ class TaskService:
|
||||
# self.pricing_service.generate_tcgplayer_inventory_update_file_with_pricing(['e20cc342-23cb-4593-89cb-56a0cb3ed3f3'])
|
||||
|
||||
def register_scheduled_tasks(self):
|
||||
self.scheduler.add_job(self.hourly_pricing, 'cron', minute='36')
|
||||
# every 5 hours on the 24th minute
|
||||
#self.scheduler.add_job(self.inventory_pricing, 'cron', hour='*', minute='44')
|
||||
self.scheduler.add_job(self.hourly_pricing, 'cron', minute='45')
|
||||
self.logger.info("Scheduled tasks registered.")
|
||||
|
||||
def hourly_pricing(self):
|
||||
@ -35,9 +31,6 @@ class TaskService:
|
||||
self.pricing_service.cron_load_prices()
|
||||
self.logger.info("Finished hourly pricing task")
|
||||
|
||||
def inventory_pricing(self):
|
||||
self.tcgplayer_api_service.cron_tcgplayer_api_pricing()
|
||||
|
||||
async def process_manabox_file(self, file: File):
|
||||
self.logger.info("Processing ManaBox file")
|
||||
self.product_service.bg_process_manabox_file(file.id)
|
||||
|
@ -21,8 +21,6 @@ import pandas as pd
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from app.schemas.file import CreateFileRequest
|
||||
import os
|
||||
from app.services.util._docker import DockerUtil
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -55,7 +53,6 @@ class TCGPlayerService:
|
||||
self.previous_request_time = None
|
||||
self.df_util = DataframeUtil()
|
||||
self.file_service = file_service
|
||||
self.docker_util = DockerUtil()
|
||||
|
||||
def _insert_groups(self, groups):
|
||||
for group in groups:
|
||||
@ -122,6 +119,47 @@ class TCGPlayerService:
|
||||
logger.error(f"Failed to get browser cookies: {str(e)}")
|
||||
return None
|
||||
|
||||
def is_in_docker(self) -> bool:
|
||||
"""Check if we're running inside a Docker container using multiple methods"""
|
||||
# Method 1: Check cgroup
|
||||
try:
|
||||
with open('/proc/1/cgroup', 'r') as f:
|
||||
content = f.read().lower()
|
||||
if any(container_id in content for container_id in ['docker', 'containerd', 'kubepods']):
|
||||
logger.debug("Docker detected via cgroup")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not read cgroup file: {e}")
|
||||
|
||||
# Method 2: Check /.dockerenv file
|
||||
if os.path.exists('/.dockerenv'):
|
||||
logger.debug("Docker detected via /.dockerenv file")
|
||||
return True
|
||||
|
||||
# Method 3: Check environment variables
|
||||
docker_env = any(os.environ.get(var, False) for var in [
|
||||
'DOCKER_CONTAINER',
|
||||
'IN_DOCKER',
|
||||
'KUBERNETES_SERVICE_HOST', # For k8s
|
||||
'DOCKER_HOST'
|
||||
])
|
||||
if docker_env:
|
||||
logger.debug("Docker detected via environment variables")
|
||||
return True
|
||||
|
||||
# Method 4: Check container runtime
|
||||
try:
|
||||
with open('/proc/self/mountinfo', 'r') as f:
|
||||
content = f.read().lower()
|
||||
if any(rt in content for rt in ['docker', 'containerd', 'kubernetes']):
|
||||
logger.debug("Docker detected via mountinfo")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not read mountinfo: {e}")
|
||||
|
||||
logger.debug("No Docker environment detected")
|
||||
return False
|
||||
|
||||
def _send_request(self, url: str, method: str, data=None, except_302=False) -> requests.Response:
|
||||
"""Send a request with the specified cookies"""
|
||||
# Rate limiting logic
|
||||
@ -135,7 +173,7 @@ class TCGPlayerService:
|
||||
|
||||
# Move cookie initialization outside and make it more explicit
|
||||
if not self.cookies:
|
||||
if self.docker_util.is_in_docker():
|
||||
if self.is_in_docker():
|
||||
logger.debug("Running in Docker - using cookies from file")
|
||||
self.cookies = self.get_cookies_from_file()
|
||||
else:
|
||||
@ -499,49 +537,34 @@ class TCGPlayerService:
|
||||
except SQLAlchemyError as e:
|
||||
raise RuntimeError(f"Failed to retrieve group IDs: {str(e)}")
|
||||
|
||||
def load_tcgplayer_cards(self, file_content):
|
||||
def load_tcgplayer_cards(self) -> File:
|
||||
try:
|
||||
# Get pricing export
|
||||
export_csv_file = self.get_pricing_export_for_all_products()
|
||||
export_csv = self.file_service.get_file_content(export_csv_file.id)
|
||||
|
||||
# load to card tcgplayer
|
||||
self.load_export_csv_to_card_tcgplayer(file_content)
|
||||
self.load_export_csv_to_card_tcgplayer(export_csv, export_csv_file.id)
|
||||
|
||||
return export_csv_file
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load prices: {e}")
|
||||
raise
|
||||
|
||||
def open_box_cards_to_tcgplayer_inventory_df(self, open_box_ids: List[str]) -> pd.DataFrame:
|
||||
# Using sqlalchemy to group and sum quantities for duplicate TCGplayer IDs
|
||||
tcgcards = (self.db.query(
|
||||
CardTCGPlayer.product_id,
|
||||
CardTCGPlayer.tcgplayer_id,
|
||||
CardTCGPlayer.product_line,
|
||||
CardTCGPlayer.set_name,
|
||||
CardTCGPlayer.product_name,
|
||||
CardTCGPlayer.title,
|
||||
CardTCGPlayer.number,
|
||||
CardTCGPlayer.rarity,
|
||||
CardTCGPlayer.condition,
|
||||
func.sum(OpenBoxCard.quantity).label('quantity')
|
||||
)
|
||||
tcgcards = (self.db.query(OpenBoxCard, CardTCGPlayer)
|
||||
.filter(OpenBoxCard.open_box_id.in_(open_box_ids))
|
||||
.join(CardTCGPlayer, OpenBoxCard.card_id == CardTCGPlayer.product_id)
|
||||
.group_by(
|
||||
CardTCGPlayer.tcgplayer_id,
|
||||
CardTCGPlayer.product_id,
|
||||
CardTCGPlayer.product_line,
|
||||
CardTCGPlayer.set_name,
|
||||
CardTCGPlayer.product_name,
|
||||
CardTCGPlayer.title,
|
||||
CardTCGPlayer.number,
|
||||
CardTCGPlayer.rarity,
|
||||
CardTCGPlayer.condition
|
||||
)
|
||||
.all())
|
||||
|
||||
if not tcgcards:
|
||||
return None
|
||||
|
||||
# Create dataframe directly from the query results
|
||||
df = pd.DataFrame(tcgcards,
|
||||
# Create dataframe
|
||||
df = pd.DataFrame([(tcg.product_id, tcg.tcgplayer_id, tcg.product_line, tcg.set_name, tcg.product_name,
|
||||
tcg.title, tcg.number, tcg.rarity, tcg.condition, obc.quantity)
|
||||
for obc, tcg in tcgcards],
|
||||
columns=['product_id', 'tcgplayer_id', 'product_line', 'set_name', 'product_name',
|
||||
'title', 'number', 'rarity', 'condition', 'quantity'])
|
||||
|
||||
|
@ -1,322 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from app.db.models import Orders, OrderProducts, CardTCGPlayer, CardManabox, APIPricing, TCGPlayerInventory
|
||||
from app.services.util._requests import RequestsUtil
|
||||
from app.services.util._docker import DockerUtil
|
||||
from app.db.utils import db_transaction
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime
|
||||
from uuid import uuid4 as uuid
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
class TCGPlayerAPIConfig:
|
||||
"""Configuration for TCGPlayer API"""
|
||||
ORDER_BASE_URL: str = "https://order-management-api.tcgplayer.com/orders"
|
||||
API_VERSION: str = "?api-version=2.0"
|
||||
|
||||
class TCGPlayerAPIService:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.docker_util = DockerUtil()
|
||||
self.requests_util = RequestsUtil()
|
||||
self.is_in_docker = self.docker_util.is_in_docker()
|
||||
self.config = TCGPlayerAPIConfig()
|
||||
self.cookies = self.get_cookies()
|
||||
self.session = None
|
||||
|
||||
def get_cookies(self) -> dict:
|
||||
if self.is_in_docker:
|
||||
return self.requests_util.get_tcgplayer_cookies_from_file()
|
||||
else:
|
||||
return self.requests_util.get_tcgplayer_browser_cookies()
|
||||
|
||||
def get_order(self, order_id: str) -> dict:
|
||||
url = f"{self.config.ORDER_BASE_URL}/{order_id}{self.config.API_VERSION}"
|
||||
response = self.requests_util.send_request(url, method='GET', cookies=self.cookies)
|
||||
if response:
|
||||
return response.json()
|
||||
return None
|
||||
|
||||
def get_product_ids_from_sku(self, sku_ids: list[str]) -> dict:
|
||||
"""Get product IDs from TCGPlayer SKU IDs"""
|
||||
# convert SKU IDs to integers
|
||||
sku_ids = [int(sku_id) for sku_id in sku_ids]
|
||||
tcg_cards = self.db.query(CardTCGPlayer).filter(CardTCGPlayer.tcgplayer_id.in_(sku_ids)).all()
|
||||
return {str(card.tcgplayer_id): card.product_id for card in tcg_cards}
|
||||
|
||||
def save_order(self, order: dict):
|
||||
# check if order exists by order number
|
||||
order_number = order['orderNumber']
|
||||
existing_order = self.db.query(Orders).filter(Orders.order_id == order_number).first()
|
||||
if existing_order:
|
||||
logger.info(f"Order {order_number} already exists in database")
|
||||
return existing_order
|
||||
transaction = order['transaction']
|
||||
shipping = order['shippingAddress']
|
||||
products = order['products']
|
||||
with db_transaction(self.db):
|
||||
db_order = Orders(
|
||||
id = str(uuid()),
|
||||
order_id=order_number,
|
||||
buyer_name=order['buyerName'],
|
||||
recipient_name=shipping['recipientName'],
|
||||
recipient_address_one=shipping['addressOne'],
|
||||
recipient_address_two=shipping['addressTwo'] if 'addressTwo' in shipping else '',
|
||||
recipient_city=shipping['city'],
|
||||
recipient_state=shipping['territory'],
|
||||
recipient_zip=shipping['postalCode'],
|
||||
recipient_country=shipping['country'],
|
||||
order_date=order['createdAt'],
|
||||
status=order['status'],
|
||||
num_products=len(products),
|
||||
num_cards=sum([product['quantity'] for product in products]),
|
||||
product_amount=transaction['productAmount'],
|
||||
shipping_amount=transaction['shippingAmount'],
|
||||
gross_amount=transaction['grossAmount'],
|
||||
fee_amount=transaction['feeAmount'],
|
||||
net_amount=transaction['netAmount'],
|
||||
direct_fee_amount=transaction['directFeeAmount']
|
||||
)
|
||||
self.db.add(db_order)
|
||||
self.db.flush()
|
||||
|
||||
product_ids = [product['skuId'] for product in products]
|
||||
sku_to_product_id_mapping = self.get_product_ids_from_sku(product_ids)
|
||||
order_products = []
|
||||
for product in products:
|
||||
product_id = sku_to_product_id_mapping.get(product['skuId'])
|
||||
if product_id:
|
||||
order_products.append(
|
||||
OrderProducts(
|
||||
id=str(uuid()),
|
||||
order_id=db_order.id,
|
||||
product_id=product_id,
|
||||
quantity=product['quantity'],
|
||||
unit_price=product['unitPrice']
|
||||
)
|
||||
)
|
||||
self.db.add_all(order_products)
|
||||
return db_order
|
||||
|
||||
def process_orders(self, orders: list[str]):
|
||||
processed_orders = []
|
||||
for order_id in orders:
|
||||
order = self.get_order(order_id)
|
||||
if order:
|
||||
self.save_order(order)
|
||||
processed_orders.append(order_id)
|
||||
return processed_orders
|
||||
|
||||
def get_scryfall_data(self, scryfall_id: str):
|
||||
url = f"https://api.scryfall.com/cards/{scryfall_id}?format=json"
|
||||
response = self.requests_util.bare_request(url, method='GET')
|
||||
return response
|
||||
|
||||
def get_tcgplayer_pricing_data(self, tcgplayer_id: str):
|
||||
if not self.session:
|
||||
self.session = self.requests_util.get_session()
|
||||
response = self.session.get("https://tcgplayer.com")
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
'accept-language': 'en-US,en;q=0.8',
|
||||
'priority': 'u=1, i',
|
||||
'sec-ch-ua': '"Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"macOS"',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-site',
|
||||
'sec-gpc': '1',
|
||||
'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"
|
||||
#self.requests_util.rate_limit()
|
||||
response = self.session.get(url, headers=headers)
|
||||
self.requests_util.previous_request_time = datetime.now()
|
||||
return response
|
||||
|
||||
# pricing
|
||||
def get_tcgplayer_pricing_data_for_product(self, product_id: str):
|
||||
# get tcgplayer pricing data for a single card by product id
|
||||
# product_id to manabox card
|
||||
manabox_card = self.db.query(CardManabox).filter(CardManabox.product_id == product_id).first()
|
||||
tcgplayer_card = self.db.query(CardTCGPlayer).filter(CardTCGPlayer.product_id == product_id).first()
|
||||
if not manabox_card or not tcgplayer_card:
|
||||
logger.warning(f"Card with product id {product_id} missing in either Manabox or TCGPlayer")
|
||||
return None
|
||||
mbfoil = manabox_card.foil
|
||||
if str.lower(mbfoil) == 'foil':
|
||||
logger.warning(f"Card with product id {product_id} is foil, skipping")
|
||||
return None
|
||||
# get scryfall id, tcgplayer id, and tcgplayer sku
|
||||
scryfall_id = manabox_card.scryfall_id
|
||||
tcgplayer_sku = tcgplayer_card.tcgplayer_id
|
||||
tcgplayer_id = self.get_scryfall_data(scryfall_id).json().get('tcgplayer_id')
|
||||
tcgplayer_pricing = self.get_tcgplayer_pricing_data(tcgplayer_id)
|
||||
if not tcgplayer_pricing:
|
||||
logger.warning(f"TCGPlayer pricing data not found for product id {product_id}")
|
||||
return None
|
||||
else:
|
||||
logger.info(f"TCGPlayer pricing data found for product id {product_id}")
|
||||
return tcgplayer_pricing.json()
|
||||
|
||||
def save_tcgplayer_pricing_data(self, product_id: str, pricing_data: dict):
|
||||
# convert to json
|
||||
pricing_data_json = json.dumps(pricing_data)
|
||||
with db_transaction(self.db):
|
||||
pricing_record = APIPricing(
|
||||
id=str(uuid()),
|
||||
product_id=product_id,
|
||||
pricing_data=str(pricing_data_json)
|
||||
)
|
||||
self.db.add(pricing_record)
|
||||
|
||||
def cron_tcgplayer_api_pricing(self):
|
||||
# Join both tables but retrieve both objects
|
||||
results = self.db.query(TCGPlayerInventory, CardTCGPlayer).join(
|
||||
CardTCGPlayer,
|
||||
TCGPlayerInventory.tcgplayer_id == CardTCGPlayer.tcgplayer_id
|
||||
).all()
|
||||
|
||||
for inventory, card in results:
|
||||
# Now use card.product_id (from CardTCGPlayer)
|
||||
pricing_data = self.get_tcgplayer_pricing_data_for_product(card.product_id)
|
||||
if pricing_data:
|
||||
self.save_tcgplayer_pricing_data(card.product_id, pricing_data)
|
||||
|
||||
|
||||
|
||||
# this one contains nearly everything, use it first
|
||||
# what does score mean? - totally ignore score, it seems related to price and changes based on what is on the page. probably some psy op shit to get you to buy expensive stuff, not useful for us
|
||||
# can i get volatility from here?
|
||||
# no historical data here
|
||||
"""
|
||||
curl 'https://mp-search-api.tcgplayer.com/v2/product/615745/details?mpfev=3279' \
|
||||
-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; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; SearchSortSettings=M=1&ProductSortOption=MinPrice&ProductSortDesc=True&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; tcg-segment-session=1740595460137%257C1740595481177' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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'
|
||||
"""
|
||||
|
||||
# get volatility also
|
||||
"""
|
||||
curl 'https://mpgateway.tcgplayer.com/v1/pricepoints/marketprice/skus/8395586/volatility?mpfev=3279' \
|
||||
-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; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; SearchCriteria=M=1&WantVerifiedSellers=False&WantDirect=False&WantSellersInCart=False; SearchSortSettings=M=1&ProductSortOption=Sales&ProductSortDesc=False&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg-segment-session=1740597680921%257C1740598418227' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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'
|
||||
"""
|
||||
|
||||
# handle historical data later
|
||||
|
||||
|
||||
# detailed range quarter - detailed pricing info for the last quarter. seems simple
|
||||
"""
|
||||
|
||||
"""
|
||||
# listings - lots of stuff here
|
||||
"""
|
||||
QUANTITY OVERVIEW
|
||||
|
||||
curl 'https://mp-search-api.tcgplayer.com/v1/product/615745/listings?mpfev=3279' \
|
||||
-H 'accept: application/json, text/plain, */*' \
|
||||
-H 'accept-language: en-US,en;q=0.8' \
|
||||
-H 'content-type: application/json' \
|
||||
-b 'tcgpartner=PK=TRADECARDS&M=1; valid=set=true; product-display-settings=sort=price+shipping&size=10; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; SearchSortSettings=M=1&ProductSortOption=MinPrice&ProductSortDesc=True&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; tcg-segment-session=1740595460137%257C1740595481430' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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' \
|
||||
--data-raw '{"filters":{"term":{"sellerStatus":"Live","channelId":0,"language":["English"],"direct-seller":true,"directProduct":true,"listingType":"standard"},"range":{"quantity":{"gte":1},"direct-inventory":{"gte":1}},"exclude":{"channelExclusion":0,"listingType":"custom"}},"from":0,"size":1,"context":{"shippingCountry":"US","cart":{}},"sort":{"field":"price+shipping","order":"asc"}}'
|
||||
|
||||
|
||||
AGGREGATION AND SOME SPECIFIC DATA IDK THIS MIGHT BE A GOOD ONE
|
||||
|
||||
curl 'https://mp-search-api.tcgplayer.com/v1/product/615745/listings?mpfev=3279' \
|
||||
-H 'accept: application/json, text/plain, */*' \
|
||||
-H 'accept-language: en-US,en;q=0.8' \
|
||||
-H 'content-type: application/json' \
|
||||
-b 'tcgpartner=PK=TRADECARDS&M=1; valid=set=true; product-display-settings=sort=price+shipping&size=10; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; SearchSortSettings=M=1&ProductSortOption=MinPrice&ProductSortDesc=True&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; tcg-segment-session=1740595460137%257C1740595481430' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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' \
|
||||
--data-raw '{"filters":{"term":{"sellerStatus":"Live","channelId":0,"language":["English"]},"range":{"quantity":{"gte":1}},"exclude":{"channelExclusion":0}},"from":0,"size":10,"sort":{"field":"price+shipping","order":"asc"},"context":{"shippingCountry":"US","cart":{}},"aggregations":["listingType"]}'
|
||||
|
||||
|
||||
AGGREGATION OF RANDOM SHIT IDK
|
||||
|
||||
curl 'https://mp-search-api.tcgplayer.com/v1/product/615745/listings?mpfev=3279' \
|
||||
-H 'accept: application/json, text/plain, */*' \
|
||||
-H 'accept-language: en-US,en;q=0.8' \
|
||||
-H 'content-type: application/json' \
|
||||
-b 'tcgpartner=PK=TRADECARDS&M=1; valid=set=true; product-display-settings=sort=price+shipping&size=10; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; SearchSortSettings=M=1&ProductSortOption=MinPrice&ProductSortDesc=True&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; tcg-segment-session=1740595460137%257C1740595481430' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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' \
|
||||
--data-raw '{"filters":{"term":{"condition":["Near Mint","Lightly Played","Moderately Played","Heavily Played","Damaged"],"printing":["Foil"],"language":["English"],"sellerStatus":"Live"},"range":{"quantity":{"gte":1}},"exclude":{"channelExclusion":0}},"context":{"shippingCountry":"US","cart":{}},"aggregations":["seller-key"],"size":0}'
|
||||
|
||||
|
||||
VOLATILITY
|
||||
|
||||
curl 'https://mpgateway.tcgplayer.com/v1/pricepoints/marketprice/skus/8547894/volatility?mpfev=3279' \
|
||||
-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; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; spDisabledUIFeatures=orders; SellerProximity=ZipCode=&MaxSellerDistance=1000&IsActive=false; tcg-uuid=613192dc-ecf6-481a-bec1-afdee8686db7; LastSeller=e576ed4c; __RequestVerificationToken=VFv72VLK6McJVzthg8O-41p7BNkdoW2jQAlDAu-ylO39qfzCddRi2-7bWiH4qloc8Vo_ZftOAAa5OhXL3OByFHIdlwY1; TCGAuthTicket_Production=270B0566400C905C51DEAD644E3CDBD634ECBCCC796B1F77717F461EE104FCE101CFAD2A8458319330A0931018A99214D4EA5601E7551E25E2069ACA550BB71775C0A04F30724E2C4E262CB167EAC2C2EB05D15F9EA08363FC6455B94654F1F110CF079E24201C3B8CEF26762423D8CAA71DDF7B; ASP.NET_SessionId=5ycv15jf0mon3l5adodmkog5; StoreSaveForLater_PRODUCTION=SFLK=a167bf88521f4d0fbeb7497a7ed74629&Ignore=false; TCG_VisitorKey=81fe992f-9a12-4926-a417-7815c4f94edd; setting=CD=US&M=1; SearchSortSettings=M=1&ProductSortOption=MinPrice&ProductSortDesc=True&PriceSortOption=Shipping&ProductResultDisplay=grid; tcg_analytics_previousPageData=%7B%22title%22%3A%22Seller%20Feedback%22%2C%22href%22%3A%22https%3A%2F%2Fshop.tcgplayer.com%2Fsellerfeedback%2Fbe27fef9%22%7D; fileDownloadToken=1740499419709; StoreCart_PRODUCTION=CK=b4f8aff616974a12a6b2811129b81ee2&Ignore=false; 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}}; tcg-segment-session=1740595460137%257C1740595481430' \
|
||||
-H 'origin: https://www.tcgplayer.com' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://www.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'
|
||||
"""
|
@ -1,49 +0,0 @@
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DockerUtil:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def is_in_docker(self) -> bool:
|
||||
"""Check if we're running inside a Docker container using multiple methods"""
|
||||
# Method 1: Check cgroup
|
||||
try:
|
||||
with open('/proc/1/cgroup', 'r') as f:
|
||||
content = f.read().lower()
|
||||
if any(container_id in content for container_id in ['docker', 'containerd', 'kubepods']):
|
||||
logger.debug("Docker detected via cgroup")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not read cgroup file: {e}")
|
||||
|
||||
# Method 2: Check /.dockerenv file
|
||||
if os.path.exists('/.dockerenv'):
|
||||
logger.debug("Docker detected via /.dockerenv file")
|
||||
return True
|
||||
|
||||
# Method 3: Check environment variables
|
||||
docker_env = any(os.environ.get(var, False) for var in [
|
||||
'DOCKER_CONTAINER',
|
||||
'IN_DOCKER',
|
||||
'KUBERNETES_SERVICE_HOST', # For k8s
|
||||
'DOCKER_HOST'
|
||||
])
|
||||
if docker_env:
|
||||
logger.debug("Docker detected via environment variables")
|
||||
return True
|
||||
|
||||
# Method 4: Check container runtime
|
||||
try:
|
||||
with open('/proc/self/mountinfo', 'r') as f:
|
||||
content = f.read().lower()
|
||||
if any(rt in content for rt in ['docker', 'containerd', 'kubernetes']):
|
||||
logger.debug("Docker detected via mountinfo")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not read mountinfo: {e}")
|
||||
|
||||
logger.debug("No Docker environment detected")
|
||||
return False
|
@ -1,191 +0,0 @@
|
||||
from typing import Dict, Optional
|
||||
from app.services.util._docker import DockerUtil
|
||||
from enum import Enum
|
||||
import browser_cookie3
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import time
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Browser(Enum):
|
||||
"""Supported browser types for cookie extraction"""
|
||||
BRAVE = "brave"
|
||||
CHROME = "chrome"
|
||||
FIREFOX = "firefox"
|
||||
|
||||
class Method(Enum):
|
||||
"""Supported HTTP methods"""
|
||||
GET = "GET"
|
||||
POST = "POST"
|
||||
|
||||
class TCGPlayerEndpoints(Enum):
|
||||
"""Supported TCGPlayer API endpoints"""
|
||||
ORDERS = "https://order-management-api.tcgplayer.com/orders"
|
||||
|
||||
class Headers:
|
||||
ACCEPT = 'application/json, text/plain, */*'
|
||||
ACCEPT_ENCODING = 'gzip, deflate, br, zstd'
|
||||
ACCEPT_LANGUAGE = 'en-US,en;q=0.8'
|
||||
PRIORITY = 'u=1, i'
|
||||
SECCHUA = '"Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"'
|
||||
SECCHUA_MOBILE = '?0'
|
||||
SECCHUA_PLATFORM = '"macOS"'
|
||||
SEC_FETCH_DEST = 'empty'
|
||||
SEC_FETCH_MODE = 'cors'
|
||||
SEC_FETCH_SITE = 'same-site'
|
||||
SEC_GPC = '1'
|
||||
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'
|
||||
SELLER_ORIGIN = 'https://sellerportal.tcgplayer.com'
|
||||
SELLER_REFERER = 'https://sellerportal.tcgplayer.com/'
|
||||
|
||||
class RequestHeaders:
|
||||
BASE_HEADERS = {
|
||||
'accept': Headers.ACCEPT,
|
||||
'accept-encoding': Headers.ACCEPT_ENCODING,
|
||||
'accept-language': Headers.ACCEPT_LANGUAGE,
|
||||
'priority': Headers.PRIORITY,
|
||||
'sec-ch-ua': Headers.SECCHUA,
|
||||
'sec-ch-ua-mobile': Headers.SECCHUA_MOBILE,
|
||||
'sec-ch-ua-platform': Headers.SECCHUA_PLATFORM,
|
||||
'sec-fetch-dest': Headers.SEC_FETCH_DEST,
|
||||
'sec-fetch-mode': Headers.SEC_FETCH_MODE,
|
||||
'sec-fetch-site': Headers.SEC_FETCH_SITE,
|
||||
'sec-gpc': Headers.SEC_GPC,
|
||||
'user-agent': Headers.USER_AGENT
|
||||
}
|
||||
SELLER_HEADERS = {
|
||||
'origin': Headers.SELLER_ORIGIN,
|
||||
'referer': Headers.SELLER_REFERER
|
||||
}
|
||||
|
||||
class URLHeaders:
|
||||
# combine base and seller headers
|
||||
ORDER_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS}
|
||||
|
||||
class RequestsUtil:
|
||||
def __init__(self, browser_type: Browser = Browser.BRAVE):
|
||||
self.browser_type = browser_type
|
||||
self.docker_util = DockerUtil()
|
||||
self.previous_request_time = datetime.now()
|
||||
|
||||
def get_session(self, cookies: Dict = None) -> requests.Session:
|
||||
"""Create a session with the specified cookies"""
|
||||
session = requests.Session()
|
||||
if cookies:
|
||||
session.cookies.update(cookies)
|
||||
return session
|
||||
|
||||
def bare_request(self, url: str, method: str, cookies: dict = None, data=None) -> requests.Response:
|
||||
"""Send a request without any additional processing"""
|
||||
try:
|
||||
response = requests.request(method, url, cookies=cookies, data=data)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Request failed: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_tcgplayer_cookies_from_file(self) -> Dict:
|
||||
# check if cookies file exists
|
||||
if not os.path.exists('cookies/tcg_cookies.json'):
|
||||
raise ValueError("Cookies file not found")
|
||||
with open('cookies/tcg_cookies.json', 'r') as f:
|
||||
logger.debug("Loading cookies from file")
|
||||
cookies = json.load(f)
|
||||
return cookies
|
||||
|
||||
def get_tcgplayer_browser_cookies(self) -> Optional[Dict]:
|
||||
"""Retrieve cookies from the specified browser"""
|
||||
try:
|
||||
cookie_getter = getattr(browser_cookie3, self.browser_type.value, None)
|
||||
if not cookie_getter:
|
||||
raise ValueError(f"Unsupported browser type: {self.browser_type.value}")
|
||||
return cookie_getter()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get browser cookies: {str(e)}")
|
||||
return None
|
||||
|
||||
def rate_limit(self, time_between_requests: int = 10):
|
||||
"""Rate limit requests by waiting for a specified time between requests"""
|
||||
time_diff = (datetime.now() - self.previous_request_time).total_seconds()
|
||||
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)
|
||||
|
||||
def send_request(self, url: str, method: str, cookies: dict, data=None) -> requests.Response:
|
||||
"""Send a request with the specified cookies"""
|
||||
|
||||
headers = self.set_headers(url)
|
||||
if not headers:
|
||||
raise ValueError("Headers not set")
|
||||
|
||||
try:
|
||||
self.rate_limit()
|
||||
response = requests.request(method, url, headers=headers, cookies=cookies, data=data)
|
||||
response.raise_for_status()
|
||||
self.previous_request_time = datetime.now()
|
||||
|
||||
return response
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Request failed: {str(e)}")
|
||||
return None
|
||||
|
||||
def set_headers(self, url: str):
|
||||
# use tcgplayerendpoints enum to set headers where url partially matches enum value
|
||||
for endpoint in TCGPlayerEndpoints:
|
||||
if endpoint.value in url:
|
||||
return URLHeaders.ORDER_HEADERS
|
||||
else:
|
||||
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'
|
||||
"""
|
22
requests.md
22
requests.md
@ -2,20 +2,16 @@ curl -J http://192.168.1.41:8000/api/tcgplayer/inventory/update --remote-name
|
||||
|
||||
curl -J -X POST http://192.168.1.41:8000/api/tcgplayer/inventory/add \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"open_box_ids": ["fcc28f9e-bc8b-4e89-a46e-7da746a96a5b","4a09930f-1830-4b8b-8006-a391e1adca66"]}' \
|
||||
-d '{"open_box_ids": ["fb629d9d-13d2-405e-9a69-6c44294d55de"]}' \
|
||||
--remote-name
|
||||
|
||||
curl -X POST http://192.168.1.41:8000/api/boxes \
|
||||
-F "type=collector" \
|
||||
-F "set_code=TDM" \
|
||||
-F "sku=1234" \
|
||||
-F "num_cards_expected=123"
|
||||
-F "type=draft" \
|
||||
-F "set_code=CLB" \
|
||||
-F "sku=195166181127" \
|
||||
-F "num_cards_expected=480"
|
||||
|
||||
curl -X POST "http://192.168.1.41:8000/api/boxes/d95d26a8-1f82-47f2-89fa-3f88a4636823/open" \
|
||||
-F "product_id=d95d26a8-1f82-47f2-89fa-3f88a4636823" \
|
||||
-F "file_ids=0f29efd2-448c-4a05-ba49-1420fd3d524b" \
|
||||
-F "date_opened=2025-04-04"
|
||||
|
||||
curl -X POST "http://192.168.1.41:8000/api/processOrders" \
|
||||
-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"]}'
|
||||
curl -X POST "http://192.168.1.41:8000/api/boxes/0d31b9c3-3093-438a-9e8c-b6b70a2d437e/open" \
|
||||
-F "product_id=0d31b9c3-3093-438a-9e8c-b6b70a2d437e" \
|
||||
-F "file_ids=bb4a022c-5427-49b5-b57c-0147b5e9c4a9" \
|
||||
-F "date_opened=2025-02-15"
|
Loading…
x
Reference in New Issue
Block a user