Skip to content

Commit e8d30c0

Browse files
Added for backup because of OO change.
1 parent 8a516d1 commit e8d30c0

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed

kilt/__init__.py

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
import webbrowser
2+
import traceback
3+
import json
4+
import urllib.request
5+
import logging
6+
import os
7+
import hashlib
8+
import semantic_version
9+
import kilt.errors
10+
11+
class versionData:
12+
site = "https://api.modrinth.com/api/v1/mod?query={}&limit={}&index={}&offset={}"
13+
__version__ = semantic_version.Version("0.1.0-alpha0+api.1")
14+
__major__ = __version__.major
15+
__minor__ = __version__.minor
16+
__patch__ = __version__.patch
17+
__prerelease__ = __version__.prerelease
18+
__build__ = __version__.build
19+
20+
21+
def alpha():
22+
if versionData.__prerelease__[0] == "alpha":
23+
return True
24+
else:
25+
return False
26+
27+
28+
def beta():
29+
if versionData.__prerelease__[0] == "beta":
30+
return True
31+
else:
32+
return False
33+
34+
35+
def api():
36+
return versionData.__build__[1]
37+
38+
39+
def release():
40+
if versionData.__prerelease__[0] == "":
41+
return True
42+
else:
43+
return False
44+
45+
46+
is_source = is_src = alpha
47+
is_github_release = beta
48+
is_pypi_release = release
49+
50+
51+
52+
class ReturnValues:
53+
mod_exists_already = "mod_exists_already"
54+
55+
# sets up logging
56+
if not os.path.exists("logs/"):
57+
os.mkdir("logs")
58+
logging_format = '[%(filename)s][%(funcName)s/%(levelname)s] %(message)s'
59+
logging.basicConfig(level=logging.DEBUG, filename="logs/debug.log", filemode="w", format=logging_format)
60+
61+
62+
def removekey(d, key):
63+
r = dict(d)
64+
del r[key]
65+
return r
66+
67+
68+
def get_version(version_type="full"):
69+
if version_type == "major":
70+
return versionData.__major__
71+
elif version_type == "minor":
72+
return versionData.__minor__
73+
elif version_type == "patch":
74+
return versionData.__patch__
75+
elif version_type == "prerelease":
76+
return versionData.__prerelease__
77+
elif version_type == "build":
78+
return versionData.__build__
79+
elif version_type == "full":
80+
return versionData.__version__
81+
82+
83+
def get_number_of_mods():
84+
number_of_mods = 0
85+
there_are_more_mods = True
86+
i = 0
87+
while there_are_more_mods:
88+
mod_list = json.loads(urllib.request.urlopen(versionData.site.format("", 100, "newest", i * 100 + 1)).read())[
89+
"hits"]
90+
number_of_mods += len(mod_list)
91+
if len(mod_list) == 0:
92+
there_are_more_mods = False
93+
i += 1
94+
logging.info("There are {} mods on modrinth".format(number_of_mods))
95+
return number_of_mods
96+
97+
98+
def search(id=None, id_array=[], get=True, saveIcon=False, logging_level=logging.INFO, crash=False, modlist=None, filemode="w", openweb=False, output=True, outputfile=None, index="relevance",
99+
offset=0,
100+
limit=10, saveDescriptionToFile=None, web_save=None, download_folder=None, body=False, search_array=[],
101+
repeat=1, search=""):
102+
# make sure arguments are correct
103+
failed_mods = []
104+
valueToReturn = None
105+
extra_values = []
106+
dict_of_pages = {
107+
"issues": 'issues_url',
108+
"source": 'source_url',
109+
"wiki": 'wiki_url',
110+
"discord": "discord_url",
111+
"donation": "donation_urls"
112+
}
113+
logger = logging.getLogger()
114+
logger.setLevel(logging_level)
115+
if type(saveIcon) is not bool:
116+
raise kilt.errors.InvalidArgument("{} (saveIcon) is not a boolean".format(saveIcon))
117+
if type(limit) is not int or limit not in list(range(0, 101)):
118+
raise kilt.errors.InvalidArgument("{} (limit) is not in range 0, 100, or is not an integer.".format(limit))
119+
if web_save not in list(dict_of_pages.keys()) and web_save not in {"home", None}:
120+
raise kilt.errors.InvalidArgument(
121+
"{} (web_save) is not in {}".format(web_save, (list(dict_of_pages.keys()), "home")))
122+
if type(crash) is not bool:
123+
raise kilt.errors.InvalidArgument("{} (crash) is not a boolean".format(crash))
124+
if filemode not in {"w", "a"}:
125+
raise kilt.errors.InvalidArgument("{} (filemode) is not in 'a' or 'w'".format(filemode))
126+
if type(openweb) is not bool:
127+
raise kilt.errors.InvalidArgument("{} (openweb) is not a boolean".format(openweb))
128+
if type(output) is not bool:
129+
raise kilt.errors.InvalidArgument("{} (output) is not a boolean".format(output))
130+
if type(offset) is not int or offset not in list(range(0, 101)):
131+
raise kilt.errors.InvalidArgument("{} (offset) is not in range 0, 100, or it is not an integer".format(offset))
132+
if type(body) is not bool:
133+
raise kilt.errors.InvalidArgument("{} (body) is not a boolean".format(body))
134+
if type(repeat) is not int or repeat <= 0:
135+
raise kilt.errors.InvalidArgument("{} (repeat) is not an integer, or it is below 0.".format(repeat))
136+
###
137+
# searching of mods
138+
##
139+
if not search_array:
140+
if search == "":
141+
index = "newest"
142+
search_array.append(search)
143+
patched_searches = []
144+
for i in search_array:
145+
patched_searches.append(i.replace(" ","%20"))
146+
search_array = patched_searches
147+
logging.info("Mods to search for: {}".format(" ,".join(search_array)))
148+
if saveDescriptionToFile is not None:
149+
with open(saveDescriptionToFile, "w") as file:
150+
file.write("Mod Descriptions\n")
151+
if modlist is not None:
152+
with open(modlist, "w") as file:
153+
file.write("""<!DOCTYPE html>
154+
<html>
155+
<head>
156+
<title>Modlist</title>
157+
</head>
158+
159+
<body>""")
160+
for this_fake_var in range(repeat):
161+
for this_search in search_array:
162+
if id is None:
163+
modSearch = versionData.site.format(this_search, limit, index, offset)
164+
modSearchJson = json.loads(urllib.request.urlopen(modSearch).read())
165+
try:
166+
mod_response = modSearchJson["hits"][0]
167+
except IndexError:
168+
if offset == 0 and repeat == 1:
169+
logging.info("There were no results for your search")
170+
raise kilt.errors.EndOfSearch("No results found for your query")
171+
elif offset == 0 and repeat != 1:
172+
logging.info("You hit the end of your search!")
173+
raise kilt.errors.EndOfSearch("You attempted to access search result {} but {} was the max".format(offset+1, offset))
174+
else:
175+
break
176+
modJsonURL = "https://api.modrinth.com/api/v1/mod/" + str(mod_response["mod_id"].replace("local-", ""))
177+
else:
178+
modJsonURL = "https://api.modrinth.com/api/v1/mod/" + id
179+
mod_struct = json.loads(urllib.request.urlopen(modJsonURL).read())
180+
if not get:
181+
extra_values = mod_struct
182+
if get:
183+
try:
184+
extra_values.append({"title": mod_struct["title"], "url": "https://modrinth.com/mod/{}".format(mod_struct["slug"]),
185+
"desc": mod_struct["description"], "id": mod_struct["id"]})
186+
except AttributeError:
187+
pass
188+
if saveIcon:
189+
os.makedirs("cache", exist_ok=True)
190+
if not os.path.exists("cache/{}.png".format(mod_struct["title"])):
191+
if mod_struct["icon_url"] is None:
192+
logging.debug("{} does not have a mod icon".format(mod_struct["title"]))
193+
mod_icon_fileLikeObject = urllib.request.urlopen("https://raw.githubusercontent.com/Jefaxe"
194+
"/Kilt/main/meta/missing.png")
195+
else:
196+
mod_icon_fileLikeObject = urllib.request.urlopen(str(mod_struct["icon_url"]))
197+
with open("cache/{}.png".format(mod_struct["title"]), "wb") as file:
198+
file.write(mod_icon_fileLikeObject.read())
199+
mod_struct_minus_body = removekey(mod_struct, "body")
200+
logging.debug(
201+
"[Labrinth] Requested mod json(minus body): {json}".format(json=mod_struct_minus_body))
202+
# logging.debug("[Modrinth]: {json}".format(json=modSearchJson))
203+
try:
204+
if offset >= modSearchJson['total_hits']:
205+
logging.error(
206+
"There are not THAT many in the search, set `limit` higher. Or that may be it all. NOTE THAT `offset` WILL BE SET TO 0")
207+
offset = 0
208+
except UnboundLocalError: #when using 'id', modSearchJson is not defined
209+
pass
210+
# output events
211+
if saveDescriptionToFile is not None:
212+
with open(saveDescriptionToFile, "a") as desc:
213+
desc.write(mod_struct["title"] + ": " + mod_struct["description"] + "\n")
214+
# web events
215+
if web_save is not None:
216+
page = mod_response["page_url"] if web_save == "home" else mod_struct[dict_of_pages[web_save]]
217+
if openweb and page not in [None, [], ""]:
218+
webbrowser.open(page)
219+
logging.debug("[Knosses] Opened {}'s {} page at {}".format(mod_struct["title"], web_save, page))
220+
valueToReturn = page
221+
try:
222+
downloadLink = \
223+
json.loads(urllib.request.urlopen("{json}/version".format(json=modJsonURL)).read())[0]["files"][0][
224+
"url"].replace(" ", "%20")
225+
except IndexError:
226+
logging.error("mod {} does not have any versions, skipping...".format(mod_response["title"]))
227+
downloadLink="https://modrinth.com/download-was-not-found-by-kilt"
228+
failed_mods.append(mod_response["title"])
229+
valueToReturn = failed_mods
230+
# downloads
231+
if download_folder is not None:
232+
if type(download_folder) == bool and download_folder:
233+
download_folder = "mods"
234+
try:
235+
os.makedirs(download_folder, exist_ok=True)
236+
try:
237+
filename = \
238+
json.loads(urllib.request.urlopen("{json}/version".format(json=modJsonURL)).read())[0][
239+
"files"][0][
240+
"filename"]
241+
except IndexError:
242+
logging.error("mod {} has no versions".format(mod_response["title"]))
243+
try:
244+
if filename in os.listdir(download_folder):
245+
logging.info(
246+
"[Kilt]{} is already downloaded (note we have only checked the filename, not the SHA1 hash".format(
247+
filename))
248+
if crash:
249+
raise errors.AlreadyDownloaded("{}".format(filename))
250+
else:
251+
valueToReturn = ReturnValues.mod_exists_already
252+
except UnboundLocalError:
253+
pass
254+
else:
255+
logging.info(
256+
"[Kilt] Downloading {mod} from {url}".format(mod=mod_struct["title"], url=downloadLink))
257+
downloadUrrlib = urllib.request.urlopen(downloadLink)
258+
with open(download_folder + "/{mod}".format(mod=downloadLink.rsplit("/", 1)[-1]).replace("%20",
259+
" "),
260+
"wb") as modsave:
261+
modsave.write(downloadUrrlib.read())
262+
BLOCK_SIZE = 65536 # The size of each read from the file
263+
file_hash = hashlib.sha256() # Create the hash object, can use something other than `.sha256()` if you wish
264+
with open(download_folder + "/{mod}".format(mod=downloadLink.rsplit("/", 1)[-1]).replace("%20",
265+
" "),
266+
'rb') as f: # Open the file to read it's bytes
267+
fb = f.read(BLOCK_SIZE) # Read from the file. Take in the amount declared above
268+
while len(fb) > 0: # While there is still data being read from the file
269+
file_hash.update(fb) # Update the hash
270+
fb = f.read(BLOCK_SIZE) # Read the next block from the file
271+
valueToReturn = file_hash.hexdigest() # Get the hexadecimal digest of the hash
272+
except urllib.error.HTTPError as e:
273+
logging.critical(
274+
"[Labrinth] COULD NOT DOWNLOAD MOD {} from {} because {}".format(mod_response["title"],
275+
downloadLink, e))
276+
failed_mods.append(mod_response["title"])
277+
valueToReturn = failed_mods
278+
if modlist is not None:
279+
with open(modlist, "a") as file:
280+
file.write(
281+
"<image src={} width=64 height=64 alt={}><a href={}>{} (by {}): </a><a href={}>Download<p></p>".format(
282+
mod_struct["icon_url"],
283+
mod_struct["title"],
284+
mod_response["page_url"],
285+
mod_struct["title"],
286+
mod_response["author"],
287+
downloadLink))
288+
if body:
289+
with open("generated/meta/{mod}".format(mod=mod_struct["title"] + ".md"), "w") as modsave:
290+
modsave.write(mod_struct["body"])
291+
valueToReturn = mod_struct["body"]
292+
if not output:
293+
print(valueToReturn)
294+
if outputfile is not None:
295+
with open(outputfile, "w") as file:
296+
for x in [valueToReturn]:
297+
json.dump(x, file, indent=4)
298+
offset += 1
299+
if modlist is not None:
300+
with open(modlist, "a") as file:
301+
file.write(""" </body>
302+
</html>""")
303+
#logging.info(extra_values)
304+
return [valueToReturn, extra_values]
305+
306+
307+
if __name__ == "__main__":
308+
try:
309+
search()
310+
except (Exception, SystemExit) as e:
311+
if type(e) == SystemExit:
312+
print(e)
313+
else:
314+
logging.error(traceback.format_exc())
315+
print(e)
316+
print("The process ran into an error. The error can be found in version_data.log")

kilt/errors.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class AlreadyDownloaded(FileExistsError):
2+
pass
3+
4+
5+
class EndOfSearch(IndexError):
6+
pass
7+
8+
9+
class InvalidArgument(TypeError):
10+
pass

0 commit comments

Comments
 (0)