diff --git a/.gitignore b/.gitignore index b8c0c3c8b285..0c9f51c4cd66 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ taucetistation.dmb Thumbs.db data/ +cache/ cfg/ .vscode **/MapFXDrawer/*.png +test_merge.txt diff --git a/code/__DEFINES/cache.dm b/code/__DEFINES/cache.dm index 8d5acd9109ab..5abe88c7311e 100644 --- a/code/__DEFINES/cache.dm +++ b/code/__DEFINES/cache.dm @@ -1,3 +1,5 @@ +#define PERSISTENT_CACHE_FOLDER "cache/persistent" + #define try_access_persistent_cache(filename_key, arguments...) config.use_persistent_cache && _try_access_persistent_cache(filename_key, list(##arguments)) #define save_persistent_cache(file, filename_key, arguments...) config.use_persistent_cache && _save_persistent_cache(file, filename_key, list(##arguments)) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 22cc320a36d7..2cc826d6c4ea 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -319,8 +319,11 @@ #define NOTIFY_ATTACK "attack" #define NOTIFY_ORBIT "orbit" +#define TEST_MERGE_DEFAULT_TEXT "Loading..." + #define TURF_DECALS_LIMIT 4 // max of /obj/effect/decal/turf_decal in one turf +// todo: do something with this monster #define CAN_SMOOTH_WITH_WALLS list( \ /turf/unsimulated/wall, \ /turf/simulated/wall, \ diff --git a/code/_globalvars/configuration.dm b/code/_globalvars/configuration.dm index d214e2a4820c..d33f5148b0bf 100644 --- a/code/_globalvars/configuration.dm +++ b/code/_globalvars/configuration.dm @@ -3,8 +3,7 @@ var/global/datum/configuration/config = null var/global/host = null var/global/join_motd = null var/global/host_announcements -var/global/join_test_merge = null -var/global/test_merges +var/global/list/test_merges var/global/station_name = "NSS Exodus" var/global/station_name_ru = "КСН Исход" var/global/system_name = "Tau Ceti" diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 5cb9eec85156..3e6402073f33 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -172,6 +172,7 @@ var/global/bridge_secret = null var/ghost_interaction = 0 var/python_path = "" //Path to the python executable. Defaults to "python" on windows and "/usr/bin/env python2" on unix + var/github_token = "" // todo: move this to globals for security var/use_overmap = 0 var/chat_bridge = 0 @@ -531,6 +532,9 @@ var/global/bridge_secret = null else //probably windows, if not this should work anyway config.python_path = "python" + if("github_token") + config.github_token = value + if("allow_cult_ghostwriter") config.cult_ghostwriter = 1 @@ -630,8 +634,8 @@ var/global/bridge_secret = null var/repo_path = replacetext(config.repository_link, "https://github.com/", "") if(repo_path != config.repository_link) var/split = splittext(repo_path, "/") - github_repository_owner = split[1] - github_repository_name = split[2] + config.github_repository_owner = split[1] + config.github_repository_name = split[2] if("registration_panic_bunker_age") config.registration_panic_bunker_age = value diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm index 9140f1a0a4db..fc096a5a7cb7 100644 --- a/code/game/verbs/ooc.dm +++ b/code/game/verbs/ooc.dm @@ -209,3 +209,24 @@ var/global/bridge_ooc_colour = "#7b804f" to_chat(src, "UI resource files resent successfully. If you are still having issues, please try manually clearing your BYOND cache.") +/client/verb/show_test_merges() + set name = "Show Test Merges" + set desc = "Shows a list of all test merges that are currently active" + set category = "OOC" + + if(!test_merges) + to_chat(src, "
No test merges are currently active
") + return + + var/joined_text = "[EMBED_TIP("Test merged PRs", "Данные изменения временно залиты на сервер, для теста перед окончательным принятием изменений или сбора отзывов")]:
" + var/is_loading = FALSE + for(var/pr in test_merges) + if(test_merges[pr]) + joined_text += "[ENTITY_TAB]#[pr]: [test_merges[pr]]
" + if(test_merges[pr] == TEST_MERGE_DEFAULT_TEXT) + is_loading = TRUE + + if(is_loading) + joined_text += "
You can use OOC - Show Test Merges a bit later for more information about current test merges." + + to_chat(src, "
[joined_text]
") diff --git a/code/game/world.dm b/code/game/world.dm index 77eebc12bc69..fd9b558f6683 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -32,7 +32,7 @@ var/global/it_is_a_snow_day = FALSE load_last_mode() load_motd() load_host_announcements() - load_test_merge() + load_test_merges() load_admins() load_mentors() load_supporters() @@ -320,13 +320,47 @@ var/global/shutdown_processed = FALSE host_announcements = "

Important Admin Announcements:


[host_announcements]" -/world/proc/load_test_merge() - if(fexists("test_merge.txt")) - join_test_merge = "Test merged PRs: " - var/list/prs = splittext(trim(file2text("test_merge.txt")), " ") - for(var/pr in prs) - test_merges += "#[pr] " - join_test_merge += "#[pr] " +/world/proc/load_test_merges() + if(!fexists("test_merge.txt")) + return + + test_merges = splittext(trim(file2text("test_merge.txt")), " ") + + var/list/to_fetch = list() + + for(var/pr in test_merges) + var/path = "[PERSISTENT_CACHE_FOLDER]/github/[pr]" + if(fexists(path)) + test_merges[pr] = sanitize(file2text(path)) + else + test_merges[pr] = TEST_MERGE_DEFAULT_TEXT + to_fetch += pr + + if(length(to_fetch)) + fetch_new_test_merges(to_fetch) + +/world/proc/fetch_new_test_merges(list/to_fetch) + set waitfor = FALSE + + if(!to_fetch) + return + + var/arguments = to_fetch.Join(" ") + if(config.github_token) + arguments += " -t '[config.github_token]'" + if(config.repository_link) + arguments += " -r '[config.github_repository_owner]/[config.github_repository_name]'" + + var/json_content = world.ext_python("fetch_test_merges.py", arguments) + if(!json_content) + return + + var/list/fetch = json_decode(json_content) // {"number": {"title": title, "success": TRUE|FALSE}} + for(var/pr in fetch) + test_merges[pr] = sanitize(fetch[pr]["title"]) + if(fetch[pr]["success"]) + var/path = "[PERSISTENT_CACHE_FOLDER]/github/[pr]" + text2file(fetch[pr]["title"], path) /world/proc/load_regisration_panic_bunker() if(config.registration_panic_bunker_age) diff --git a/code/modules/mob/dead/new_player/login.dm b/code/modules/mob/dead/new_player/login.dm index 80bbdf42be57..6405df9a5c3b 100644 --- a/code/modules/mob/dead/new_player/login.dm +++ b/code/modules/mob/dead/new_player/login.dm @@ -9,8 +9,8 @@ if(join_motd) to_chat(src, "
[join_motd]
") - if(join_test_merge) - to_chat(src, "
[join_test_merge]
") + if(test_merges) + client.show_test_merges() if(host_announcements) to_chat(src, "
[host_announcements]
") diff --git a/code/modules/persistent_cache/persistent_cache.dm b/code/modules/persistent_cache/persistent_cache.dm index c9728ed24ec8..a7eb3b73a851 100644 --- a/code/modules/persistent_cache/persistent_cache.dm +++ b/code/modules/persistent_cache/persistent_cache.dm @@ -8,8 +8,6 @@ // I = do_generation() // save_persistent_cache(I, "filename.dmi", "icons/source.dmi", "icons/source2.dmi") -#define PERSISTENT_CACHE_FOLDER "cache/persistent" - // use wrapper try_access_persistent_cache() /proc/_try_access_persistent_cache(filename_key, list/key_files) var/key_files_hash = key_files_hash(key_files) @@ -53,5 +51,3 @@ var/global/list/md5_files_cache = list() . += md5_files_cache[path] . = md5(.) // just for short nice name - -#undef PERSISTENT_CACHE_FOLDER diff --git a/code/modules/statistic/statistics.dm b/code/modules/statistic/statistics.dm index 85ef4c9c611f..f3261b4b886e 100644 --- a/code/modules/statistic/statistics.dm +++ b/code/modules/statistic/statistics.dm @@ -118,7 +118,8 @@ var/global/datum/stat_collector/SSStatistics = new /datum/stat_collector minimap_image = "nano/images/nanomap_[SSmapping.station_image]_1.png" server_address = BYOND_SERVER_ADDRESS base_commit_sha = global.base_commit_sha - test_merges = global.test_merges + if(global.test_merges) + test_merges = "#" + jointext(global.test_merges, "# ") completion_html = SSticker.mode.completition_text save_manifest_entries() diff --git a/config/example/config.txt b/config/example/config.txt index a1783326911e..fb1067957768 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -162,10 +162,14 @@ ALIEN_AVAILABLE_BY_TIME DIONA 40000 ## Required time to respawn after death #DEATHTIME_REQUIRED 18000 -## Path to the python executable on the system. Leave blank for default. +## Path to the python executable on the system. Leave blank for default. ## Default is "python" on Windows, "/usr/bin/env python2" on UNIX. #PYTHON_PATH +## GitHub PAT with no scopes to increase rate limits of GitHub requests. +## Ignored when PYTHON_PATH is not set. +#GITHUB_TOKEN + ## Expected round length in minutes EXPECTED_ROUND_LENGTH 90 diff --git a/interface/interface.dm b/interface/interface.dm index 657f7237eb6a..8f3f5576f320 100644 --- a/interface/interface.dm +++ b/interface/interface.dm @@ -35,7 +35,7 @@ var/url_params = "[issue_template]" if(global.round_id || config.server_name) url_params += "Issue reported from [global.round_id ? " Round ID: [global.round_id][servername ? " ([servername])" : ""]" : servername]\n" - url_params += "Testmerges: ```[test_merges ? test_merges : "No test merges"]```\n" + url_params += "Testmerges: ```[test_merges ? "#" + jointext(test_merges, "# ") : "No test merges"]```\n" url_params += "Reporting client version: [byond_version].[byond_build]\n" DIRECT_OUTPUT(src, link("[githuburl]/issues/new?body=[url_encode(url_params)]")) diff --git a/scripts/fetch_test_merges.py b/scripts/fetch_test_merges.py new file mode 100644 index 000000000000..768888be8c1c --- /dev/null +++ b/scripts/fetch_test_merges.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +# Fetches titles of test merged PRs from GitHub and saves them to cache +# Args: +# * PR IDs +# * GitHub token (optional) +# * Repository owner and name (optional e.g. 'TauCetiStation/TauCetiClassic' +# * * If not set will try to get names from cache (in case you want to use something else to fetch and store test merges) +# Returns: +# * JSON-encoded object of test merges and their titles + +import argparse +from dataclasses import dataclass +import json +import sys +from pathlib import Path +from concurrent.futures import ThreadPoolExecutor +import requests + + +@dataclass +class FetchArgs: + pr_id: str + repo: str + headers: dict + + +def read_arguments(): + parser = argparse.ArgumentParser(description="get wrapper") + + parser.add_argument("prs", nargs="+") + + parser.add_argument("-t", "--token", default=None) + + parser.add_argument("-r", "--repo_owner_and_name", default=None, dest="repo") + + return parser.parse_args() + + +def fetch_merge(args: FetchArgs): + try: + resp = requests.get( + f"https://api.github.com/repos/{args.repo}/pulls/{args.pr_id}", + timeout=30, + headers=args.headers, + ) + if resp.status_code == 429: + # you look at your anonymous access and sigh + return args.pr_id, ("GITHUB API ERROR: RATE LIMITED", False) + if resp.status_code == 404: + # you look at your shithub and sigh + return args.pr_id, ("GITHUB API ERROR: PR NOT FOUND", False) + if resp.status_code == 401: + # you look at your token and sigh + return args.pr_id, ("GITHUB API ERROR: BAD CREDENTIALS", False) + if resp.status_code != 200: + error_msg = json.loads(resp.text)["message"] + print(error_msg, file=sys.stderr) + return args.pr_id, ("GITHUB API ERROR", False) + json_object = json.loads(resp.text) + return args.pr_id, (f'{json_object["title"]} ({json_object["user"]["login"]})', True) + except (requests.exceptions.RequestException, json.JSONDecodeError) as exc: + print(exc, file=sys.stderr) + return args.pr_id, ("FAILED TO GET PR TITLE", False) + + +def main(options): + base_cache_path = Path("cache/github/pr") + base_cache_path.mkdir(parents=True, exist_ok=True) + + test_merges = {} + + to_fetch = set(options.prs) + headers = { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + } + if options.token: + headers["Authorization"] = f"Bearer {options.token}" + + with ThreadPoolExecutor(max_workers=10) as pool: + for merge_id, (title, success) in pool.map( + fetch_merge, + (FetchArgs(merge_id, options.repo, headers) for merge_id in to_fetch), + ): + test_merges[merge_id] = {'title': title, 'success': success} + + sys.stdout.buffer.write(json.dumps(test_merges).encode("utf-8")) + + return 0 + + +if __name__ == "__main__": + sys.exit(main(read_arguments()))