Skip to content

Commit 3cf3b72

Browse files
committed
NicePrice v1.0.0 - GG.deals price comparison plugin for Millennium
0 parents  commit 3cf3b72

11 files changed

Lines changed: 5281 additions & 0 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
.millennium/
3+
*.log
4+
settings.json

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# NicePrice
2+
3+
Compare game prices across 30+ stores directly inside the Steam client. See the best retail and keyshop deals at a glance, powered by [GG.deals](https://gg.deals/).
4+
5+
## Features
6+
7+
- Best retail and keyshop prices on every game page
8+
- Historical lowest prices
9+
- Direct links to GG.deals for full deal listings
10+
- Works in both Steam Library and Store pages
11+
- Auto-adapts to any Steam theme (reads colors from the DOM at runtime)
12+
- Your API key stays local — stored in `settings.json`, never sent anywhere except GG.deals
13+
14+
## Installation
15+
16+
1. Install [Millennium](https://steambrew.app/)
17+
2. Clone this repo into your Millennium plugins folder:
18+
```
19+
C:\Program Files (x86)\Steam\plugins\NicePrice
20+
```
21+
3. Install dependencies and build:
22+
```bash
23+
npm install
24+
npm run build
25+
```
26+
4. Restart Steam
27+
5. Open NicePrice settings and add your GG.deals API key
28+
29+
## API Key
30+
31+
NicePrice uses the [GG.deals API](https://gg.deals/api/) which requires a free API key:
32+
33+
1. Go to [gg.deals/api](https://gg.deals/api/)
34+
2. Create an account and confirm your email
35+
3. Copy your API key
36+
4. Paste it in the NicePrice settings panel inside Steam
37+
38+
The key is stored locally in `settings.json` inside the plugin folder. It is never shared with anyone other than GG.deals.
39+
40+
## Project Structure
41+
42+
```
43+
NicePrice/
44+
plugin.json Millennium manifest
45+
package.json Dependencies and build scripts
46+
backend/
47+
main.lua API calls, settings persistence
48+
frontend/
49+
index.tsx Library widget (SharedJSContext)
50+
webkit/
51+
index.tsx Store page widget (browser context)
52+
```
53+
54+
## Credits
55+
56+
- Price data by [GG.deals](https://gg.deals/)
57+
- Built on [Millennium](https://steambrew.app/)
58+
59+
## License
60+
61+
MIT

backend/main.lua

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
local http = require("http")
2+
local json = require("json")
3+
local logger = require("logger")
4+
local millennium = require("millennium")
5+
6+
local GG_DEALS_API = "https://api.gg.deals/v1/prices/by-steam-app-id/"
7+
8+
local function get_settings_path()
9+
return millennium.get_install_path() .. "/settings.json"
10+
end
11+
12+
local function load_settings()
13+
local file = io.open(get_settings_path(), "r")
14+
if not file then return {} end
15+
local content = file:read("*a")
16+
file:close()
17+
local ok, parsed = pcall(json.decode, content)
18+
if not ok or type(parsed) ~= "table" then return {} end
19+
return parsed
20+
end
21+
22+
local function save_settings(settings)
23+
local file, err = io.open(get_settings_path(), "w")
24+
if not file then
25+
logger:err("NicePrice: failed to write settings: " .. tostring(err))
26+
return false
27+
end
28+
file:write(json.encode(settings))
29+
file:close()
30+
return true
31+
end
32+
33+
function get_api_key()
34+
local settings = load_settings()
35+
return json.encode({ success = true, api_key = settings.api_key or "" })
36+
end
37+
38+
function save_api_key(api_key)
39+
local settings = load_settings()
40+
settings.api_key = api_key or ""
41+
local ok = save_settings(settings)
42+
return json.encode({ success = ok })
43+
end
44+
45+
function fetch_prices(steam_app_id)
46+
local settings = load_settings()
47+
local key = settings.api_key or ""
48+
49+
if key == "" then
50+
return json.encode({ success = false, error = "no_api_key" })
51+
end
52+
53+
local ok, result = pcall(function()
54+
local url = GG_DEALS_API .. "?key=" .. key .. "&ids=" .. tostring(steam_app_id)
55+
local resp, err = http.get(url, { timeout = 10 })
56+
if not resp then
57+
return json.encode({ success = false, error = tostring(err) })
58+
end
59+
if resp.status == 400 then
60+
return json.encode({ success = false, error = "invalid_api_key" })
61+
end
62+
if resp.status == 429 then
63+
return json.encode({ success = false, error = "rate_limited" })
64+
end
65+
if resp.status ~= 200 then
66+
return json.encode({ success = false, error = "HTTP " .. tostring(resp.status) })
67+
end
68+
return resp.body
69+
end)
70+
71+
if not ok then
72+
return json.encode({ success = false, error = tostring(result) })
73+
end
74+
return result
75+
end
76+
77+
local function on_load()
78+
millennium.ready()
79+
logger:info("NicePrice loaded")
80+
end
81+
82+
local function on_unload() end
83+
84+
return {
85+
on_load = on_load,
86+
on_unload = on_unload,
87+
fetch_prices = fetch_prices,
88+
get_api_key = get_api_key,
89+
save_api_key = save_api_key,
90+
}

0 commit comments

Comments
 (0)