Skip to content

Commit b9027a1

Browse files
committed
initial commit
1 parent 984fdc5 commit b9027a1

File tree

5 files changed

+177
-1
lines changed

5 files changed

+177
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,6 @@ cython_debug/
160160
# and can be added to the global gitignore or merged into this file. For a more nuclear
161161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162162
#.idea/
163+
164+
# Download file specific
165+
mods/

.vscode/launch.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"python.terminal.activateEnvironment": true,
7+
"configurations": [
8+
9+
{
10+
"name": "Python Debugger: Current File",
11+
"type": "debugpy",
12+
"request": "launch",
13+
"program": "${file}",
14+
"console": "integratedTerminal"
15+
}
16+
]
17+
}

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
11
# factorio-mod-downloader
2-
factorio-mod-downloader
2+
3+
Factorio mod downloader, used [re146.dev]{https://re146.dev/factorio/mods} to recursively download a mod and its all required depndedncies. Its really helpful if you want to download a modpack as a modpack contains various different recommended mods, that if you want to download separately will take you a lot of clicks and headache.
4+
5+
If you love the game please buy it and support the developers. I am a big fan of the game.
6+
7+
### How to run
8+
This code is built using python so make sure you have python installed. You can use official Python website to download any version > 3.10
9+
10+
1. Open main.py, and replace the mod_url line number 13 with the factorio 2 official mod url of the mod you want to download. for e.g. you want to download Krastorio 2, the mod_url will be exactly like this `mod_url = 'https://mods.factorio.com/mod/Krastorio2'`, dont forget to add inverted commas.
11+
2. Run this command in cmd or terminal. `pip install -r requirements.txt` or `pip3 install -r requirements.txt` (Windows uses pip, while mac/linux uses pip3). It will install all dependencies, required to run this code.
12+
2. Finally run the script using `python main.py` or `python3 main.py` (Windows uses python while mac/linux uses python3)
13+
3. All the mods and its dependencies will be downloaded and saved in a new mods folder
14+
15+
### Note
16+
I have not included optional dependencies, as its a stupid idea, since a lots of mods, even they don't need something have optional dependencies mentioned. So it will probbaly take forever to finish downloading. It can be implemented although. Not a big task.
17+
18+
Feel free to reach out to me if you need soem help.

main.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from selenium import webdriver
2+
from selenium.webdriver.common.by import By
3+
from selenium.webdriver.chrome.service import Service
4+
from selenium.webdriver.chrome.options import Options
5+
import chromedriver_autoinstaller
6+
from bs4 import BeautifulSoup
7+
import requests
8+
import random
9+
import time
10+
import os
11+
12+
13+
mod_url = 'https://mods.factorio.com/mod/Krastorio2'
14+
15+
16+
chromedriver_autoinstaller.install()
17+
chrome_options = Options()
18+
chrome_options.add_argument("--headless") # Run in headless mode (without a GUI)
19+
chrome_options.add_argument("--disable-gpu")
20+
chrome_options.add_argument("--remote-debugging-port=9222")
21+
22+
23+
24+
def get_latest_version(url):
25+
driver = webdriver.Chrome(options=chrome_options)
26+
driver.get(url)
27+
time.sleep(2) # Adjust sleep time if needed
28+
html_code = driver.page_source
29+
driver.quit()
30+
soup = BeautifulSoup(html_code, 'html.parser')
31+
32+
select = soup.find('select', {'id': 'mod-version'})
33+
34+
if not select:
35+
raise ValueError("No select element found with id 'mod-version'")
36+
37+
latest_version = None
38+
39+
for option in select.find_all('option'):
40+
if '(last)' in option.text:
41+
latest_version = option['value']
42+
break
43+
44+
# If no option with '(last)' is found, use the first version
45+
if not latest_version:
46+
first_option = select.find('option')
47+
latest_version = first_option['value']
48+
49+
return latest_version
50+
51+
52+
def generate_anticache():
53+
random_number = random.randint(1_000_000_000_000_000, 9_999_999_999_999_999) # 16-digit number
54+
return f"0.{random_number}"
55+
56+
57+
def download_file(url, file_path):
58+
response = requests.get(url, stream=True)
59+
response.raise_for_status() # Check if the request was successful
60+
61+
with open(file_path, 'wb') as file:
62+
for chunk in response.iter_content(chunk_size=8192):
63+
file.write(chunk)
64+
print(f"Downloaded: {file_path}")
65+
66+
67+
def get_required_dependencies(soup):
68+
dependencies = []
69+
required_deps = soup.find('dd', {'id': 'mod-info-required-dependencies'})
70+
71+
if required_deps:
72+
links = required_deps.find_all('a', href=True)
73+
for link in links:
74+
mod_name = link.text.strip()
75+
mod_url = link['href']
76+
dependencies.append((mod_name, mod_url))
77+
78+
return dependencies
79+
80+
81+
def download_mod_with_dependencies(mod_url, download_path):
82+
# Get the latest version of the mod
83+
latest_version = get_latest_version(mod_url)
84+
mod_name = mod_url.split("/")[-1]
85+
mod_name = mod_name.strip()
86+
87+
# Construct the download URL
88+
download_url = f"https://mods-storage.re146.dev/{mod_name}/{latest_version}.zip?anticache={generate_anticache()}"
89+
file_name = f"{mod_name}_{latest_version}.zip"
90+
file_path = os.path.join(download_path, file_name)
91+
92+
# Download the mod file
93+
os.makedirs(download_path, exist_ok=True)
94+
print(f"Downloading {file_name} from {download_url}")
95+
download_file(download_url, file_path)
96+
97+
# Parse the page to check for dependencies
98+
driver = webdriver.Chrome(options=chrome_options)
99+
driver.get(mod_url)
100+
time.sleep(2)
101+
html = driver.page_source
102+
driver.quit()
103+
soup = BeautifulSoup(html, 'html.parser')
104+
105+
# Get required dependencies and recursively download them
106+
dependencies = get_required_dependencies(soup)
107+
if len(dependencies) == 0:
108+
return
109+
110+
for _, dep_url in dependencies:
111+
download_mod_with_dependencies(dep_url, download_path)
112+
113+
114+
if __name__ == "__main__":
115+
re146_url = 'https://re146.dev/factorio/mods/en#' + mod_url
116+
download_mod_with_dependencies(re146_url, "mods")

requirements.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
attrs==24.2.0
2+
beautifulsoup4==4.12.3
3+
certifi==2024.7.4
4+
charset-normalizer==3.3.2
5+
chromedriver-autoinstaller==0.6.4
6+
exceptiongroup==1.2.2
7+
h11==0.14.0
8+
idna==3.8
9+
outcome==1.3.0.post0
10+
packaging==24.1
11+
PySocks==1.7.1
12+
python-dotenv==1.0.1
13+
requests==2.32.3
14+
selenium==4.23.1
15+
sniffio==1.3.1
16+
sortedcontainers==2.4.0
17+
soupsieve==2.6
18+
trio==0.26.2
19+
trio-websocket==0.11.1
20+
typing_extensions==4.12.2
21+
urllib3==2.2.2
22+
webdriver-manager==4.0.2
23+
websocket-client==1.8.0
24+
wsproto==1.2.0

0 commit comments

Comments
 (0)