itch-jam-scout 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- itch_jam_scout-0.2.0/LICENSE +21 -0
- itch_jam_scout-0.2.0/PKG-INFO +103 -0
- itch_jam_scout-0.2.0/README.md +85 -0
- itch_jam_scout-0.2.0/itch_jam_scout.egg-info/PKG-INFO +103 -0
- itch_jam_scout-0.2.0/itch_jam_scout.egg-info/SOURCES.txt +9 -0
- itch_jam_scout-0.2.0/itch_jam_scout.egg-info/dependency_links.txt +1 -0
- itch_jam_scout-0.2.0/itch_jam_scout.egg-info/entry_points.txt +2 -0
- itch_jam_scout-0.2.0/itch_jam_scout.egg-info/top_level.txt +1 -0
- itch_jam_scout-0.2.0/jam_scout.py +140 -0
- itch_jam_scout-0.2.0/pyproject.toml +29 -0
- itch_jam_scout-0.2.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arcade Forge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: itch-jam-scout
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Read-only CLI that checks an itch.io jam's real phase, deadlines, and pool size from itch's own embedded JSON, instead of the page's countdown text.
|
|
5
|
+
Author: Arcade Forge
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fernforge-arcade/itch-jam-scout
|
|
8
|
+
Keywords: itch.io,game-jam,gamedev,cli
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Games/Entertainment
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# itch-jam-scout
|
|
20
|
+
|
|
21
|
+
A read-only CLI that answers the two questions that actually matter before you
|
|
22
|
+
enter an itch.io game jam: **what phase is it in right now**, and **how big is
|
|
23
|
+
the pool you're competing in**.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install itch-jam-scout
|
|
27
|
+
jam-scout trijam-379 codex-game-jam-2026
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"url": "https://itch.io/jam/trijam-379",
|
|
33
|
+
"start_date": "2026-07-04 00:00:00",
|
|
34
|
+
"end_date": "2026-07-06 00:00:00",
|
|
35
|
+
"voting_end_date": "2026-07-13 00:00:00",
|
|
36
|
+
"phase": "voting open",
|
|
37
|
+
"rating_queue_disclosed": false,
|
|
38
|
+
"coverage_rule_disclosed": false,
|
|
39
|
+
"entry_count_hint": "28 entries"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
No `pip`? It's one file with zero dependencies — `python3 jam_scout.py <jam-slug>` works straight from a clone.
|
|
44
|
+
|
|
45
|
+
## Why this exists
|
|
46
|
+
|
|
47
|
+
itch jam pages show a countdown timer in the page header, and that timer is
|
|
48
|
+
sometimes wrong — hosts extend deadlines after the page ships, and the
|
|
49
|
+
human-readable text doesn't always get updated in step. The one place that
|
|
50
|
+
can't lie is the `I.ViewJam(...)` JSON blob itch embeds in every jam page to
|
|
51
|
+
drive its own countdown widget. This tool parses that blob directly instead
|
|
52
|
+
of trusting the rendered text, so a moved deadline shows up correctly the
|
|
53
|
+
first time you run it, not after you've already miscounted.
|
|
54
|
+
|
|
55
|
+
It also surfaces the join/entry count where itch exposes it. That number
|
|
56
|
+
matters more than people give it credit for: in a jam with 800 entries, your
|
|
57
|
+
own rating activity is noise. In a jam with 20, it's a meaningful fraction of
|
|
58
|
+
total votes — worth knowing before you decide where to spend a Saturday.
|
|
59
|
+
|
|
60
|
+
## What it checks
|
|
61
|
+
|
|
62
|
+
- **Phase** — not started / submissions open / voting open / closed, computed
|
|
63
|
+
against the jam's actual start/end/voting-end timestamps, not the countdown
|
|
64
|
+
text.
|
|
65
|
+
- **Pool size** — join or entry count, when itch's page markup exposes it.
|
|
66
|
+
- **Rating Queue mention** — whether the host's own jam description names
|
|
67
|
+
itch's Rating Queue system in text. This is a text search, not an API flag:
|
|
68
|
+
a `false` means "not mentioned on the page," not "confirmed off." Treat a
|
|
69
|
+
`true` as a real signal and a `false` as no signal either way.
|
|
70
|
+
- **Coverage rule mention** — whether the page states a minimum number of
|
|
71
|
+
ratings you have to give or receive to count as a valid entry. Worth
|
|
72
|
+
checking because jam size alone doesn't predict how many entries actually
|
|
73
|
+
get rated: a 65-entry jam with no enforced minimum left 11 entries at zero
|
|
74
|
+
ratings, while a 485-entry jam with one rated 483 of them. In practice most
|
|
75
|
+
hosts don't put this in the page description even when it's true, so
|
|
76
|
+
expect `false` a lot — read it as "not stated here," not "doesn't exist."
|
|
77
|
+
|
|
78
|
+
## What it doesn't do
|
|
79
|
+
|
|
80
|
+
No login, no rating, no write actions of any kind — it fetches one public
|
|
81
|
+
page and parses it. It can't tell you whether Rating Queue is toggled on for
|
|
82
|
+
a jam that doesn't say so in its description; that toggle only becomes
|
|
83
|
+
visible to logged-in entrants on the jam's own rate page, which is out of
|
|
84
|
+
scope for something meant to run anonymously against any jam slug.
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
jam-scout <jam-slug> [<jam-slug> ...]
|
|
90
|
+
jam-scout https://itch.io/jam/some-jam-2026
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or, without installing anything: `python3 jam_scout.py <jam-slug>`.
|
|
94
|
+
|
|
95
|
+
Pass as many slugs or full URLs as you want; each prints its own JSON object.
|
|
96
|
+
|
|
97
|
+
## Requirements
|
|
98
|
+
|
|
99
|
+
Python 3.8+, standard library only. No dependencies to install.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
Built autonomously by an AI agent as part of [Arcade Forge](https://fernforge.itch.io), an itch.io distribution experiment.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# itch-jam-scout
|
|
2
|
+
|
|
3
|
+
A read-only CLI that answers the two questions that actually matter before you
|
|
4
|
+
enter an itch.io game jam: **what phase is it in right now**, and **how big is
|
|
5
|
+
the pool you're competing in**.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install itch-jam-scout
|
|
9
|
+
jam-scout trijam-379 codex-game-jam-2026
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"url": "https://itch.io/jam/trijam-379",
|
|
15
|
+
"start_date": "2026-07-04 00:00:00",
|
|
16
|
+
"end_date": "2026-07-06 00:00:00",
|
|
17
|
+
"voting_end_date": "2026-07-13 00:00:00",
|
|
18
|
+
"phase": "voting open",
|
|
19
|
+
"rating_queue_disclosed": false,
|
|
20
|
+
"coverage_rule_disclosed": false,
|
|
21
|
+
"entry_count_hint": "28 entries"
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
No `pip`? It's one file with zero dependencies — `python3 jam_scout.py <jam-slug>` works straight from a clone.
|
|
26
|
+
|
|
27
|
+
## Why this exists
|
|
28
|
+
|
|
29
|
+
itch jam pages show a countdown timer in the page header, and that timer is
|
|
30
|
+
sometimes wrong — hosts extend deadlines after the page ships, and the
|
|
31
|
+
human-readable text doesn't always get updated in step. The one place that
|
|
32
|
+
can't lie is the `I.ViewJam(...)` JSON blob itch embeds in every jam page to
|
|
33
|
+
drive its own countdown widget. This tool parses that blob directly instead
|
|
34
|
+
of trusting the rendered text, so a moved deadline shows up correctly the
|
|
35
|
+
first time you run it, not after you've already miscounted.
|
|
36
|
+
|
|
37
|
+
It also surfaces the join/entry count where itch exposes it. That number
|
|
38
|
+
matters more than people give it credit for: in a jam with 800 entries, your
|
|
39
|
+
own rating activity is noise. In a jam with 20, it's a meaningful fraction of
|
|
40
|
+
total votes — worth knowing before you decide where to spend a Saturday.
|
|
41
|
+
|
|
42
|
+
## What it checks
|
|
43
|
+
|
|
44
|
+
- **Phase** — not started / submissions open / voting open / closed, computed
|
|
45
|
+
against the jam's actual start/end/voting-end timestamps, not the countdown
|
|
46
|
+
text.
|
|
47
|
+
- **Pool size** — join or entry count, when itch's page markup exposes it.
|
|
48
|
+
- **Rating Queue mention** — whether the host's own jam description names
|
|
49
|
+
itch's Rating Queue system in text. This is a text search, not an API flag:
|
|
50
|
+
a `false` means "not mentioned on the page," not "confirmed off." Treat a
|
|
51
|
+
`true` as a real signal and a `false` as no signal either way.
|
|
52
|
+
- **Coverage rule mention** — whether the page states a minimum number of
|
|
53
|
+
ratings you have to give or receive to count as a valid entry. Worth
|
|
54
|
+
checking because jam size alone doesn't predict how many entries actually
|
|
55
|
+
get rated: a 65-entry jam with no enforced minimum left 11 entries at zero
|
|
56
|
+
ratings, while a 485-entry jam with one rated 483 of them. In practice most
|
|
57
|
+
hosts don't put this in the page description even when it's true, so
|
|
58
|
+
expect `false` a lot — read it as "not stated here," not "doesn't exist."
|
|
59
|
+
|
|
60
|
+
## What it doesn't do
|
|
61
|
+
|
|
62
|
+
No login, no rating, no write actions of any kind — it fetches one public
|
|
63
|
+
page and parses it. It can't tell you whether Rating Queue is toggled on for
|
|
64
|
+
a jam that doesn't say so in its description; that toggle only becomes
|
|
65
|
+
visible to logged-in entrants on the jam's own rate page, which is out of
|
|
66
|
+
scope for something meant to run anonymously against any jam slug.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
jam-scout <jam-slug> [<jam-slug> ...]
|
|
72
|
+
jam-scout https://itch.io/jam/some-jam-2026
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or, without installing anything: `python3 jam_scout.py <jam-slug>`.
|
|
76
|
+
|
|
77
|
+
Pass as many slugs or full URLs as you want; each prints its own JSON object.
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
Python 3.8+, standard library only. No dependencies to install.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
Built autonomously by an AI agent as part of [Arcade Forge](https://fernforge.itch.io), an itch.io distribution experiment.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: itch-jam-scout
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Read-only CLI that checks an itch.io jam's real phase, deadlines, and pool size from itch's own embedded JSON, instead of the page's countdown text.
|
|
5
|
+
Author: Arcade Forge
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fernforge-arcade/itch-jam-scout
|
|
8
|
+
Keywords: itch.io,game-jam,gamedev,cli
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Games/Entertainment
|
|
14
|
+
Requires-Python: >=3.8
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# itch-jam-scout
|
|
20
|
+
|
|
21
|
+
A read-only CLI that answers the two questions that actually matter before you
|
|
22
|
+
enter an itch.io game jam: **what phase is it in right now**, and **how big is
|
|
23
|
+
the pool you're competing in**.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip install itch-jam-scout
|
|
27
|
+
jam-scout trijam-379 codex-game-jam-2026
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"url": "https://itch.io/jam/trijam-379",
|
|
33
|
+
"start_date": "2026-07-04 00:00:00",
|
|
34
|
+
"end_date": "2026-07-06 00:00:00",
|
|
35
|
+
"voting_end_date": "2026-07-13 00:00:00",
|
|
36
|
+
"phase": "voting open",
|
|
37
|
+
"rating_queue_disclosed": false,
|
|
38
|
+
"coverage_rule_disclosed": false,
|
|
39
|
+
"entry_count_hint": "28 entries"
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
No `pip`? It's one file with zero dependencies — `python3 jam_scout.py <jam-slug>` works straight from a clone.
|
|
44
|
+
|
|
45
|
+
## Why this exists
|
|
46
|
+
|
|
47
|
+
itch jam pages show a countdown timer in the page header, and that timer is
|
|
48
|
+
sometimes wrong — hosts extend deadlines after the page ships, and the
|
|
49
|
+
human-readable text doesn't always get updated in step. The one place that
|
|
50
|
+
can't lie is the `I.ViewJam(...)` JSON blob itch embeds in every jam page to
|
|
51
|
+
drive its own countdown widget. This tool parses that blob directly instead
|
|
52
|
+
of trusting the rendered text, so a moved deadline shows up correctly the
|
|
53
|
+
first time you run it, not after you've already miscounted.
|
|
54
|
+
|
|
55
|
+
It also surfaces the join/entry count where itch exposes it. That number
|
|
56
|
+
matters more than people give it credit for: in a jam with 800 entries, your
|
|
57
|
+
own rating activity is noise. In a jam with 20, it's a meaningful fraction of
|
|
58
|
+
total votes — worth knowing before you decide where to spend a Saturday.
|
|
59
|
+
|
|
60
|
+
## What it checks
|
|
61
|
+
|
|
62
|
+
- **Phase** — not started / submissions open / voting open / closed, computed
|
|
63
|
+
against the jam's actual start/end/voting-end timestamps, not the countdown
|
|
64
|
+
text.
|
|
65
|
+
- **Pool size** — join or entry count, when itch's page markup exposes it.
|
|
66
|
+
- **Rating Queue mention** — whether the host's own jam description names
|
|
67
|
+
itch's Rating Queue system in text. This is a text search, not an API flag:
|
|
68
|
+
a `false` means "not mentioned on the page," not "confirmed off." Treat a
|
|
69
|
+
`true` as a real signal and a `false` as no signal either way.
|
|
70
|
+
- **Coverage rule mention** — whether the page states a minimum number of
|
|
71
|
+
ratings you have to give or receive to count as a valid entry. Worth
|
|
72
|
+
checking because jam size alone doesn't predict how many entries actually
|
|
73
|
+
get rated: a 65-entry jam with no enforced minimum left 11 entries at zero
|
|
74
|
+
ratings, while a 485-entry jam with one rated 483 of them. In practice most
|
|
75
|
+
hosts don't put this in the page description even when it's true, so
|
|
76
|
+
expect `false` a lot — read it as "not stated here," not "doesn't exist."
|
|
77
|
+
|
|
78
|
+
## What it doesn't do
|
|
79
|
+
|
|
80
|
+
No login, no rating, no write actions of any kind — it fetches one public
|
|
81
|
+
page and parses it. It can't tell you whether Rating Queue is toggled on for
|
|
82
|
+
a jam that doesn't say so in its description; that toggle only becomes
|
|
83
|
+
visible to logged-in entrants on the jam's own rate page, which is out of
|
|
84
|
+
scope for something meant to run anonymously against any jam slug.
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
jam-scout <jam-slug> [<jam-slug> ...]
|
|
90
|
+
jam-scout https://itch.io/jam/some-jam-2026
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or, without installing anything: `python3 jam_scout.py <jam-slug>`.
|
|
94
|
+
|
|
95
|
+
Pass as many slugs or full URLs as you want; each prints its own JSON object.
|
|
96
|
+
|
|
97
|
+
## Requirements
|
|
98
|
+
|
|
99
|
+
Python 3.8+, standard library only. No dependencies to install.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
Built autonomously by an AI agent as part of [Arcade Forge](https://fernforge.itch.io), an itch.io distribution experiment.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
jam_scout
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
jam_scout.py — screen an itch.io jam page for the visibility-algorithm axis
|
|
4
|
+
(opportunity p-gje5aa: pick jams whose ranking mechanic rewards rating volume).
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 jam_scout.py <jam-slug-or-url> [<jam-slug-or-url> ...]
|
|
8
|
+
|
|
9
|
+
For each jam, fetches the public jam page and reports:
|
|
10
|
+
- submission/voting window (parsed from itch's embedded I.ViewJam JSON — the
|
|
11
|
+
only authoritative source; the human-readable countdown text has been wrong
|
|
12
|
+
on this jam before)
|
|
13
|
+
- current phase (submissions open / voting open / closed) as of now (UTC)
|
|
14
|
+
- whether the host's own description text names itch's Rating Queue system
|
|
15
|
+
(the fairness-floor mechanic — NOT a "rate more to be seen more" multiplier,
|
|
16
|
+
see memory/discovery-backlog.md) — this is host-disclosed, not an API flag,
|
|
17
|
+
so a false "no" just means "not verifiable from the page", not "absent"
|
|
18
|
+
- entry count, when the page exposes it, since a small pool is where an
|
|
19
|
+
agent's own rating activity is a bigger fraction of total votes
|
|
20
|
+
- whether the page states a minimum-ratings-to-unlock-results rule, since
|
|
21
|
+
real coverage data shows jam SIZE alone doesn't predict how many entries
|
|
22
|
+
get rated (a 65-entry jam left 11 entries at zero; a 485-entry jam with an
|
|
23
|
+
enforced rating requirement rated 483/485) — an explicit rule is a much
|
|
24
|
+
better signal than pool size
|
|
25
|
+
|
|
26
|
+
No login, no rating, no write actions — read-only reconnaissance only.
|
|
27
|
+
"""
|
|
28
|
+
import sys
|
|
29
|
+
import re
|
|
30
|
+
import json
|
|
31
|
+
import urllib.request
|
|
32
|
+
from datetime import datetime, timezone
|
|
33
|
+
|
|
34
|
+
UA = "Mozilla/5.0 ArcadeForge/1.0"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fetch(url):
|
|
38
|
+
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
|
39
|
+
with urllib.request.urlopen(req, timeout=20) as r:
|
|
40
|
+
return r.read().decode("utf-8", errors="replace")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def normalize_url(arg):
|
|
44
|
+
if arg.startswith("http"):
|
|
45
|
+
return arg
|
|
46
|
+
return f"https://itch.io/jam/{arg}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_view_jam(html):
|
|
50
|
+
m = re.search(r"I\.ViewJam\('#view_jam_\d+',\s*(\{.*?\})\)", html)
|
|
51
|
+
if not m:
|
|
52
|
+
return None
|
|
53
|
+
return json.loads(m.group(1))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def parse_dt(s):
|
|
57
|
+
return datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def phase(meta, now):
|
|
61
|
+
start = parse_dt(meta["start_date"])
|
|
62
|
+
end = parse_dt(meta["end_date"])
|
|
63
|
+
voting_end = meta.get("voting_end_date")
|
|
64
|
+
voting_end = parse_dt(voting_end) if voting_end else None
|
|
65
|
+
if now < start:
|
|
66
|
+
return "not started"
|
|
67
|
+
if now < end:
|
|
68
|
+
return "submissions open"
|
|
69
|
+
if voting_end and now < voting_end:
|
|
70
|
+
return "voting open"
|
|
71
|
+
return "closed"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def rating_queue_signal(html):
|
|
75
|
+
return bool(re.search(r"rating queue", html, re.IGNORECASE))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def coverage_rule_signal(html):
|
|
79
|
+
# Look for host-stated language requiring a minimum number of ratings
|
|
80
|
+
# given/received to unlock results or count as a valid entry. This is a
|
|
81
|
+
# weaker text-match than rating_queue_signal on purpose: hosts phrase it
|
|
82
|
+
# many ways ("rate at least 5 games", "minimum of 3 ratings to qualify").
|
|
83
|
+
# A False here means "not stated in the page text", not "does not exist" —
|
|
84
|
+
# some jams enforce this via a separate rules tab this tool doesn't fetch.
|
|
85
|
+
return bool(re.search(
|
|
86
|
+
r"(rate|review)\s+(at least|a minimum of|\d+)\s+\w*\s*(game|entr|submission)",
|
|
87
|
+
html, re.IGNORECASE,
|
|
88
|
+
))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def entry_count(html):
|
|
92
|
+
# itch shows a "Joined"/"Entries" stat pair in stat_box widgets; entries only
|
|
93
|
+
# appears once submissions have closed, so "Joined" is the best pre-close proxy
|
|
94
|
+
# for pool size (bigger pool = your own ratings move the needle less).
|
|
95
|
+
stats = {label: value for value, label in re.findall(
|
|
96
|
+
r'stat_value">(\d+)</div><div class="stat_label">([^<]+)', html)}
|
|
97
|
+
for label in ("Entries", "Submissions", "Joined"):
|
|
98
|
+
if label in stats:
|
|
99
|
+
return f"{stats[label]} {label.lower()}"
|
|
100
|
+
m = re.search(r'([\d,]+)\s+(?:entries|submissions|games)\b', html, re.IGNORECASE)
|
|
101
|
+
if m:
|
|
102
|
+
return m.group(1)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def scout(arg, now=None):
|
|
107
|
+
url = normalize_url(arg)
|
|
108
|
+
now = now or datetime.now(timezone.utc)
|
|
109
|
+
html = fetch(url)
|
|
110
|
+
meta = parse_view_jam(html)
|
|
111
|
+
result = {"url": url}
|
|
112
|
+
if meta is None:
|
|
113
|
+
result["error"] = "no I.ViewJam block found (page shape changed or not a jam page)"
|
|
114
|
+
return result
|
|
115
|
+
result["start_date"] = meta.get("start_date")
|
|
116
|
+
result["end_date"] = meta.get("end_date")
|
|
117
|
+
result["voting_end_date"] = meta.get("voting_end_date")
|
|
118
|
+
result["phase"] = phase(meta, now)
|
|
119
|
+
result["rating_queue_disclosed"] = rating_queue_signal(html)
|
|
120
|
+
result["coverage_rule_disclosed"] = coverage_rule_signal(html)
|
|
121
|
+
result["entry_count_hint"] = entry_count(html)
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main(argv):
|
|
126
|
+
if len(argv) < 2:
|
|
127
|
+
print(__doc__)
|
|
128
|
+
return 1
|
|
129
|
+
for arg in argv[1:]:
|
|
130
|
+
r = scout(arg)
|
|
131
|
+
print(json.dumps(r, indent=2))
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def cli():
|
|
136
|
+
sys.exit(main(sys.argv))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
if __name__ == "__main__":
|
|
140
|
+
cli()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "itch-jam-scout"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Read-only CLI that checks an itch.io jam's real phase, deadlines, and pool size from itch's own embedded JSON, instead of the page's countdown text."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Arcade Forge" }]
|
|
13
|
+
keywords = ["itch.io", "game-jam", "gamedev", "cli"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Games/Entertainment",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/fernforge-arcade/itch-jam-scout"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
jam-scout = "jam_scout:cli"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
py-modules = ["jam_scout"]
|