Compare commits
No commits in common. "bfd5bf1a00fcb7090b887c026bc7ab70b7c05985" and "2b48048bdd9a659ad7b6546efdebc4547a3c05d4" have entirely different histories.
bfd5bf1a00
...
2b48048bdd
|
|
@ -1,4 +1,3 @@
|
||||||
.env
|
.env
|
||||||
out/
|
out/
|
||||||
.hurl/
|
.hurl/
|
||||||
__pycache__
|
|
||||||
|
|
|
||||||
240
curse-stats.py
240
curse-stats.py
|
|
@ -11,9 +11,8 @@ from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
|
||||||
class Mod:
|
class Mod:
|
||||||
def from_curse(self, data):
|
def __init__(self, data):
|
||||||
self.id = str(data["id"])
|
self.id = int(data["id"])
|
||||||
self.platform = "CURSE"
|
|
||||||
self.name = str(data["name"])
|
self.name = str(data["name"])
|
||||||
self.downloads = int(data["downloadCount"])
|
self.downloads = int(data["downloadCount"])
|
||||||
self.date_created = str(data["dateCreated"])
|
self.date_created = str(data["dateCreated"])
|
||||||
|
|
@ -26,7 +25,7 @@ class Mod:
|
||||||
year_created = datetime.strptime(
|
year_created = datetime.strptime(
|
||||||
data["dateCreated"], "%Y-%m-%dT%H:%M:%S%z"
|
data["dateCreated"], "%Y-%m-%dT%H:%M:%S%z"
|
||||||
).strftime("%Y")
|
).strftime("%Y")
|
||||||
self.year_created = int(year_created)
|
self.year_created = year_created
|
||||||
self.date_modified = str(data["dateModified"])
|
self.date_modified = str(data["dateModified"])
|
||||||
try:
|
try:
|
||||||
self.date_released = datetime.strptime(
|
self.date_released = datetime.strptime(
|
||||||
|
|
@ -37,33 +36,6 @@ class Mod:
|
||||||
data["dateReleased"], "%Y-%m-%dT%H:%M:%S%z"
|
data["dateReleased"], "%Y-%m-%dT%H:%M:%S%z"
|
||||||
)
|
)
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
def from_modrinth(self, data):
|
|
||||||
self.id = str(data["project_id"])
|
|
||||||
self.platform = "MODRINTH"
|
|
||||||
self.name = str(data["title"])
|
|
||||||
self.downloads = int(data["downloads"])
|
|
||||||
self.date_created = str(data["date_created"])
|
|
||||||
self.date_released = datetime.strptime(
|
|
||||||
self.date_created, "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
||||||
)
|
|
||||||
year_created = ""
|
|
||||||
try:
|
|
||||||
year_created = datetime.strptime(
|
|
||||||
data["date_created"], "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
||||||
).strftime("%Y")
|
|
||||||
except ValueError:
|
|
||||||
year_created = datetime.strptime(
|
|
||||||
data["date_created"], "%Y-%m-%dT%H:%M:%S%z"
|
|
||||||
).strftime("%Y")
|
|
||||||
self.year_created = int(year_created)
|
|
||||||
self.date_modified = str(data["date_modified"])
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
# def __init__(self, data):
|
|
||||||
|
|
||||||
|
|
||||||
class FilterType(Enum):
|
class FilterType(Enum):
|
||||||
LastYear = "last-year"
|
LastYear = "last-year"
|
||||||
|
|
@ -77,124 +49,6 @@ class SortType(Enum):
|
||||||
InvReleaseYear = "-release-year"
|
InvReleaseYear = "-release-year"
|
||||||
|
|
||||||
|
|
||||||
class ModLoaderType(Enum):
|
|
||||||
Any = 0
|
|
||||||
Forge = 1
|
|
||||||
Liteloader = 3
|
|
||||||
Fabric = 4
|
|
||||||
Quilt = 5
|
|
||||||
NeoForge = 6
|
|
||||||
|
|
||||||
|
|
||||||
def filter_mod(mod: Mod) -> bool:
|
|
||||||
# filter mods with under 1M downloads
|
|
||||||
if FilterType.Downloads in filters:
|
|
||||||
if mod.downloads < 1_000_000:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# filter mods without a release in the past year
|
|
||||||
if FilterType.LastYear in filters:
|
|
||||||
past = datetime.now(timezone.utc) - timedelta(days=365)
|
|
||||||
if mod.date_released < past:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_curse_mods() -> tuple[list[Mod], int]:
|
|
||||||
URL = "https://api.curseforge.com/v1/mods/search"
|
|
||||||
PARAMS = {
|
|
||||||
"gameId": "432", # minecraft
|
|
||||||
"classId": 6, # mods
|
|
||||||
"gameVersion": GAME_VERSION,
|
|
||||||
"modLoaderType": MOD_LOADER.value,
|
|
||||||
"sortField": 6, # downloads
|
|
||||||
"sortOrder": "desc",
|
|
||||||
"pageSize": PAGE_SIZE,
|
|
||||||
"index": 0,
|
|
||||||
}
|
|
||||||
CURSE_API_TOKEN = os.environ["CURSE_API_TOKEN"]
|
|
||||||
HEADERS = {"x-api-key": CURSE_API_TOKEN}
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
mods: list[Mod] = []
|
|
||||||
|
|
||||||
for i in range(0, MAX_INDEX, PAGE_SIZE):
|
|
||||||
PARAMS["index"] = i
|
|
||||||
r = requests.get(url=URL, params=PARAMS, headers=HEADERS)
|
|
||||||
try:
|
|
||||||
data = r.json()
|
|
||||||
except ValueError as err:
|
|
||||||
print(r)
|
|
||||||
print(err)
|
|
||||||
break
|
|
||||||
|
|
||||||
if ONLY_COUNT:
|
|
||||||
count = int(data["pagination"]["totalCount"])
|
|
||||||
break
|
|
||||||
|
|
||||||
data = data["data"]
|
|
||||||
|
|
||||||
for entry in data:
|
|
||||||
mod = Mod().from_curse(entry)
|
|
||||||
if filter_mod(mod):
|
|
||||||
mods.append(mod)
|
|
||||||
|
|
||||||
print(f"{i} / {MAX_INDEX}")
|
|
||||||
|
|
||||||
return (mods, count)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_modrinth_mods() -> tuple[list[Mod], int]:
|
|
||||||
URL = "https://api.modrinth.com/v2/search"
|
|
||||||
PARAMS = {
|
|
||||||
"facets": format(
|
|
||||||
f'[["project_type:mod"],["categories:{MOD_LOADER.name}"],["versions:{GAME_VERSION}"]]'
|
|
||||||
),
|
|
||||||
"limit": PAGE_SIZE,
|
|
||||||
"index": "downloads",
|
|
||||||
}
|
|
||||||
HEADERS = {}
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
mods: list[Mod] = []
|
|
||||||
|
|
||||||
for i in range(0, MAX_INDEX, PAGE_SIZE):
|
|
||||||
r = requests.get(url=URL, params=PARAMS, headers=HEADERS)
|
|
||||||
try:
|
|
||||||
data = r.json()
|
|
||||||
except ValueError as err:
|
|
||||||
print(r)
|
|
||||||
print(err)
|
|
||||||
break
|
|
||||||
|
|
||||||
if ONLY_COUNT:
|
|
||||||
count = data["total_hits"]
|
|
||||||
break
|
|
||||||
|
|
||||||
data = data["hits"]
|
|
||||||
|
|
||||||
for entry in data:
|
|
||||||
mod = Mod().from_modrinth(entry)
|
|
||||||
|
|
||||||
# filter mods with under 1M downloads
|
|
||||||
if FilterType.Downloads in filters:
|
|
||||||
if mod.downloads < 1_000_000:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# filter mods without a release in the past year
|
|
||||||
if FilterType.LastYear in filters:
|
|
||||||
past = datetime.now(timezone.utc) - timedelta(days=365)
|
|
||||||
if mod.date_released < past:
|
|
||||||
continue
|
|
||||||
|
|
||||||
mods.append(mod)
|
|
||||||
|
|
||||||
print(f"{i} / {MAX_INDEX}")
|
|
||||||
|
|
||||||
return (mods, count)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
@ -240,30 +94,6 @@ if __name__ == "__main__":
|
||||||
required=False,
|
required=False,
|
||||||
dest="max_index",
|
dest="max_index",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--count",
|
|
||||||
help="Fetched only the max number of entries of the search",
|
|
||||||
default=False,
|
|
||||||
required=False,
|
|
||||||
action="store_true",
|
|
||||||
dest="count",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--game-version",
|
|
||||||
help="Narrows down the search for only a specific game version",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
required=False,
|
|
||||||
dest="game_version",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--mod-loader",
|
|
||||||
help="Narrows down the search for only a specific mod loader",
|
|
||||||
type=str,
|
|
||||||
default="any",
|
|
||||||
required=False,
|
|
||||||
dest="mod_loader",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
page_size = clamp(args.page_size, 10, 50)
|
page_size = clamp(args.page_size, 10, 50)
|
||||||
|
|
@ -281,7 +111,7 @@ if __name__ == "__main__":
|
||||||
for sort_logic in sort_list:
|
for sort_logic in sort_list:
|
||||||
sorts.append(sort_logic)
|
sorts.append(sort_logic)
|
||||||
|
|
||||||
def sort_mod(mod: Mod, sorts: list[SortType]) -> list:
|
def sort_mod(mod: Mod, sorts: list[SortType]):
|
||||||
sorting = []
|
sorting = []
|
||||||
for sort in sorts:
|
for sort in sorts:
|
||||||
if sort is SortType.Downloads:
|
if sort is SortType.Downloads:
|
||||||
|
|
@ -291,37 +121,59 @@ if __name__ == "__main__":
|
||||||
elif sort is SortType.ReleaseYear:
|
elif sort is SortType.ReleaseYear:
|
||||||
sorting.append(mod.year_created)
|
sorting.append(mod.year_created)
|
||||||
elif sort is SortType.InvReleaseYear:
|
elif sort is SortType.InvReleaseYear:
|
||||||
sorting.append(-mod.year_created)
|
sorting.append(-int(mod.year_created))
|
||||||
|
|
||||||
return sorting
|
return tuple(sorting)
|
||||||
|
|
||||||
MOD_LOADER = ModLoaderType[args.mod_loader]
|
|
||||||
GAME_VERSION = args.game_version
|
|
||||||
ONLY_COUNT = args.count
|
|
||||||
PAGE_SIZE = page_size
|
PAGE_SIZE = page_size
|
||||||
MAX_INDEX = args.max_index
|
MAX_INDEX = args.max_index
|
||||||
|
URL = "https://api.curseforge.com/v1/mods/search"
|
||||||
|
PARAMS = {
|
||||||
|
"gameId": "432", # minecraft
|
||||||
|
"classId": 6, # mods
|
||||||
|
"sortField": 6, # downloads
|
||||||
|
"sortOrder": "desc",
|
||||||
|
"pageSize": PAGE_SIZE,
|
||||||
|
"index": 0,
|
||||||
|
}
|
||||||
|
CURSE_API_TOKEN = os.environ["CURSE_API_TOKEN"]
|
||||||
|
HEADERS = {"x-api-key": CURSE_API_TOKEN}
|
||||||
|
|
||||||
all_mods: list[Mod] = []
|
all_mods = []
|
||||||
|
|
||||||
(curse_mods, curse_count) = fetch_curse_mods()
|
for i in range(0, MAX_INDEX, PAGE_SIZE):
|
||||||
all_mods.extend(curse_mods)
|
PARAMS["index"] = i
|
||||||
|
r = requests.get(url=URL, params=PARAMS, headers=HEADERS)
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
except ValueError as err:
|
||||||
|
print(err)
|
||||||
|
break
|
||||||
|
|
||||||
(modrinth_mods, modrinth_count) = fetch_modrinth_mods()
|
data = data["data"]
|
||||||
all_mods.extend(modrinth_mods)
|
|
||||||
|
|
||||||
if ONLY_COUNT:
|
for entry in data:
|
||||||
print(f"{MOD_LOADER.name}-{GAME_VERSION} {curse_count + modrinth_count}")
|
mod = Mod(entry)
|
||||||
# print(
|
|
||||||
# f"{MOD_LOADER.name}-{GAME_VERSION}\n Curse {curse_count}\n Modrinth {modrinth_count}"
|
# filter mods with under 1M downloads
|
||||||
# )
|
if FilterType.Downloads in filters:
|
||||||
exit(0)
|
if mod.downloads < 1_000_000:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# filter mods without a release in the past year
|
||||||
|
if FilterType.LastYear in filters:
|
||||||
|
past = datetime.now(timezone.utc) - timedelta(days=365)
|
||||||
|
if mod.date_released < past:
|
||||||
|
continue
|
||||||
|
|
||||||
|
all_mods.append(mod)
|
||||||
|
print(f"{i} / {MAX_INDEX}")
|
||||||
|
|
||||||
all_mods.sort(key=lambda m: sort_mod(m, sorts))
|
all_mods.sort(key=lambda m: sort_mod(m, sorts))
|
||||||
|
|
||||||
mods_map = map(
|
all_mods = map(
|
||||||
lambda m: [
|
lambda m: [
|
||||||
m.name,
|
m.name,
|
||||||
m.platform,
|
|
||||||
"{0:,d}".format(m.downloads),
|
"{0:,d}".format(m.downloads),
|
||||||
m.date_created,
|
m.date_created,
|
||||||
m.date_released,
|
m.date_released,
|
||||||
|
|
@ -329,8 +181,8 @@ if __name__ == "__main__":
|
||||||
all_mods,
|
all_mods,
|
||||||
)
|
)
|
||||||
table = tabulate(
|
table = tabulate(
|
||||||
mods_map,
|
all_mods,
|
||||||
headers=["Name", "Platform", "Downloads", "Created Date", "Last Release Date"],
|
headers=["Name", "Downloads", "Created Date", "Last Release Date"],
|
||||||
tablefmt="double_grid",
|
tablefmt="double_grid",
|
||||||
showindex=True,
|
showindex=True,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
17
justfile
17
justfile
|
|
@ -1,17 +0,0 @@
|
||||||
|
|
||||||
set quiet
|
|
||||||
|
|
||||||
count-120:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
for i in {1..6}; do
|
|
||||||
./curse-stats.py --game-version "1.20.${i}" --mod-loader Forge --count
|
|
||||||
printf "\n"
|
|
||||||
done
|
|
||||||
|
|
||||||
count-121:
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
for i in {1..8}; do
|
|
||||||
./curse-stats.py --game-version "1.21.${i}" --mod-loader Forge --count
|
|
||||||
./curse-stats.py --game-version "1.21.${i}" --mod-loader NeoForge --count
|
|
||||||
printf "\n"
|
|
||||||
done
|
|
||||||
Loading…
Reference in New Issue