-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.py
More file actions
155 lines (129 loc) · 4.52 KB
/
Copy pathapi.py
File metadata and controls
155 lines (129 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from typing import Optional
from fastapi import FastAPI, Query, HTTPException
from pydantic import BaseModel
from scraper import get_game, Sale, PricePoint, GameData
app = FastAPI(title="Unofficial PriceCharting API")
class SaleOut(BaseModel):
date: str
title: str
price: Optional[float]
url: Optional[str]
class PricePointOut(BaseModel):
price: Optional[float]
change: Optional[float]
volume: Optional[str]
class GameOut(BaseModel):
product_id: Optional[int]
name: str
console: str
console_slug: str
slug: str
image: Optional[str]
loose: Optional[PricePointOut]
complete: Optional[PricePointOut]
new: Optional[PricePointOut]
graded: Optional[PricePointOut]
box_only: Optional[PricePointOut]
manual_only: Optional[PricePointOut]
chart_data: Optional[dict]
recent_sales: Optional[dict[str, list[SaleOut]]]
cached: bool # Whether data came from cache
def _to_out(data: GameData) -> GameOut:
def pp(p: Optional[PricePoint]) -> Optional[PricePointOut]:
if p is None:
return None
return PricePointOut(price=p.price, change=p.change, volume=p.volume)
def sl(s: Optional[Sale]) -> Optional[SaleOut]:
if s is None:
return None
return SaleOut(date=s.date, title=s.title, price=s.price, url=s.url)
return GameOut(
product_id=data.product_id,
name=data.name,
console=data.console,
console_slug=data.console_slug,
slug=data.slug,
image=data.image,
loose=pp(data.loose),
complete=pp(data.complete),
new=pp(data.new),
graded=pp(data.graded),
box_only=pp(data.box_only),
manual_only=pp(data.manual_only),
chart_data=data.chart_data,
recent_sales={k: [sl(s) for s in v] for k, v in (data.recent_sales or {}).items()},
cached=data.cached,
)
@app.get("/game/{game_id:path}", response_model=GameOut)
def read_game(
game_id: str,
cookie: Optional[str] = None,
):
"""
Fetch a game by its PriceCharting slug.
* `nintendo-3ds/super-smash-bros-for-nintendo-3ds` — full id
* `super-smash-bros-for-nintendo-3ds` — partial id (falls back to search)
"""
cookies = {}
if cookie:
for part in cookie.split(";"):
if "=" in part:
k, v = part.split("=", 1)
cookies[k.strip()] = v.strip()
try:
data = get_game(game_id, cookies=cookies or None)
except ValueError as exc:
raise HTTPException(status_code=404, detail=str(exc))
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))
return _to_out(data)
@app.get("/search")
def search(
q: str,
cookie: Optional[str] = None,
):
"""Search for games by title."""
from scraper import BASE, HEADERS
import httpx
from bs4 import BeautifulSoup
cookies = {}
if cookie:
for part in cookie.split(";"):
if "=" in part:
k, v = part.split("=", 1)
cookies[k.strip()] = v.strip()
jar = httpx.Cookies()
if cookies:
for k, v in cookies.items():
jar.set(k, v)
with httpx.Client(headers=HEADERS, cookies=jar, follow_redirects=True, timeout=20) as client:
r = client.get(f"{BASE}/search-products", params={"type": "prices", "q": q, "go": "Go"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
results = []
for tr in soup.select("#search-results tbody tr"):
title_a = tr.select_one("td.title a")
console_a = tr.select_one("td.console a")
used = tr.select_one("td.used_price span.js-price")
cib = tr.select_one("td.cib_price span.js-price")
new = tr.select_one("td.new_price span.js-price")
img = tr.select_one("td.image img")
if not title_a:
continue
href = title_a.get("href", "")
slug = ""
if "/game/" in href:
slug = href.split("/game/", 1)[1]
results.append({
"name": title_a.get_text(strip=True),
"console": console_a.get_text(strip=True) if console_a else None,
"slug": slug,
"url": href,
"image": img.get("src") if img else None,
"prices": {
"loose": used.get_text(strip=True) if used else None,
"complete": cib.get_text(strip=True) if cib else None,
"new": new.get_text(strip=True) if new else None,
},
})
return {"query": q, "results": results}