Compare commits

..

2 Commits

3 changed files with 212 additions and 46 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.env .env
out/ out/
.hurl/ .hurl/
__pycache__

View File

@ -11,8 +11,9 @@ from datetime import datetime, timedelta, timezone
class Mod: class Mod:
def __init__(self, data): def from_curse(self, data):
self.id = int(data["id"]) self.id = str(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"])
@ -25,7 +26,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 = year_created self.year_created = int(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(
@ -36,6 +37,33 @@ 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"
@ -49,6 +77,124 @@ 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()
@ -94,6 +240,30 @@ 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)
@ -111,7 +281,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]): def sort_mod(mod: Mod, sorts: list[SortType]) -> list:
sorting = [] sorting = []
for sort in sorts: for sort in sorts:
if sort is SortType.Downloads: if sort is SortType.Downloads:
@ -121,59 +291,37 @@ 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(-int(mod.year_created)) sorting.append(-mod.year_created)
return tuple(sorting) return 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 = [] all_mods: list[Mod] = []
for i in range(0, MAX_INDEX, PAGE_SIZE): (curse_mods, curse_count) = fetch_curse_mods()
PARAMS["index"] = i all_mods.extend(curse_mods)
r = requests.get(url=URL, params=PARAMS, headers=HEADERS)
try:
data = r.json()
except ValueError as err:
print(err)
break
data = data["data"] (modrinth_mods, modrinth_count) = fetch_modrinth_mods()
all_mods.extend(modrinth_mods)
for entry in data: if ONLY_COUNT:
mod = Mod(entry) print(f"{MOD_LOADER.name}-{GAME_VERSION} {curse_count + modrinth_count}")
# print(
# filter mods with under 1M downloads # f"{MOD_LOADER.name}-{GAME_VERSION}\n Curse {curse_count}\n Modrinth {modrinth_count}"
if FilterType.Downloads in filters: # )
if mod.downloads < 1_000_000: exit(0)
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))
all_mods = map( mods_map = 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,
@ -181,8 +329,8 @@ if __name__ == "__main__":
all_mods, all_mods,
) )
table = tabulate( table = tabulate(
all_mods, mods_map,
headers=["Name", "Downloads", "Created Date", "Last Release Date"], headers=["Name", "Platform", "Downloads", "Created Date", "Last Release Date"],
tablefmt="double_grid", tablefmt="double_grid",
showindex=True, showindex=True,
) )

17
justfile 100644
View File

@ -0,0 +1,17 @@
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